Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Side by Side Diff: chrome_mac/Google Chrome Packaging/keystone_install.sh

Issue 85333005: Update reference builds to Chrome 32.0.1700.19 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/reference_builds/
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:executable
+ *
OLDNEW
(Empty)
1 #!/bin/bash -p
2
3 # Copyright (c) 2012 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: keystone_install.sh update_dmg_mount_point
8 #
9 # Called by the Keystone system to update the installed application with a new
10 # version from a disk image.
11 #
12 # Environment variables:
13 # GOOGLE_CHROME_UPDATER_DEBUG
14 # When set to a non-empty value, additional information about this script's
15 # actions will be logged to stderr. The same debugging information will
16 # also be enabled when "Library/Google/Google Chrome Updater Debug" in the
17 # root directory or in ${HOME} exists.
18 #
19 # Exit codes:
20 # 0 Happiness
21 # 1 Unknown failure
22 # 2 Basic sanity check source failure (e.g. no app on disk image)
23 # 3 Basic sanity check destination failure (e.g. ticket points to nothing)
24 # 4 Update driven by user ticket when a system ticket is also present
25 # 5 Could not prepare existing installed version to receive update
26 # 6 Patch sanity check failure
27 # 7 rsync failed (could not copy new versioned directory to Versions)
28 # 8 rsync failed (could not update outer .app bundle)
29 # 9 Could not get the version, update URL, or channel after update
30 # 10 Updated application does not have the version number from the update
31 # 11 ksadmin failure
32 # 12 dirpatcher failed for versioned directory
33 # 13 dirpatcher failed for outer .app bundle
34 #
35 # The following exit codes are not used by this script, but can be used to
36 # convey special meaning to Keystone:
37 # 66 (unused) success, request reboot
38 # 77 (unused) try installation again later
39
40 set -eu
41
42 # http://b/2290916: Keystone runs the installation with a restrictive PATH
43 # that only includes the directory containing ksadmin, /bin, and /usr/bin. It
44 # does not include /sbin or /usr/sbin. This script uses lsof, which is in
45 # /usr/sbin, and it's conceivable that it might want to use other tools in an
46 # sbin directory. Adjust the path accordingly.
47 export PATH="${PATH}:/sbin:/usr/sbin"
48
49 # Environment sanitization. Clear environment variables that might impact the
50 # interpreter's operation. The |bash -p| invocation on the #! line takes the
51 # bite out of BASH_ENV, ENV, and SHELLOPTS (among other features), but
52 # clearing them here ensures that they won't impact any shell scripts used as
53 # utility programs. SHELLOPTS is read-only and can't be unset, only
54 # unexported.
55 unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
56 export -n SHELLOPTS
57
58 set -o pipefail
59 shopt -s nullglob
60
61 ME="$(basename "${0}")"
62 readonly ME
63
64 # Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3
65 # In bash 4.0, "declare VAR" no longer initializes VAR if not already set.
66 : ${GOOGLE_CHROME_UPDATER_DEBUG:=}
67 err() {
68 local error="${1}"
69
70 local id=
71 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
72 id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
73 fi
74
75 echo "${ME}${id}: ${error}" >& 2
76 }
77
78 note() {
79 local message="${1}"
80
81 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
82 err "${message}"
83 fi
84 }
85
86 g_temp_dir=
87 cleanup() {
88 local status=${?}
89
90 trap - EXIT
91 trap '' HUP INT QUIT TERM
92
93 if [[ ${status} -ge 128 ]]; then
94 err "Caught signal $((${status} - 128))"
95 fi
96
97 if [[ -n "${g_temp_dir}" ]]; then
98 rm -rf "${g_temp_dir}"
99 fi
100
101 exit ${status}
102 }
103
104 ensure_temp_dir() {
105 if [[ -z "${g_temp_dir}" ]]; then
106 # Choose a template that won't be a dot directory. Make it safe by
107 # removing leading hyphens, too.
108 local template="${ME}"
109 if [[ "${template}" =~ ^[-.]+(.*)$ ]]; then
110 template="${BASH_REMATCH[1]}"
111 fi
112 if [[ -z "${template}" ]]; then
113 template="keystone_install"
114 fi
115
116 g_temp_dir="$(mktemp -d -t "${template}")"
117 note "g_temp_dir = ${g_temp_dir}"
118 fi
119 }
120
121 # Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
122 # writable on the basis of its POSIX permissions. This is used to determine
123 # writability like test's -w primary, but -w resolves symbolic links and this
124 # function does not.
125 is_writable_symlink() {
126 local symlink="${1}"
127
128 local link_mode
129 link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
130 if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
131 return 1
132 fi
133
134 local link_user link_group
135 link_user="$(stat -f %u "${symlink}" 2> /dev/null || true)"
136 link_group="$(stat -f %g "${symlink}" 2> /dev/null || true)"
137 if [[ -z "${link_user}" ]] || [[ -z "${link_group}" ]]; then
138 return 1
139 fi
140
141 # If the users match, check the owner-write bit.
142 if [[ ${EUID} -eq "${link_user}" ]]; then
143 if [[ "${link_mode:2:1}" = "w" ]]; then
144 return 0
145 fi
146 return 1
147 fi
148
149 # If the file's group matches any of the groups that this process is a
150 # member of, check the group-write bit.
151 local group_match=
152 local group
153 for group in "${GROUPS[@]}"; do
154 if [[ "${group}" -eq "${link_group}" ]]; then
155 group_match="y"
156 break
157 fi
158 done
159 if [[ -n "${group_match}" ]]; then
160 if [[ "${link_mode:5:1}" = "w" ]]; then
161 return 0
162 fi
163 return 1
164 fi
165
166 # Check the other-write bit.
167 if [[ "${link_mode:8:1}" = "w" ]]; then
168 return 0
169 fi
170
171 return 1
172 }
173
174 # If |symlink| exists and is a symbolic link, but is not writable according to
175 # is_writable_symlink, this function attempts to replace it with a new
176 # writable symbolic link. If |symlink| does not exist, is not a symbolic
177 # link, or is already writable, this function does nothing. This function
178 # always returns 0 (true).
179 ensure_writable_symlink() {
180 local symlink="${1}"
181
182 if [[ -L "${symlink}" ]] && ! is_writable_symlink "${symlink}"; then
183 # If ${symlink} refers to a directory, doing this naively might result in
184 # the new link being placed in that directory, instead of replacing the
185 # existing link. ln -fhs is supposed to handle this case, but it does so
186 # by unlinking (removing) the existing symbolic link before creating a new
187 # one. That leaves a small window during which the symbolic link is not
188 # present on disk at all.
189 #
190 # To avoid that possibility, a new symbolic link is created in a temporary
191 # location and then swapped into place with mv. An extra temporary
192 # directory is used to convince mv to replace the symbolic link: again, if
193 # the existing link refers to a directory, "mv newlink oldlink" will
194 # actually leave oldlink alone and place newlink into the directory.
195 # "mv newlink dirname(oldlink)" works as expected, but in order to replace
196 # oldlink, newlink must have the same basename, hence the temporary
197 # directory.
198
199 local target
200 target="$(readlink "${symlink}" 2> /dev/null || true)"
201 if [[ -z "${target}" ]]; then
202 return 0
203 fi
204
205 # Error handling strategy: if anything fails, such as the mktemp, ln,
206 # chmod, or mv, ignore the failure and return 0 (success), leaving the
207 # existing state with the non-writable symbolic link intact. Failures
208 # in this function will be difficult to understand and diagnose, and a
209 # non-writable symbolic link is not necessarily fatal. If something else
210 # requires a writable symbolic link, allowing it to fail when a symbolic
211 # link is not writable is easier to understand than bailing out of the
212 # script on failure here.
213
214 local symlink_dir temp_link_dir temp_link
215 symlink_dir="$(dirname "${symlink}")"
216 temp_link_dir="$(mktemp -d "${symlink_dir}/.symlink_temp.XXXXXX" || true)"
217 if [[ -z "${temp_link_dir}" ]]; then
218 return 0
219 fi
220 temp_link="${temp_link_dir}/$(basename "${symlink}")"
221
222 (ln -fhs "${target}" "${temp_link}" && \
223 chmod -h 755 "${temp_link}" && \
224 mv -f "${temp_link}" "${symlink_dir}/") || true
225 rm -rf "${temp_link_dir}"
226 fi
227
228 return 0
229 }
230
231 # ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
232 # symbolic link in |directory|, recursively.
233 #
234 # In some very weird and rare cases, it is possible to wind up with a user
235 # installation that contains symbolic links that the user does not have write
236 # permission over. More on how that might happen later.
237 #
238 # If a weird and rare case like this is observed, rsync will exit with an
239 # error when attempting to update the times on these symbolic links. rsync
240 # may not be intelligent enough to try creating a new symbolic link in these
241 # cases, but this script can be.
242 #
243 # The problem occurs when an administrative user first drag-installs the
244 # application to /Applications, resulting in the program's user being set to
245 # the user's own ID. If, subsequently, a .pkg package is installed over that,
246 # the existing directory ownership will be preserved, but file ownership will
247 # be changed to whatever is specified by the package, typically root. This
248 # applies to symbolic links as well. On a subsequent update, rsync will be
249 # able to copy the new files into place, because the user still has permission
250 # to write to the directories. If the symbolic link targets are not changing,
251 # though, rsync will not replace them, and they will remain owned by root.
252 # The user will not have permission to update the time on the symbolic links,
253 # resulting in an rsync error.
254 ensure_writable_symlinks_recursive() {
255 local directory="${1}"
256
257 # This fix-up is not necessary when running as root, because root will
258 # always be able to write everything needed.
259 if [[ ${EUID} -eq 0 ]]; then
260 return 0
261 fi
262
263 # This step isn't critical.
264 local set_e=
265 if [[ "${-}" =~ e ]]; then
266 set_e="y"
267 set +e
268 fi
269
270 # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
271 local symlink
272 while IFS= read -r -d $'\0' symlink; do
273 ensure_writable_symlink "${symlink}"
274 done < <(find "${directory}" -type l -print0)
275
276 # Go back to how things were.
277 if [[ -n "${set_e}" ]]; then
278 set -e
279 fi
280 }
281
282 # is_version_ge accepts two version numbers, left and right, and performs a
283 # piecewise comparison determining the result of left >= right, returning true
284 # (0) if left >= right, and false (1) if left < right. If left or right are
285 # missing components relative to the other, the missing components are assumed
286 # to be 0, such that 10.6 == 10.6.0.
287 is_version_ge() {
288 local left="${1}"
289 local right="${2}"
290
291 local -a left_array right_array
292 IFS=. left_array=(${left})
293 IFS=. right_array=(${right})
294
295 local left_count=${#left_array[@]}
296 local right_count=${#right_array[@]}
297 local count=${left_count}
298 if [[ ${right_count} -lt ${count} ]]; then
299 count=${right_count}
300 fi
301
302 # Compare the components piecewise, as long as there are corresponding
303 # components on each side. If left_element and right_element are unequal,
304 # a comparison can be made.
305 local index=0
306 while [[ ${index} -lt ${count} ]]; do
307 local left_element="${left_array[${index}]}"
308 local right_element="${right_array[${index}]}"
309 if [[ ${left_element} -gt ${right_element} ]]; then
310 return 0
311 elif [[ ${left_element} -lt ${right_element} ]]; then
312 return 1
313 fi
314 ((++index))
315 done
316
317 # If there are more components on the left than on the right, continue
318 # comparing, assuming 0 for each of the missing components on the right.
319 while [[ ${index} -lt ${left_count} ]]; do
320 local left_element="${left_array[${index}]}"
321 if [[ ${left_element} -gt 0 ]]; then
322 return 0
323 fi
324 ((++index))
325 done
326
327 # If there are more components on the right than on the left, continue
328 # comparing, assuming 0 for each of the missing components on the left.
329 while [[ ${index} -lt ${right_count} ]]; do
330 local right_element="${right_array[${index}]}"
331 if [[ ${right_element} -gt 0 ]]; then
332 return 1
333 fi
334 ((++index))
335 done
336
337 # Upon reaching this point, the two version numbers are semantically equal.
338 return 0
339 }
340
341 # Prints the OS version, as reported by sw_vers -productVersion, to stdout.
342 # This function operates with "static" variables: it will only check the OS
343 # version once per script run.
344 g_checked_os_version=
345 g_os_version=
346 os_version() {
347 if [[ -z "${g_checked_os_version}" ]]; then
348 g_checked_os_version="y"
349 g_os_version="$(sw_vers -productVersion)"
350 note "g_os_version = ${g_os_version}"
351 fi
352 echo "${g_os_version}"
353 return 0
354 }
355
356 # Compares the running OS version against a supplied version number,
357 # |check_version|, and returns 0 (true) if the running OS version is greater
358 # than or equal to |check_version| according to a piece-wise comparison.
359 # Returns 1 (false) if the running OS version number cannot be determined or
360 # if |check_version| is greater than the running OS version. |check_version|
361 # should be a string of the form "major.minor" or "major.minor.micro".
362 is_os_version_ge() {
363 local check_version="${1}"
364
365 local os_version="$(os_version)"
366 is_version_ge "${os_version}" "${check_version}"
367
368 # The return value of is_version_ge is used as this function's return value.
369 }
370
371 # Returns 0 (true) if xattr supports -r for recursive operation.
372 os_xattr_supports_r() {
373 # xattr -r is supported in Mac OS X 10.6.
374 is_os_version_ge 10.6
375
376 # The return value of is_os_version_ge is used as this function's return
377 # value.
378 }
379
380 # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
381 # stdout. This function operates with "static" variables: it will only check
382 # the ksadmin version once per script run. If ksadmin is old enough to not
383 # support --ksadmin-version, or another error occurs, this function prints an
384 # empty string.
385 g_checked_ksadmin_version=
386 g_ksadmin_version=
387 ksadmin_version() {
388 if [[ -z "${g_checked_ksadmin_version}" ]]; then
389 g_checked_ksadmin_version="y"
390 g_ksadmin_version="$(ksadmin --ksadmin-version || true)"
391 note "g_ksadmin_version = ${g_ksadmin_version}"
392 fi
393 echo "${g_ksadmin_version}"
394 return 0
395 }
396
397 # Compares the installed ksadmin version against a supplied version number,
398 # |check_version|, and returns 0 (true) if the installed Keystone version is
399 # greater than or equal to |check_version| according to a piece-wise
400 # comparison. Returns 1 (false) if the installed Keystone version number
401 # cannot be determined or if |check_version| is greater than the installed
402 # Keystone version. |check_version| should be a string of the form
403 # "major.minor.micro.build".
404 is_ksadmin_version_ge() {
405 local check_version="${1}"
406
407 local ksadmin_version="$(ksadmin_version)"
408 is_version_ge "${ksadmin_version}" "${check_version}"
409
410 # The return value of is_version_ge is used as this function's return value.
411 }
412
413 # Returns 0 (true) if ksadmin supports --tag.
414 ksadmin_supports_tag() {
415 local ksadmin_version
416
417 ksadmin_version="$(ksadmin_version)"
418 if [[ -n "${ksadmin_version}" ]]; then
419 # A ksadmin that recognizes --ksadmin-version and provides a version
420 # number is new enough to recognize --tag.
421 return 0
422 fi
423
424 return 1
425 }
426
427 # Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
428 ksadmin_supports_tagpath_tagkey() {
429 # --tag-path and --tag-key were introduced in Keystone 1.0.7.1306.
430 is_ksadmin_version_ge 1.0.7.1306
431
432 # The return value of is_ksadmin_version_ge is used as this function's
433 # return value.
434 }
435
436 # Returns 0 (true) if ksadmin supports --brand-path and --brand-key.
437 ksadmin_supports_brandpath_brandkey() {
438 # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620.
439 is_ksadmin_version_ge 1.0.8.1620
440
441 # The return value of is_ksadmin_version_ge is used as this function's
442 # return value.
443 }
444
445 # Returns 0 (true) if ksadmin supports --version-path and --version-key.
446 ksadmin_supports_versionpath_versionkey() {
447 # --version-path and --version-key were introduced in Keystone 1.0.9.2318.
448 is_ksadmin_version_ge 1.0.9.2318
449
450 # The return value of is_ksadmin_version_ge is used as this function's
451 # return value.
452 }
453
454 # Runs "defaults read" to obtain the value of a key in a property list. As
455 # with "defaults read", an absolute path to a plist is supplied, without the
456 # ".plist" extension.
457 #
458 # As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences)
459 # normally communicates with cfprefsd to read and write plists. Changes to a
460 # plist file aren't necessarily reflected immediately via this API family when
461 # not made through this API family, because cfprefsd may return cached data
462 # from a former on-disk version of a plist file instead of reading the current
463 # version from disk. The old behavior can be restored by setting the
464 # __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care
465 # should be used because portions of the system that use this API family
466 # normally and thus use cfprefsd and its cache will become unsynchronized with
467 # the on-disk state.
468 #
469 # This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling
470 # "defaults read" and thus avoid cfprefsd and its on-disk cache, and is
471 # intended only to be used to read values from Info.plist files, which are not
472 # preferences. The use of "defaults" for this purpose has always been
473 # questionable, but there's no better option to interact with plists from
474 # shell scripts. Definitely don't use infoplist_read to read preference
475 # plists.
476 #
477 # This function exists because the update process delivers new copies of
478 # Info.plist files to the disk behind cfprefsd's back, and if cfprefsd becomes
479 # aware of the original version of the file for any reason (such as this
480 # script reading values from it via "defaults read"), the new version of the
481 # file will not be immediately effective or visible via cfprefsd after the
482 # update is applied.
483 infoplist_read() {
484 __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
485 }
486
487 usage() {
488 echo "usage: ${ME} update_dmg_mount_point" >& 2
489 }
490
491 main() {
492 local update_dmg_mount_point="${1}"
493
494 # Early steps are critical. Don't continue past any failure.
495 set -e
496
497 trap cleanup EXIT HUP INT QUIT TERM
498
499 readonly PRODUCT_NAME="Google Chrome"
500 readonly APP_DIR="${PRODUCT_NAME}.app"
501 readonly ALTERNATE_APP_DIR="${PRODUCT_NAME} Canary.app"
502 readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
503 readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
504 readonly PATCH_DIR=".patch"
505 readonly CONTENTS_DIR="Contents"
506 readonly APP_PLIST="${CONTENTS_DIR}/Info"
507 readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions"
508 readonly UNROOTED_BRAND_PLIST="Library/Google/Google Chrome Brand"
509 readonly UNROOTED_DEBUG_FILE="Library/Google/Google Chrome Updater Debug"
510
511 readonly APP_VERSION_KEY="CFBundleShortVersionString"
512 readonly APP_BUNDLEID_KEY="CFBundleIdentifier"
513 readonly KS_VERSION_KEY="KSVersion"
514 readonly KS_PRODUCT_KEY="KSProductID"
515 readonly KS_URL_KEY="KSUpdateURL"
516 readonly KS_CHANNEL_KEY="KSChannelID"
517 readonly KS_BRAND_KEY="KSBrandID"
518
519 readonly QUARANTINE_ATTR="com.apple.quarantine"
520 readonly KEYCHAIN_REAUTHORIZE_DIR=".keychain_reauthorize"
521
522 # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners
523 # and groups, respectively, from the source, and that is undesirable in this
524 # case. -D copies devices and special files; copying devices only works
525 # when running as root, so for consistency between privileged and
526 # unprivileged operation, this option is omitted as well.
527 # -I, --ignore-times don't skip files that match in size and mod-time
528 # -l, --links copy symlinks as symlinks
529 # -r, --recursive recurse into directories
530 # -p, --perms preserve permissions
531 # -t, --times preserve times
532 readonly RSYNC_FLAGS="-Ilprt"
533
534 # It's difficult to get GOOGLE_CHROME_UPDATER_DEBUG set in the environment
535 # when this script is called from Keystone. If a "debug file" exists in
536 # either the root directory or the home directory of the user who owns the
537 # ticket, turn on verbosity. This may aid debugging.
538 if [[ -e "/${UNROOTED_DEBUG_FILE}" ]] ||
539 [[ -e ~/"${UNROOTED_DEBUG_FILE}" ]]; then
540 export GOOGLE_CHROME_UPDATER_DEBUG="y"
541 fi
542
543 note "update_dmg_mount_point = ${update_dmg_mount_point}"
544
545 # The argument should be the disk image path. Make sure it exists and that
546 # it's an absolute path.
547 note "checking update"
548
549 if [[ -z "${update_dmg_mount_point}" ]] ||
550 [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
551 ! [[ -d "${update_dmg_mount_point}" ]]; then
552 err "update_dmg_mount_point must be an absolute path to a directory"
553 usage
554 exit 2
555 fi
556
557 local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
558 if [[ "${patch_dir:0:1}" != "/" ]]; then
559 note "patch_dir = ${patch_dir}"
560 err "patch_dir must be an absolute path"
561 exit 2
562 fi
563
564 # Figure out if this is an ordinary installation disk image being used as a
565 # full update, or a patch. A patch will have a .patch directory at the root
566 # of the disk image containing information about the update, tools to apply
567 # it, and the update contents.
568 local is_patch=
569 local dirpatcher=
570 if [[ -d "${patch_dir}" ]]; then
571 # patch_dir exists and is a directory - this is a patch update.
572 is_patch="y"
573 dirpatcher="${patch_dir}/dirpatcher.sh"
574 if ! [[ -x "${dirpatcher}" ]]; then
575 err "couldn't locate dirpatcher"
576 exit 6
577 fi
578 elif [[ -e "${patch_dir}" ]]; then
579 # patch_dir exists, but is not a directory - what's that mean?
580 note "patch_dir = ${patch_dir}"
581 err "patch_dir must be a directory"
582 exit 2
583 else
584 # patch_dir does not exist - this is a full "installer."
585 patch_dir=
586 fi
587 note "patch_dir = ${patch_dir}"
588 note "is_patch = ${is_patch}"
589 note "dirpatcher = ${dirpatcher}"
590
591 # The update to install.
592
593 # update_app is the path to the new version of the .app. It will only be
594 # set at this point for a non-patch update. It is not yet set for a patch
595 # update because no such directory exists yet; it will be set later when
596 # dirpatcher creates it.
597 local update_app=
598
599 # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
600 # be set for patch updates.
601 local update_version_app_old=
602 local patch_app_dir=
603 local patch_versioned_dir=
604
605 local update_version_app update_version_ks product_id
606 if [[ -z "${is_patch}" ]]; then
607 update_app="${update_dmg_mount_point}/${APP_DIR}"
608 note "update_app = ${update_app}"
609
610 # Make sure that it's an absolute path.
611 if [[ "${update_app:0:1}" != "/" ]]; then
612 err "update_app must be an absolute path"
613 exit 2
614 fi
615
616 # Make sure there's something to copy from.
617 if ! [[ -d "${update_app}" ]]; then
618 update_app="${update_dmg_mount_point}/${ALTERNATE_APP_DIR}"
619 note "update_app = ${update_app}"
620
621 if [[ "${update_app:0:1}" != "/" ]]; then
622 err "update_app (alternate) must be an absolute path"
623 exit 2
624 fi
625
626 if ! [[ -d "${update_app}" ]]; then
627 err "update_app must be a directory"
628 exit 2
629 fi
630 fi
631
632 # Get some information about the update.
633 note "reading update values"
634
635 local update_app_plist="${update_app}/${APP_PLIST}"
636 note "update_app_plist = ${update_app_plist}"
637 if ! update_version_app="$(infoplist_read "${update_app_plist}" \
638 "${APP_VERSION_KEY}")" ||
639 [[ -z "${update_version_app}" ]]; then
640 err "couldn't determine update_version_app"
641 exit 2
642 fi
643 note "update_version_app = ${update_version_app}"
644
645 local update_ks_plist="${update_app_plist}"
646 note "update_ks_plist = ${update_ks_plist}"
647 if ! update_version_ks="$(infoplist_read "${update_ks_plist}" \
648 "${KS_VERSION_KEY}")" ||
649 [[ -z "${update_version_ks}" ]]; then
650 err "couldn't determine update_version_ks"
651 exit 2
652 fi
653 note "update_version_ks = ${update_version_ks}"
654
655 if ! product_id="$(infoplist_read "${update_ks_plist}" \
656 "${KS_PRODUCT_KEY}")" ||
657 [[ -z "${product_id}" ]]; then
658 err "couldn't determine product_id"
659 exit 2
660 fi
661 note "product_id = ${product_id}"
662 else # [[ -n "${is_patch}" ]]
663 # Get some information about the update.
664 note "reading update values"
665
666 if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
667 [[ -z "${update_version_app_old}" ]]; then
668 err "couldn't determine update_version_app_old"
669 exit 2
670 fi
671 note "update_version_app_old = ${update_version_app_old}"
672
673 if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
674 [[ -z "${update_version_app}" ]]; then
675 err "couldn't determine update_version_app"
676 exit 2
677 fi
678 note "update_version_app = ${update_version_app}"
679
680 if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
681 [[ -z "${update_version_ks}" ]]; then
682 err "couldn't determine update_version_ks"
683 exit 2
684 fi
685 note "update_version_ks = ${update_version_ks}"
686
687 if ! product_id=$(<"${patch_dir}/ks_product") ||
688 [[ -z "${product_id}" ]]; then
689 err "couldn't determine product_id"
690 exit 2
691 fi
692 note "product_id = ${product_id}"
693
694 patch_app_dir="${patch_dir}/application.dirpatch"
695 if ! [[ -d "${patch_app_dir}" ]]; then
696 err "couldn't locate patch_app_dir"
697 exit 6
698 fi
699 note "patch_app_dir = ${patch_app_dir}"
700
701 patch_versioned_dir=\
702 "${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
703 if ! [[ -d "${patch_versioned_dir}" ]]; then
704 err "couldn't locate patch_versioned_dir"
705 exit 6
706 fi
707 note "patch_versioned_dir = ${patch_versioned_dir}"
708 fi
709
710 # ksadmin is required. Keystone should have set a ${PATH} that includes it.
711 # Check that here, so that more useful feedback can be offered in the
712 # unlikely event that ksadmin is missing.
713 note "checking Keystone"
714
715 local ksadmin_path
716 if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then
717 err "couldn't locate ksadmin_path"
718 exit 3
719 fi
720 note "ksadmin_path = ${ksadmin_path}"
721
722 # Call ksadmin_version once to prime the global state. This is needed
723 # because subsequent calls to ksadmin_version that occur in $(...)
724 # expansions will not affect the global state (although they can read from
725 # the already-initialized global state) and thus will cause a new ksadmin
726 # --ksadmin-version process to run for each check unless the globals have
727 # been properly initialized beforehand.
728 ksadmin_version >& /dev/null || true
729 local ksadmin_version_string
730 ksadmin_version_string="$(ksadmin_version 2> /dev/null || true)"
731 note "ksadmin_version_string = ${ksadmin_version_string}"
732
733 # Figure out where to install.
734 local installed_app
735 if ! installed_app="$(ksadmin -pP "${product_id}" | sed -Ene \
736 "s%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>\$%\\1%p")" ||
737 [[ -z "${installed_app}" ]]; then
738 err "couldn't locate installed_app"
739 exit 3
740 fi
741 note "installed_app = ${installed_app}"
742
743 if [[ "${installed_app:0:1}" != "/" ]] ||
744 ! [[ -d "${installed_app}" ]]; then
745 err "installed_app must be an absolute path to a directory"
746 exit 3
747 fi
748
749 # If this script is running as root, it's being driven by a system ticket.
750 # Otherwise, it's being driven by a user ticket.
751 local system_ticket=
752 if [[ ${EUID} -eq 0 ]]; then
753 system_ticket="y"
754 fi
755 note "system_ticket = ${system_ticket}"
756
757 # If this script is being driven by a user ticket, but a system ticket is
758 # also present, there's a potential for the two to collide. Both ticket
759 # types might be present if another user on the system promoted the ticket
760 # to system: the other user could not have removed this user's user ticket.
761 # Handle that case here by deleting the user ticket and exiting early with
762 # a discrete exit code.
763 #
764 # Current versions of ksadmin will exit 1 (false) when asked to print tickets
765 # and given a specific product ID to print. Older versions of ksadmin would
766 # exit 0 (true), but those same versions did not support -S (meaning to check
767 # the system ticket store) and would exit 1 (false) with this invocation due
768 # to not understanding the question. Therefore, the usage here will only
769 # delete the existing user ticket when running as non-root with access to a
770 # sufficiently recent ksadmin. Older ksadmins are tolerated: the update will
771 # likely fail for another reason and the user ticket will hang around until
772 # something is eventually able to remove it.
773 if [[ -z "${system_ticket}" ]] &&
774 ksadmin -S --print-tickets --productid "${product_id}" >& /dev/null; then
775 ksadmin --delete --productid "${product_id}" || true
776 err "can't update on a user ticket when a system ticket is also present"
777 exit 4
778 fi
779
780 # Figure out what the existing installed application is using for its
781 # versioned directory. This will be used later, to avoid removing the
782 # existing installed version's versioned directory in case anything is still
783 # using it.
784 note "reading install values"
785
786 local installed_app_plist="${installed_app}/${APP_PLIST}"
787 note "installed_app_plist = ${installed_app_plist}"
788 local installed_app_plist_path="${installed_app_plist}.plist"
789 note "installed_app_plist_path = ${installed_app_plist_path}"
790 local old_version_app
791 old_version_app="$(infoplist_read "${installed_app_plist}" \
792 "${APP_VERSION_KEY}" || true)"
793 note "old_version_app = ${old_version_app}"
794
795 # old_version_app is not required, because it won't be present in skeleton
796 # bootstrap installations, which just have an empty .app directory. Only
797 # require it when doing a patch update, and use it to validate that the
798 # patch applies to the old installed version. By definition, skeleton
799 # bootstraps can't be installed with patch updates. They require the full
800 # application on the disk image.
801 if [[ -n "${is_patch}" ]]; then
802 if [[ -z "${old_version_app}" ]]; then
803 err "old_version_app required for patch"
804 exit 6
805 elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
806 err "this patch does not apply to the installed version"
807 exit 6
808 fi
809 fi
810
811 local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
812 note "installed_versions_dir = ${installed_versions_dir}"
813
814 # If the installed application is incredibly old, old_versioned_dir may not
815 # exist.
816 local old_versioned_dir
817 if [[ -n "${old_version_app}" ]]; then
818 old_versioned_dir="${installed_versions_dir}/${old_version_app}"
819 fi
820 note "old_versioned_dir = ${old_versioned_dir}"
821
822 # Collect the installed application's brand code, it will be used later. It
823 # is not an error for the installed application to not have a brand code.
824 local old_ks_plist="${installed_app_plist}"
825 note "old_ks_plist = ${old_ks_plist}"
826 local old_brand
827 old_brand="$(infoplist_read "${old_ks_plist}" \
828 "${KS_BRAND_KEY}" 2> /dev/null ||
829 true)"
830 note "old_brand = ${old_brand}"
831
832 ensure_writable_symlinks_recursive "${installed_app}"
833
834 # By copying to ${installed_app}, the existing application name will be
835 # preserved, if the user has renamed the application on disk. Respecting
836 # the user's changes is friendly.
837
838 # Make sure that ${installed_versions_dir} exists, so that it can receive
839 # the versioned directory. It may not exist if updating from an older
840 # version that did not use the versioned layout on disk. Later, during the
841 # rsync to copy the application directory, the mode bits and timestamp on
842 # ${installed_versions_dir} will be set to conform to whatever is present in
843 # the update.
844 #
845 # ${installed_app} is guaranteed to exist at this point, but
846 # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
847 # if this update is actually an initial installation from a Keystone
848 # skeleton bootstrap. The mkdir creates ${installed_app}/${CONTENTS_DIR} if
849 # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
850 note "creating installed_versions_dir"
851 if ! mkdir -p "${installed_versions_dir}"; then
852 err "mkdir of installed_versions_dir failed"
853 exit 5
854 fi
855
856 local new_versioned_dir
857 new_versioned_dir="${installed_versions_dir}/${update_version_app}"
858 note "new_versioned_dir = ${new_versioned_dir}"
859
860 # If there's an entry at ${new_versioned_dir} but it's not a directory
861 # (or it's a symbolic link, whether or not it points to a directory), rsync
862 # won't get rid of it. It's never correct to have a non-directory in place
863 # of the versioned directory, so toss out whatever's there. Don't treat
864 # this as a critical step: if removal fails, operation can still proceed to
865 # to the dirpatcher or rsync, which will likely fail.
866 if [[ -e "${new_versioned_dir}" ]] &&
867 ([[ -L "${new_versioned_dir}" ]] ||
868 ! [[ -d "${new_versioned_dir}" ]]); then
869 note "removing non-directory in place of versioned directory"
870 rm -f "${new_versioned_dir}" 2> /dev/null || true
871 fi
872
873 local update_versioned_dir
874 if [[ -z "${is_patch}" ]]; then
875 update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
876 note "update_versioned_dir = ${update_versioned_dir}"
877 else # [[ -n "${is_patch}" ]]
878 # dirpatcher won't patch into a directory that already exists. Doing so
879 # would be a bad idea, anyway. If ${new_versioned_dir} already exists,
880 # it may be something left over from a previous failed or incomplete
881 # update attempt, or it may be the live versioned directory if this is a
882 # same-version update intended only to change channels. Since there's no
883 # way to tell, this case is handled by having dirpatcher produce the new
884 # versioned directory in a temporary location and then having rsync copy
885 # it into place as an ${update_versioned_dir}, the same as in a non-patch
886 # update. If ${new_versioned_dir} doesn't exist, dirpatcher can place the
887 # new versioned directory at that location directly.
888 local versioned_dir_target
889 if ! [[ -e "${new_versioned_dir}" ]]; then
890 versioned_dir_target="${new_versioned_dir}"
891 note "versioned_dir_target = ${versioned_dir_target}"
892 else
893 ensure_temp_dir
894 versioned_dir_target="${g_temp_dir}/${update_version_app}"
895 note "versioned_dir_target = ${versioned_dir_target}"
896 update_versioned_dir="${versioned_dir_target}"
897 note "update_versioned_dir = ${update_versioned_dir}"
898 fi
899
900 note "dirpatching versioned directory"
901 if ! "${dirpatcher}" "${old_versioned_dir}" \
902 "${patch_versioned_dir}" \
903 "${versioned_dir_target}"; then
904 err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
905 exit 12
906 fi
907 fi
908
909 # Copy the versioned directory. The new versioned directory should have a
910 # different name than any existing one, so this won't harm anything already
911 # present in ${installed_versions_dir}, including the versioned directory
912 # being used by any running processes. If this step is interrupted, there
913 # will be an incomplete versioned directory left behind, but it won't
914 # won't interfere with anything, and it will be replaced or removed during a
915 # future update attempt.
916 #
917 # In certain cases, same-version updates are distributed to move users
918 # between channels; when this happens, the contents of the versioned
919 # directories are identical and rsync will not render the versioned
920 # directory unusable even for an instant.
921 #
922 # ${update_versioned_dir} may be empty during a patch update (${is_patch})
923 # if the dirpatcher above was able to write it into place directly. In
924 # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
925 # place.
926 if [[ -n "${update_versioned_dir}" ]]; then
927 note "rsyncing versioned directory"
928 if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
929 "${new_versioned_dir}"; then
930 err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
931 exit 7
932 fi
933 fi
934
935 if [[ -n "${is_patch}" ]]; then
936 # If the versioned directory was prepared in a temporary directory and
937 # then rsynced into place, remove the temporary copy now that it's no
938 # longer needed.
939 if [[ -n "${update_versioned_dir}" ]]; then
940 rm -rf "${update_versioned_dir}" 2> /dev/null || true
941 update_versioned_dir=
942 note "update_versioned_dir = ${update_versioned_dir}"
943 fi
944
945 # Prepare ${update_app}. This always needs to be done in a temporary
946 # location because dirpatcher won't write to a directory that already
947 # exists, and ${installed_app} needs to be used as input to dirpatcher
948 # in any event. The new application will be rsynced into place once
949 # dirpatcher creates it.
950 ensure_temp_dir
951 update_app="${g_temp_dir}/${APP_DIR}"
952 note "update_app = ${update_app}"
953
954 note "dirpatching app directory"
955 if ! "${dirpatcher}" "${installed_app}" \
956 "${patch_app_dir}" \
957 "${update_app}"; then
958 err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
959 exit 13
960 fi
961 fi
962
963 # See if the timestamp of what's currently on disk is newer than the
964 # update's outer .app's timestamp. rsync will copy the update's timestamp
965 # over, but if that timestamp isn't as recent as what's already on disk, the
966 # .app will need to be touched.
967 local needs_touch=
968 if [[ "${installed_app}" -nt "${update_app}" ]]; then
969 needs_touch="y"
970 fi
971 note "needs_touch = ${needs_touch}"
972
973 # Copy the unversioned files into place, leaving everything in
974 # ${installed_versions_dir} alone. If this step is interrupted, the
975 # application will at least remain in a usable state, although it may not
976 # pass signature validation. Depending on when this step is interrupted,
977 # the application will either launch the old or the new version. The
978 # critical point is when the main executable is replaced. There isn't very
979 # much to copy in this step, because most of the application is in the
980 # versioned directory. This step only accounts for around 50 files, most of
981 # which are small localized InfoPlist.strings files. Note that
982 # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
983 # contents are excluded, having already been installed above.
984 note "rsyncing app directory"
985 if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
986 "${update_app}/" "${installed_app}"; then
987 err "rsync of app directory failed, status ${PIPESTATUS[0]}"
988 exit 8
989 fi
990
991 note "rsyncs complete"
992
993 if [[ -n "${is_patch}" ]]; then
994 # update_app has been rsynced into place and is no longer needed.
995 rm -rf "${update_app}" 2> /dev/null || true
996 update_app=
997 note "update_app = ${update_app}"
998 fi
999
1000 if [[ -n "${g_temp_dir}" ]]; then
1001 # The temporary directory, if any, is no longer needed.
1002 rm -rf "${g_temp_dir}" 2> /dev/null || true
1003 g_temp_dir=
1004 note "g_temp_dir = ${g_temp_dir}"
1005 fi
1006
1007 # If necessary, touch the outermost .app so that it appears to the outside
1008 # world that something was done to the bundle. This will cause
1009 # LaunchServices to invalidate the information it has cached about the
1010 # bundle even if lsregister does not run. This is not done if rsync already
1011 # updated the timestamp to something newer than what had been on disk. This
1012 # is not considered a critical step, and if it fails, this script will not
1013 # exit.
1014 if [[ -n "${needs_touch}" ]]; then
1015 touch -cf "${installed_app}" || true
1016 fi
1017
1018 # Read the new values, such as the version.
1019 note "reading new values"
1020
1021 local new_version_app
1022 if ! new_version_app="$(infoplist_read "${installed_app_plist}" \
1023 "${APP_VERSION_KEY}")" ||
1024 [[ -z "${new_version_app}" ]]; then
1025 err "couldn't determine new_version_app"
1026 exit 9
1027 fi
1028 note "new_version_app = ${new_version_app}"
1029
1030 local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
1031 note "new_versioned_dir = ${new_versioned_dir}"
1032
1033 local new_ks_plist="${installed_app_plist}"
1034 note "new_ks_plist = ${new_ks_plist}"
1035
1036 local new_version_ks
1037 if ! new_version_ks="$(infoplist_read "${new_ks_plist}" \
1038 "${KS_VERSION_KEY}")" ||
1039 [[ -z "${new_version_ks}" ]]; then
1040 err "couldn't determine new_version_ks"
1041 exit 9
1042 fi
1043 note "new_version_ks = ${new_version_ks}"
1044
1045 local update_url
1046 if ! update_url="$(infoplist_read "${new_ks_plist}" "${KS_URL_KEY}")" ||
1047 [[ -z "${update_url}" ]]; then
1048 err "couldn't determine update_url"
1049 exit 9
1050 fi
1051 note "update_url = ${update_url}"
1052
1053 # The channel ID is optional. Suppress stderr to prevent Keystone from
1054 # seeing possible error output.
1055 local channel
1056 channel="$(infoplist_read "${new_ks_plist}" \
1057 "${KS_CHANNEL_KEY}" 2> /dev/null || true)"
1058 note "channel = ${channel}"
1059
1060 # Make sure that the update was successful by comparing the version found in
1061 # the update with the version now on disk.
1062 if [[ "${new_version_ks}" != "${update_version_ks}" ]]; then
1063 err "new_version_ks and update_version_ks do not match"
1064 exit 10
1065 fi
1066
1067 # Notify LaunchServices. This is not considered a critical step, and
1068 # lsregister's exit codes shouldn't be confused with this script's own.
1069 # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
1070 # throttling disk i/o" messages that lsregister might print.
1071 note "notifying LaunchServices"
1072 local coreservices="/System/Library/Frameworks/CoreServices.framework"
1073 local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
1074 local lsregister="${launchservices}/Support/lsregister"
1075 note "coreservices = ${coreservices}"
1076 note "launchservices = ${launchservices}"
1077 note "lsregister = ${lsregister}"
1078 "${lsregister}" -f "${installed_app}" > /dev/null || true
1079
1080 # The brand information is stored differently depending on whether this is
1081 # running for a system or user ticket.
1082 note "handling brand code"
1083
1084 local set_brand_file_access=
1085 local brand_plist
1086 if [[ -n "${system_ticket}" ]]; then
1087 # System ticket.
1088 set_brand_file_access="y"
1089 brand_plist="/${UNROOTED_BRAND_PLIST}"
1090 else
1091 # User ticket.
1092 brand_plist=~/"${UNROOTED_BRAND_PLIST}"
1093 fi
1094 local brand_plist_path="${brand_plist}.plist"
1095 note "set_brand_file_access = ${set_brand_file_access}"
1096 note "brand_plist = ${brand_plist}"
1097 note "brand_plist_path = ${brand_plist_path}"
1098
1099 local ksadmin_brand_plist_path
1100 local ksadmin_brand_key
1101
1102 # Only the stable channel, identified by an empty channel string, has a
1103 # brand code. On the beta and dev channels, remove the brand plist if
1104 # present. Its presence means that the ticket used to manage a
1105 # stable-channel Chrome but the user has since replaced it with a beta or
1106 # dev channel version. Since the canary channel can run side-by-side with
1107 # another Chrome installation, don't remove the brand plist on that channel,
1108 # but skip the rest of the brand logic.
1109 if [[ "${channel}" = "beta" ]] || [[ "${channel}" = "dev" ]]; then
1110 note "defeating brand code on channel ${channel}"
1111 rm -f "${brand_plist_path}" 2>/dev/null || true
1112 elif [[ -n "${channel}" ]]; then
1113 # Canary channel.
1114 note "skipping brand code on channel ${channel}"
1115 else
1116 # Stable channel.
1117 # If the user manually updated their copy of Chrome, there might be new
1118 # brand information in the app bundle, and that needs to be copied out
1119 # into the file Keystone looks at.
1120 if [[ -n "${old_brand}" ]]; then
1121 local brand_dir
1122 brand_dir="$(dirname "${brand_plist_path}")"
1123 note "brand_dir = ${brand_dir}"
1124 if ! mkdir -p "${brand_dir}"; then
1125 err "couldn't mkdir brand_dir, continuing"
1126 else
1127 if ! defaults write "${brand_plist}" "${KS_BRAND_KEY}" \
1128 -string "${old_brand}"; then
1129 err "couldn't write brand_plist, continuing"
1130 elif [[ -n "${set_brand_file_access}" ]]; then
1131 if ! chown "root:wheel" "${brand_plist_path}"; then
1132 err "couldn't chown brand_plist_path, continuing"
1133 else
1134 if ! chmod 644 "${brand_plist_path}"; then
1135 err "couldn't chmod brand_plist_path, continuing"
1136 fi
1137 fi
1138 fi
1139 fi
1140 fi
1141
1142 # Confirm that the brand file exists. It's optional.
1143 ksadmin_brand_plist_path="${brand_plist_path}"
1144 ksadmin_brand_key="${KS_BRAND_KEY}"
1145
1146 if [[ ! -f "${ksadmin_brand_plist_path}" ]]; then
1147 # Clear any branding information.
1148 ksadmin_brand_plist_path=
1149 ksadmin_brand_key=
1150 fi
1151 fi
1152
1153 note "ksadmin_brand_plist_path = ${ksadmin_brand_plist_path}"
1154 note "ksadmin_brand_key = ${ksadmin_brand_key}"
1155
1156 note "notifying Keystone"
1157
1158 local ksadmin_args=(
1159 --register
1160 --productid "${product_id}"
1161 --version "${new_version_ks}"
1162 --xcpath "${installed_app}"
1163 --url "${update_url}"
1164 )
1165
1166 if ksadmin_supports_tag; then
1167 ksadmin_args+=(
1168 --tag "${channel}"
1169 )
1170 fi
1171
1172 if ksadmin_supports_tagpath_tagkey; then
1173 ksadmin_args+=(
1174 --tag-path "${installed_app_plist_path}"
1175 --tag-key "${KS_CHANNEL_KEY}"
1176 )
1177 fi
1178
1179 if ksadmin_supports_brandpath_brandkey; then
1180 ksadmin_args+=(
1181 --brand-path "${ksadmin_brand_plist_path}"
1182 --brand-key "${ksadmin_brand_key}"
1183 )
1184 fi
1185
1186 if ksadmin_supports_versionpath_versionkey; then
1187 ksadmin_args+=(
1188 --version-path "${installed_app_plist_path}"
1189 --version-key "${KS_VERSION_KEY}"
1190 )
1191 fi
1192
1193 note "ksadmin_args = ${ksadmin_args[*]}"
1194
1195 if ! ksadmin "${ksadmin_args[@]}"; then
1196 err "ksadmin failed"
1197 exit 11
1198 fi
1199
1200 # The remaining steps are not considered critical.
1201 set +e
1202
1203 # Try to clean up old versions that are not in use. The strategy is to keep
1204 # the versioned directory corresponding to the update just applied
1205 # (obviously) and the version that was just replaced, and to use ps and lsof
1206 # to see if it looks like any processes are currently using any other old
1207 # directories. Directories not in use are removed. Old versioned
1208 # directories that are in use are left alone so as to not interfere with
1209 # running processes. These directories can be cleaned up by this script on
1210 # future updates.
1211 #
1212 # To determine which directories are in use, both ps and lsof are used.
1213 # Each approach has limitations.
1214 #
1215 # The ps check looks for processes within the versioned directory. Only
1216 # helper processes, such as renderers, are within the versioned directory.
1217 # Browser processes are not, so the ps check will not find them, and will
1218 # assume that a versioned directory is not in use if a browser is open
1219 # without any windows. The ps mechanism can also only detect processes
1220 # running on the system that is performing the update. If network shares
1221 # are involved, all bets are off.
1222 #
1223 # The lsof check looks to see what processes have the framework dylib open.
1224 # Browser processes will have their versioned framework dylib open, so this
1225 # check is able to catch browsers even if there are no associated helper
1226 # processes. Like the ps check, the lsof check is limited to processes on
1227 # the system that is performing the update. Finally, unless running as
1228 # root, the lsof check can only find processes running as the effective user
1229 # performing the update.
1230 #
1231 # These limitations are motivations to additionally preserve the versioned
1232 # directory corresponding to the version that was just replaced.
1233 note "cleaning up old versioned directories"
1234
1235 local versioned_dir
1236 for versioned_dir in "${installed_versions_dir}/"*; do
1237 note "versioned_dir = ${versioned_dir}"
1238 if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] || \
1239 [[ "${versioned_dir}" = "${old_versioned_dir}" ]]; then
1240 # This is the versioned directory corresponding to the update that was
1241 # just applied or the version that was previously in use. Leave it
1242 # alone.
1243 note "versioned_dir is new_versioned_dir or old_versioned_dir, skipping"
1244 continue
1245 fi
1246
1247 # Look for any processes whose executables are within this versioned
1248 # directory. They'll be helper processes, such as renderers. Their
1249 # existence indicates that this versioned directory is currently in use.
1250 local ps_string="${versioned_dir}/"
1251 note "ps_string = ${ps_string}"
1252
1253 # Look for any processes using the framework dylib. This will catch
1254 # browser processes where the ps check will not, but it is limited to
1255 # processes running as the effective user.
1256 local lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
1257 note "lsof_file = ${lsof_file}"
1258
1259 # ps -e displays all users' processes, -ww causes ps to not truncate
1260 # lines, -o comm instructs it to only print the command name, and the =
1261 # tells it to not print a header line.
1262 # The cut invocation filters the ps output to only have at most the number
1263 # of characters in ${ps_string}. This is done so that grep can look for
1264 # an exact match.
1265 # grep -F tells grep to look for lines that are exact matches (not regular
1266 # expressions), -q tells it to not print any output and just indicate
1267 # matches by exit status, and -x tells it that the entire line must match
1268 # ${ps_string} exactly, as opposed to matching a substring. A match
1269 # causes grep to exit zero (true).
1270 #
1271 # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
1272 # process. If the file exists and is open, it will exit zero (true).
1273 if (! ps -ewwo comm= | \
1274 cut -c "1-${#ps_string}" | \
1275 grep -Fqx "${ps_string}") &&
1276 (! lsof "${lsof_file}" >& /dev/null); then
1277 # It doesn't look like anything is using this versioned directory. Get
1278 # rid of it.
1279 note "versioned_dir doesn't appear to be in use, removing"
1280 rm -rf "${versioned_dir}"
1281 else
1282 note "versioned_dir is in use, skipping"
1283 fi
1284 done
1285
1286 # If this script is being driven by a user Keystone ticket, it is not
1287 # running as root. If the application is installed somewhere under
1288 # /Applications, try to make it writable by all admin users. This will
1289 # allow other admin users to update the application from their own user
1290 # Keystone instances.
1291 #
1292 # If the script is being driven by a user Keystone ticket (not running as
1293 # root) and the application is not installed under /Applications, it might
1294 # not be in a system-wide location, and it probably won't be something that
1295 # other users on the system are running, so err on the side of safety and
1296 # don't make it group-writable.
1297 #
1298 # If this script is being driven by a system ticket (running as root), it's
1299 # future updates can be expected to be applied the same way, so admin-
1300 # writability is not a concern. Set the entire thing to be owned by root
1301 # in that case, regardless of where it's installed, and drop any group and
1302 # other write permission.
1303 #
1304 # If this script is running as a user that is not a member of the admin
1305 # group, the chgrp operation will not succeed. Tolerate that case, because
1306 # it's better than the alternative, which is to make the application
1307 # world-writable.
1308 note "setting permissions"
1309
1310 local chmod_mode="a+rX,u+w,go-w"
1311 if [[ -z "${system_ticket}" ]]; then
1312 if [[ "${installed_app:0:14}" = "/Applications/" ]] &&
1313 chgrp -Rh admin "${installed_app}" 2> /dev/null; then
1314 chmod_mode="a+rX,ug+w,o-w"
1315 fi
1316 else
1317 chown -Rh root:wheel "${installed_app}" 2> /dev/null
1318 fi
1319
1320 note "chmod_mode = ${chmod_mode}"
1321 chmod -R "${chmod_mode}" "${installed_app}" 2> /dev/null
1322
1323 # On the Mac, or at least on HFS+, symbolic link permissions are significant,
1324 # but chmod -R and -h can't be used together. Do another pass to fix the
1325 # permissions on any symbolic links.
1326 find "${installed_app}" -type l -exec chmod -h "${chmod_mode}" {} + \
1327 2> /dev/null
1328
1329 # If an update is triggered from within the application itself, the update
1330 # process inherits the quarantine bit (LSFileQuarantineEnabled). Any files
1331 # or directories created during the update will be quarantined in that case,
1332 # which may cause Launch Services to display quarantine UI. That's bad,
1333 # especially if it happens when the outer .app launches a quarantined inner
1334 # helper. If the application is already on the system and is being updated,
1335 # then it can be assumed that it should not be quarantined. Use xattr to
1336 # drop the quarantine attribute.
1337 #
1338 # TODO(mark): Instead of letting the quarantine attribute be set and then
1339 # dropping it here, figure out a way to get the update process to run
1340 # without LSFileQuarantineEnabled even when triggering an update from within
1341 # the application.
1342 note "lifting quarantine"
1343
1344 if os_xattr_supports_r; then
1345 # On 10.6, xattr supports -r for recursive operation.
1346 xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
1347 else
1348 # On earlier systems, xattr doesn't support -r, so run xattr via find.
1349 find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \
1350 2> /dev/null
1351 fi
1352
1353 # Do Keychain reauthorization. This involves running a stub executable on
1354 # the dmg that loads the newly-updated framework and jumps to it to perform
1355 # the reauthorization. The stub executable can be signed by the old
1356 # certificate even after the rest of Chrome switches to the new certificate,
1357 # so it still has access to the old Keychain items. The stub executable is
1358 # an unbundled flat file executable whose name matches the real
1359 # application's bundle identifier, so it's permitted access to the Keychain
1360 # items. Doing a reauthorization step at update time reauthorizes Keychain
1361 # items for users who never bother restarting Chrome, and provides a
1362 # mechanism to continue doing reauthorizations even after the certificate
1363 # changes. However, it only works for non-system ticket installations of
1364 # Chrome, because the updater runs as root when on a system ticket, and root
1365 # can't access individual user Keychains.
1366 #
1367 # Even if the reauthorization tool is launched, it doesn't necessarily try
1368 # to do anything. It will only attempt to perform a reauthorization if one
1369 # hasn't yet been done at update time.
1370 note "maybe reauthorizing Keychain"
1371
1372 if [[ -z "${system_ticket}" ]]; then
1373 local new_bundleid_app
1374 new_bundleid_app="$(infoplist_read "${installed_app_plist}" \
1375 "${APP_BUNDLEID_KEY}" || true)"
1376 note "new_bundleid_app = ${new_bundleid_app}"
1377
1378 local keychain_reauthorize_dir="\
1379 ${update_dmg_mount_point}/${KEYCHAIN_REAUTHORIZE_DIR}"
1380 local keychain_reauthorize_path="\
1381 ${keychain_reauthorize_dir}/${new_bundleid_app}"
1382 note "keychain_reauthorize_path = ${keychain_reauthorize_path}"
1383
1384 if [[ -x "${keychain_reauthorize_path}" ]]; then
1385 local framework_dir="${new_versioned_dir}/${FRAMEWORK_DIR}"
1386 local framework_code_path="${framework_dir}/${FRAMEWORK_NAME}"
1387 note "framework_code_path = ${framework_code_path}"
1388
1389 if [[ -f "${framework_code_path}" ]]; then
1390 note "reauthorizing Keychain"
1391 "${keychain_reauthorize_path}" "${framework_code_path}"
1392 fi
1393 fi
1394 else
1395 note "system ticket, not reauthorizing Keychain"
1396 fi
1397
1398 # Great success!
1399 note "done!"
1400
1401 trap - EXIT
1402
1403 return 0
1404 }
1405
1406 # Check "less than" instead of "not equal to" in case Keystone ever changes to
1407 # pass more arguments.
1408 if [[ ${#} -lt 1 ]]; then
1409 usage
1410 exit 2
1411 fi
1412
1413 main "${@}"
1414 exit ${?}
OLDNEW
« no previous file with comments | « chrome_mac/Google Chrome Packaging/goobspatch ('k') | chrome_mac/Google Chrome Packaging/liblzma_decompress.dylib » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698