Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / installer / mac / keystone_install.sh
blob9eea45914985f10785192f1c89b79181e47e6bb0
1 #!/bin/bash -p
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.
7 # usage: keystone_install.sh update_dmg_mount_point
9 # Called by the Keystone system to update the installed application with a new
10 # version from a disk image.
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.
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 # 14 The update is incompatible with the system
36 # The following exit codes can be used to convey special meaning to Keystone.
37 # KeystoneRegistration will present these codes to Chrome as "success."
38 # 66 (unused) success, request reboot
39 # 77 (unused) try installation again later
41 set -eu
43 # http://b/2290916: Keystone runs the installation with a restrictive PATH
44 # that only includes the directory containing ksadmin, /bin, and /usr/bin. It
45 # does not include /sbin or /usr/sbin. This script uses lsof, which is in
46 # /usr/sbin, and it's conceivable that it might want to use other tools in an
47 # sbin directory. Adjust the path accordingly.
48 export PATH="${PATH}:/sbin:/usr/sbin"
50 # Environment sanitization. Clear environment variables that might impact the
51 # interpreter's operation. The |bash -p| invocation on the #! line takes the
52 # bite out of BASH_ENV, ENV, and SHELLOPTS (among other features), but
53 # clearing them here ensures that they won't impact any shell scripts used as
54 # utility programs. SHELLOPTS is read-only and can't be unset, only
55 # unexported.
56 unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
57 export -n SHELLOPTS
59 set -o pipefail
60 shopt -s nullglob
62 ME="$(basename "${0}")"
63 readonly ME
65 readonly KS_CHANNEL_KEY="KSChannelID"
67 # Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3
68 # In bash 4.0, "declare VAR" no longer initializes VAR if not already set.
69 : ${GOOGLE_CHROME_UPDATER_DEBUG:=}
70 err() {
71 local error="${1}"
73 local id=
74 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
75 id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
78 echo "${ME}${id}: ${error}" >& 2
81 note() {
82 local message="${1}"
84 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
85 err "${message}"
89 g_temp_dir=
90 cleanup() {
91 local status=${?}
93 trap - EXIT
94 trap '' HUP INT QUIT TERM
96 if [[ ${status} -ge 128 ]]; then
97 err "Caught signal $((${status} - 128))"
100 if [[ -n "${g_temp_dir}" ]]; then
101 rm -rf "${g_temp_dir}"
104 exit ${status}
107 ensure_temp_dir() {
108 if [[ -z "${g_temp_dir}" ]]; then
109 # Choose a template that won't be a dot directory. Make it safe by
110 # removing leading hyphens, too.
111 local template="${ME}"
112 if [[ "${template}" =~ ^[-.]+(.*)$ ]]; then
113 template="${BASH_REMATCH[1]}"
115 if [[ -z "${template}" ]]; then
116 template="keystone_install"
119 g_temp_dir="$(mktemp -d -t "${template}")"
120 note "g_temp_dir = ${g_temp_dir}"
124 # Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
125 # writable on the basis of its POSIX permissions. This is used to determine
126 # writability like test's -w primary, but -w resolves symbolic links and this
127 # function does not.
128 is_writable_symlink() {
129 local symlink="${1}"
131 local link_mode
132 link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
133 if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
134 return 1
137 local link_user link_group
138 link_user="$(stat -f %u "${symlink}" 2> /dev/null || true)"
139 link_group="$(stat -f %g "${symlink}" 2> /dev/null || true)"
140 if [[ -z "${link_user}" ]] || [[ -z "${link_group}" ]]; then
141 return 1
144 # If the users match, check the owner-write bit.
145 if [[ ${EUID} -eq "${link_user}" ]]; then
146 if [[ "${link_mode:2:1}" = "w" ]]; then
147 return 0
149 return 1
152 # If the file's group matches any of the groups that this process is a
153 # member of, check the group-write bit.
154 local group_match=
155 local group
156 for group in "${GROUPS[@]}"; do
157 if [[ "${group}" -eq "${link_group}" ]]; then
158 group_match="y"
159 break
161 done
162 if [[ -n "${group_match}" ]]; then
163 if [[ "${link_mode:5:1}" = "w" ]]; then
164 return 0
166 return 1
169 # Check the other-write bit.
170 if [[ "${link_mode:8:1}" = "w" ]]; then
171 return 0
174 return 1
177 # If |symlink| exists and is a symbolic link, but is not writable according to
178 # is_writable_symlink, this function attempts to replace it with a new
179 # writable symbolic link. If |symlink| does not exist, is not a symbolic
180 # link, or is already writable, this function does nothing. This function
181 # always returns 0 (true).
182 ensure_writable_symlink() {
183 local symlink="${1}"
185 if [[ -L "${symlink}" ]] && ! is_writable_symlink "${symlink}"; then
186 # If ${symlink} refers to a directory, doing this naively might result in
187 # the new link being placed in that directory, instead of replacing the
188 # existing link. ln -fhs is supposed to handle this case, but it does so
189 # by unlinking (removing) the existing symbolic link before creating a new
190 # one. That leaves a small window during which the symbolic link is not
191 # present on disk at all.
193 # To avoid that possibility, a new symbolic link is created in a temporary
194 # location and then swapped into place with mv. An extra temporary
195 # directory is used to convince mv to replace the symbolic link: again, if
196 # the existing link refers to a directory, "mv newlink oldlink" will
197 # actually leave oldlink alone and place newlink into the directory.
198 # "mv newlink dirname(oldlink)" works as expected, but in order to replace
199 # oldlink, newlink must have the same basename, hence the temporary
200 # directory.
202 local target
203 target="$(readlink "${symlink}" 2> /dev/null || true)"
204 if [[ -z "${target}" ]]; then
205 return 0
208 # Error handling strategy: if anything fails, such as the mktemp, ln,
209 # chmod, or mv, ignore the failure and return 0 (success), leaving the
210 # existing state with the non-writable symbolic link intact. Failures
211 # in this function will be difficult to understand and diagnose, and a
212 # non-writable symbolic link is not necessarily fatal. If something else
213 # requires a writable symbolic link, allowing it to fail when a symbolic
214 # link is not writable is easier to understand than bailing out of the
215 # script on failure here.
217 local symlink_dir temp_link_dir temp_link
218 symlink_dir="$(dirname "${symlink}")"
219 temp_link_dir="$(mktemp -d "${symlink_dir}/.symlink_temp.XXXXXX" || true)"
220 if [[ -z "${temp_link_dir}" ]]; then
221 return 0
223 temp_link="${temp_link_dir}/$(basename "${symlink}")"
225 (ln -fhs "${target}" "${temp_link}" && \
226 chmod -h 755 "${temp_link}" && \
227 mv -f "${temp_link}" "${symlink_dir}/") || true
228 rm -rf "${temp_link_dir}"
231 return 0
234 # ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
235 # symbolic link in |directory|, recursively.
237 # In some very weird and rare cases, it is possible to wind up with a user
238 # installation that contains symbolic links that the user does not have write
239 # permission over. More on how that might happen later.
241 # If a weird and rare case like this is observed, rsync will exit with an
242 # error when attempting to update the times on these symbolic links. rsync
243 # may not be intelligent enough to try creating a new symbolic link in these
244 # cases, but this script can be.
246 # The problem occurs when an administrative user first drag-installs the
247 # application to /Applications, resulting in the program's user being set to
248 # the user's own ID. If, subsequently, a .pkg package is installed over that,
249 # the existing directory ownership will be preserved, but file ownership will
250 # be changed to whatever is specified by the package, typically root. This
251 # applies to symbolic links as well. On a subsequent update, rsync will be
252 # able to copy the new files into place, because the user still has permission
253 # to write to the directories. If the symbolic link targets are not changing,
254 # though, rsync will not replace them, and they will remain owned by root.
255 # The user will not have permission to update the time on the symbolic links,
256 # resulting in an rsync error.
257 ensure_writable_symlinks_recursive() {
258 local directory="${1}"
260 # This fix-up is not necessary when running as root, because root will
261 # always be able to write everything needed.
262 if [[ ${EUID} -eq 0 ]]; then
263 return 0
266 # This step isn't critical.
267 local set_e=
268 if [[ "${-}" =~ e ]]; then
269 set_e="y"
270 set +e
273 # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
274 local symlink
275 while IFS= read -r -d $'\0' symlink; do
276 ensure_writable_symlink "${symlink}"
277 done < <(find "${directory}" -type l -print0)
279 # Go back to how things were.
280 if [[ -n "${set_e}" ]]; then
281 set -e
285 # is_version_ge accepts two version numbers, left and right, and performs a
286 # piecewise comparison determining the result of left >= right, returning true
287 # (0) if left >= right, and false (1) if left < right. If left or right are
288 # missing components relative to the other, the missing components are assumed
289 # to be 0, such that 10.6 == 10.6.0.
290 is_version_ge() {
291 local left="${1}"
292 local right="${2}"
294 local -a left_array right_array
295 IFS=. left_array=(${left})
296 IFS=. right_array=(${right})
298 local left_count=${#left_array[@]}
299 local right_count=${#right_array[@]}
300 local count=${left_count}
301 if [[ ${right_count} -lt ${count} ]]; then
302 count=${right_count}
305 # Compare the components piecewise, as long as there are corresponding
306 # components on each side. If left_element and right_element are unequal,
307 # a comparison can be made.
308 local index=0
309 while [[ ${index} -lt ${count} ]]; do
310 local left_element="${left_array[${index}]}"
311 local right_element="${right_array[${index}]}"
312 if [[ ${left_element} -gt ${right_element} ]]; then
313 return 0
314 elif [[ ${left_element} -lt ${right_element} ]]; then
315 return 1
317 ((++index))
318 done
320 # If there are more components on the left than on the right, continue
321 # comparing, assuming 0 for each of the missing components on the right.
322 while [[ ${index} -lt ${left_count} ]]; do
323 local left_element="${left_array[${index}]}"
324 if [[ ${left_element} -gt 0 ]]; then
325 return 0
327 ((++index))
328 done
330 # If there are more components on the right than on the left, continue
331 # comparing, assuming 0 for each of the missing components on the left.
332 while [[ ${index} -lt ${right_count} ]]; do
333 local right_element="${right_array[${index}]}"
334 if [[ ${right_element} -gt 0 ]]; then
335 return 1
337 ((++index))
338 done
340 # Upon reaching this point, the two version numbers are semantically equal.
341 return 0
344 # Prints the OS version, as reported by sw_vers -productVersion, to stdout.
345 # This function operates with "static" variables: it will only check the OS
346 # version once per script run.
347 g_checked_os_version=
348 g_os_version=
349 os_version() {
350 if [[ -z "${g_checked_os_version}" ]]; then
351 g_checked_os_version="y"
352 g_os_version="$(sw_vers -productVersion)"
353 note "g_os_version = ${g_os_version}"
355 echo "${g_os_version}"
356 return 0
359 # Compares the running OS version against a supplied version number,
360 # |check_version|, and returns 0 (true) if the running OS version is greater
361 # than or equal to |check_version| according to a piece-wise comparison.
362 # Returns 1 (false) if the running OS version number cannot be determined or
363 # if |check_version| is greater than the running OS version. |check_version|
364 # should be a string of the form "major.minor" or "major.minor.micro".
365 is_os_version_ge() {
366 local check_version="${1}"
368 local os_version="$(os_version)"
369 is_version_ge "${os_version}" "${check_version}"
371 # The return value of is_version_ge is used as this function's return value.
374 # Returns 0 (true) if xattr supports -r for recursive operation.
375 os_xattr_supports_r() {
376 # xattr -r is supported in Mac OS X 10.6.
377 is_os_version_ge 10.6
379 # The return value of is_os_version_ge is used as this function's return
380 # value.
383 # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
384 # stdout. This function operates with "static" variables: it will only check
385 # the ksadmin version once per script run. If ksadmin is old enough to not
386 # support --ksadmin-version, or another error occurs, this function prints an
387 # empty string.
388 g_checked_ksadmin_version=
389 g_ksadmin_version=
390 ksadmin_version() {
391 if [[ -z "${g_checked_ksadmin_version}" ]]; then
392 g_checked_ksadmin_version="y"
393 g_ksadmin_version="$(ksadmin --ksadmin-version || true)"
394 note "g_ksadmin_version = ${g_ksadmin_version}"
396 echo "${g_ksadmin_version}"
397 return 0
400 # Compares the installed ksadmin version against a supplied version number,
401 # |check_version|, and returns 0 (true) if the installed Keystone version is
402 # greater than or equal to |check_version| according to a piece-wise
403 # comparison. Returns 1 (false) if the installed Keystone version number
404 # cannot be determined or if |check_version| is greater than the installed
405 # Keystone version. |check_version| should be a string of the form
406 # "major.minor.micro.build".
407 is_ksadmin_version_ge() {
408 local check_version="${1}"
410 local ksadmin_version="$(ksadmin_version)"
411 is_version_ge "${ksadmin_version}" "${check_version}"
413 # The return value of is_version_ge is used as this function's return value.
416 # Returns 0 (true) if ksadmin supports --tag.
417 ksadmin_supports_tag() {
418 local ksadmin_version
420 ksadmin_version="$(ksadmin_version)"
421 if [[ -n "${ksadmin_version}" ]]; then
422 # A ksadmin that recognizes --ksadmin-version and provides a version
423 # number is new enough to recognize --tag.
424 return 0
427 return 1
430 # Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
431 ksadmin_supports_tagpath_tagkey() {
432 # --tag-path and --tag-key were introduced in Keystone 1.0.7.1306.
433 is_ksadmin_version_ge 1.0.7.1306
435 # The return value of is_ksadmin_version_ge is used as this function's
436 # return value.
439 # Returns 0 (true) if ksadmin supports --brand-path and --brand-key.
440 ksadmin_supports_brandpath_brandkey() {
441 # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620.
442 is_ksadmin_version_ge 1.0.8.1620
444 # The return value of is_ksadmin_version_ge is used as this function's
445 # return value.
448 # Returns 0 (true) if ksadmin supports --version-path and --version-key.
449 ksadmin_supports_versionpath_versionkey() {
450 # --version-path and --version-key were introduced in Keystone 1.0.9.2318.
451 is_ksadmin_version_ge 1.0.9.2318
453 # The return value of is_ksadmin_version_ge is used as this function's
454 # return value.
457 has_32_bit_only_cpu() {
458 local cpu_64_bit_capable="$(sysctl -n hw.cpu64bit_capable 2>/dev/null)"
459 [[ -z "${cpu_64_bit_capable}" || "${cpu_64_bit_capable}" -eq 0 ]]
461 # The return value of the comparison is used as this function's return
462 # value.
465 # Runs "defaults read" to obtain the value of a key in a property list. As
466 # with "defaults read", an absolute path to a plist is supplied, without the
467 # ".plist" extension.
469 # As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences)
470 # normally communicates with cfprefsd to read and write plists. Changes to a
471 # plist file aren't necessarily reflected immediately via this API family when
472 # not made through this API family, because cfprefsd may return cached data
473 # from a former on-disk version of a plist file instead of reading the current
474 # version from disk. The old behavior can be restored by setting the
475 # __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care
476 # should be used because portions of the system that use this API family
477 # normally and thus use cfprefsd and its cache will become unsynchronized with
478 # the on-disk state.
480 # This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling
481 # "defaults read" and thus avoid cfprefsd and its on-disk cache, and is
482 # intended only to be used to read values from Info.plist files, which are not
483 # preferences. The use of "defaults" for this purpose has always been
484 # questionable, but there's no better option to interact with plists from
485 # shell scripts. Definitely don't use infoplist_read to read preference
486 # plists.
488 # This function exists because the update process delivers new copies of
489 # Info.plist files to the disk behind cfprefsd's back, and if cfprefsd becomes
490 # aware of the original version of the file for any reason (such as this
491 # script reading values from it via "defaults read"), the new version of the
492 # file will not be immediately effective or visible via cfprefsd after the
493 # update is applied.
494 infoplist_read() {
495 __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
498 # Adjust the tag to contain the -32bit tag suffix. This is intended to be used
499 # as a last resort, if sanity checks show that a non-32-bit update is about to
500 # be applied to a 32-bit-only system. If this happens, it means that the
501 # server delivered a non-32-bit update to a 32-bit-only system, most likely
502 # because the tag was never updated to include the -32bit tag suffix.
504 # This mechanism takes a heavy-handed approach, clearing --tag-path and
505 # --tag-key so that the channel identity will no longer follow the installed
506 # application. However, it's expected that once -32bit is added to the tag,
507 # the server will deliver a 32-bit update (possibly the final 32-bit version),
508 # and once installed, that update will restore the --tag-path and --tag-key.
509 # In any event, channel identity in this case may be moot, if 32-bit builds
510 # are no longer being produced.
512 # This provides some resilience in the update system for old 32-bit-only
513 # systems that aren't used during the window between when the -32bit tag
514 # suffix begins being used and 32-bit releases end.
515 mark_32_bit_only_system() {
516 local product_id="${1}"
518 # This step isn't critical.
519 local set_e=
520 if [[ "${-}" =~ e ]]; then
521 set_e="y"
522 set +e
525 note "marking 32-bit-only system"
527 if ! ksadmin_supports_tagpath_tagkey; then
528 note "couldn't mark 32-bit-only system, no ksadmin support"
529 if [[ -n "${set_e}" ]]; then
530 set -e
532 return 0
535 local current_tag="$(ksadmin --productid "${product_id}" --print-tag)"
536 note "current_tag = ${current_tag}"
538 if grep -Eq -- '-32bit(-|$)' <<< "${current_tag}"; then
539 note "current tag already has -32bit"
540 if [[ -n "${set_e}" ]]; then
541 set -e
543 return 0
546 # This clears any other tag suffix, but that shouldn't be a problem. The
547 # only other currently-defined tag suffix component is -full, but -full and
548 # -32bit were introduced at the same time, so if -full appears, whatever set
549 # it would have already had enough knowledge to set -32bit as well, and this
550 # codepath wouldn't be entered.
551 local current_channel="$(sed -e 's/-.*//' <<< "${current_tag}")"
552 local new_tag="${current_channel}-32bit"
553 note "new_tag = ${new_tag}"
555 # Using ksadmin without --register only updates specified values in the
556 # ticket, without changing other existing values. Giving empty values for
557 # --tag-path and --tag-key clears those fields.
558 if ! ksadmin --productid "${product_id}" \
559 --tag "${new_tag}" --tag-path '' --tag-key ''; then
560 err "ksadmin failed to mark 32-bit-only system"
561 else
562 note "marked 32-bit-only system"
565 # Go back to how things were.
566 if [[ -n "${set_e}" ]]; then
567 set -e
571 # When a patch update fails because the old installed copy doesn't match the
572 # expected state, mark_failed_patch_update updates the Keystone ticket by
573 # adding "-full" to the tag. The server will see this on a subsequent update
574 # attempt and will provide a full update (as opposed to a patch) to the
575 # client.
577 # Even if mark_failed_patch_update fails to modify the tag, the user will
578 # eventually be updated. Patch updates are only provided for successive
579 # releases on a particular channel, to update version o to version o+1. If a
580 # patch update fails in this case, eventually version o+2 will be released,
581 # and no patch update will exist to update o to o+2, so the server will
582 # provide a full update package.
583 mark_failed_patch_update() {
584 local product_id="${1}"
585 local want_full_installer_path="${2}"
586 local old_ks_plist="${3}"
587 local old_version_app="${4}"
588 local system_ticket="${5}"
590 # This step isn't critical.
591 local set_e=
592 if [[ "${-}" =~ e ]]; then
593 set_e="y"
594 set +e
597 note "marking failed patch update"
599 local channel
600 channel="$(infoplist_read "${old_ks_plist}" "${KS_CHANNEL_KEY}" 2> /dev/null)"
602 local tag="${channel}"
603 local tag_key="${KS_CHANNEL_KEY}"
604 if has_32_bit_only_cpu; then
605 tag="${tag}-32bit"
606 tag_key="${tag_key}-32bit"
609 tag="${tag}-full"
610 tag_key="${tag_key}-full"
612 note "tag = ${tag}"
613 note "tag_key = ${tag_key}"
615 # ${old_ks_plist}, used for --tag-path, is the Info.plist for the old
616 # version of Chrome. It may not contain the keys for the "-full" tag suffix.
617 # If it doesn't, just bail out without marking the patch update as failed.
618 local read_tag="$(infoplist_read "${old_ks_plist}" "${tag_key}" 2> /dev/null)"
619 note "read_tag = ${read_tag}"
620 if [[ -z "${read_tag}" ]]; then
621 note "couldn't mark failed patch update"
622 if [[ -n "${set_e}" ]]; then
623 set -e
625 return 0
628 # Chrome can't easily read its Keystone ticket prior to registration, and
629 # when Chrome registers with Keystone, it obliterates old tag values in its
630 # ticket. Therefore, an alternative mechanism is provided to signal to
631 # Chrome that a full installer is desired. If the .want_full_installer file
632 # is present and it contains Chrome's current version number, Chrome will
633 # include "-full" in its tag when it registers with Keystone. This allows
634 # "-full" to persist in the tag even after Chrome is relaunched, which on a
635 # user ticket, triggers a re-registration.
637 # .want_full_installer is placed immediately inside the .app bundle as a
638 # sibling to the Contents directory. In this location, it's outside of the
639 # view of the code signing and code signature verification machinery. This
640 # file can safely be added, modified, and removed without affecting the
641 # signature.
642 rm -f "${want_full_installer_path}" 2> /dev/null
643 echo "${old_version_app}" > "${want_full_installer_path}"
645 # See the comment below in the "setting permissions" section for an
646 # explanation of the groups and modes selected here.
647 local chmod_mode="644"
648 if [[ -z "${system_ticket}" ]] &&
649 [[ "${want_full_installer_path:0:14}" = "/Applications/" ]] &&
650 chgrp admin "${want_full_installer_path}" 2> /dev/null; then
651 chmod_mode="664"
653 note "chmod_mode = ${chmod_mode}"
654 chmod "${chmod_mode}" "${want_full_installer_path}" 2> /dev/null
656 local old_ks_plist_path="${old_ks_plist}.plist"
658 # Using ksadmin without --register only updates specified values in the
659 # ticket, without changing other existing values.
660 local ksadmin_args=(
661 --productid "${product_id}"
664 if ksadmin_supports_tag; then
665 ksadmin_args+=(
666 --tag "${tag}"
670 if ksadmin_supports_tagpath_tagkey; then
671 ksadmin_args+=(
672 --tag-path "${old_ks_plist_path}"
673 --tag-key "${tag_key}"
677 note "ksadmin_args = ${ksadmin_args[*]}"
679 if ! ksadmin "${ksadmin_args[@]}"; then
680 err "ksadmin failed to mark failed patch update"
681 else
682 note "marked failed patch update"
685 # Go back to how things were.
686 if [[ -n "${set_e}" ]]; then
687 set -e
691 usage() {
692 echo "usage: ${ME} update_dmg_mount_point" >& 2
695 main() {
696 local update_dmg_mount_point="${1}"
698 # Early steps are critical. Don't continue past any failure.
699 set -e
701 trap cleanup EXIT HUP INT QUIT TERM
703 readonly PRODUCT_NAME="Google Chrome"
704 readonly APP_DIR="${PRODUCT_NAME}.app"
705 readonly ALTERNATE_APP_DIR="${PRODUCT_NAME} Canary.app"
706 readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
707 readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
708 readonly PATCH_DIR=".patch"
709 readonly CONTENTS_DIR="Contents"
710 readonly APP_PLIST="${CONTENTS_DIR}/Info"
711 readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions"
712 readonly UNROOTED_BRAND_PLIST="Library/Google/Google Chrome Brand"
713 readonly UNROOTED_DEBUG_FILE="Library/Google/Google Chrome Updater Debug"
715 readonly APP_VERSION_KEY="CFBundleShortVersionString"
716 readonly APP_BUNDLEID_KEY="CFBundleIdentifier"
717 readonly KS_VERSION_KEY="KSVersion"
718 readonly KS_PRODUCT_KEY="KSProductID"
719 readonly KS_URL_KEY="KSUpdateURL"
720 readonly KS_BRAND_KEY="KSBrandID"
722 readonly QUARANTINE_ATTR="com.apple.quarantine"
723 readonly KEYCHAIN_REAUTHORIZE_DIR=".keychain_reauthorize"
725 # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners
726 # and groups, respectively, from the source, and that is undesirable in this
727 # case. -D copies devices and special files; copying devices only works
728 # when running as root, so for consistency between privileged and
729 # unprivileged operation, this option is omitted as well.
730 # -I, --ignore-times don't skip files that match in size and mod-time
731 # -l, --links copy symlinks as symlinks
732 # -r, --recursive recurse into directories
733 # -p, --perms preserve permissions
734 # -t, --times preserve times
735 readonly RSYNC_FLAGS="-Ilprt"
737 # It's difficult to get GOOGLE_CHROME_UPDATER_DEBUG set in the environment
738 # when this script is called from Keystone. If a "debug file" exists in
739 # either the root directory or the home directory of the user who owns the
740 # ticket, turn on verbosity. This may aid debugging.
741 if [[ -e "/${UNROOTED_DEBUG_FILE}" ]] ||
742 [[ -e ~/"${UNROOTED_DEBUG_FILE}" ]]; then
743 export GOOGLE_CHROME_UPDATER_DEBUG="y"
746 note "update_dmg_mount_point = ${update_dmg_mount_point}"
748 # The argument should be the disk image path. Make sure it exists and that
749 # it's an absolute path.
750 note "checking update"
752 if [[ -z "${update_dmg_mount_point}" ]] ||
753 [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
754 ! [[ -d "${update_dmg_mount_point}" ]]; then
755 err "update_dmg_mount_point must be an absolute path to a directory"
756 usage
757 exit 2
760 local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
761 if [[ "${patch_dir:0:1}" != "/" ]]; then
762 note "patch_dir = ${patch_dir}"
763 err "patch_dir must be an absolute path"
764 exit 2
767 # Figure out if this is an ordinary installation disk image being used as a
768 # full update, or a patch. A patch will have a .patch directory at the root
769 # of the disk image containing information about the update, tools to apply
770 # it, and the update contents.
771 local is_patch=
772 local dirpatcher=
773 if [[ -d "${patch_dir}" ]]; then
774 # patch_dir exists and is a directory - this is a patch update.
775 is_patch="y"
776 dirpatcher="${patch_dir}/dirpatcher.sh"
777 if ! [[ -x "${dirpatcher}" ]]; then
778 err "couldn't locate dirpatcher"
779 exit 6
781 elif [[ -e "${patch_dir}" ]]; then
782 # patch_dir exists, but is not a directory - what's that mean?
783 note "patch_dir = ${patch_dir}"
784 err "patch_dir must be a directory"
785 exit 2
786 else
787 # patch_dir does not exist - this is a full "installer."
788 patch_dir=
790 note "patch_dir = ${patch_dir}"
791 note "is_patch = ${is_patch}"
792 note "dirpatcher = ${dirpatcher}"
794 # The update to install.
796 # update_app is the path to the new version of the .app. It will only be
797 # set at this point for a non-patch update. It is not yet set for a patch
798 # update because no such directory exists yet; it will be set later when
799 # dirpatcher creates it.
800 local update_app=
802 # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
803 # be set for patch updates.
804 local update_version_app_old=
805 local patch_app_dir=
806 local patch_versioned_dir=
808 local update_version_app update_version_ks product_id
809 if [[ -z "${is_patch}" ]]; then
810 update_app="${update_dmg_mount_point}/${APP_DIR}"
811 note "update_app = ${update_app}"
813 # Make sure that it's an absolute path.
814 if [[ "${update_app:0:1}" != "/" ]]; then
815 err "update_app must be an absolute path"
816 exit 2
819 # Make sure there's something to copy from.
820 if ! [[ -d "${update_app}" ]]; then
821 update_app="${update_dmg_mount_point}/${ALTERNATE_APP_DIR}"
822 note "update_app = ${update_app}"
824 if [[ "${update_app:0:1}" != "/" ]]; then
825 err "update_app (alternate) must be an absolute path"
826 exit 2
829 if ! [[ -d "${update_app}" ]]; then
830 err "update_app must be a directory"
831 exit 2
835 # Get some information about the update.
836 note "reading update values"
838 local update_app_plist="${update_app}/${APP_PLIST}"
839 note "update_app_plist = ${update_app_plist}"
840 if ! update_version_app="$(infoplist_read "${update_app_plist}" \
841 "${APP_VERSION_KEY}")" ||
842 [[ -z "${update_version_app}" ]]; then
843 err "couldn't determine update_version_app"
844 exit 2
846 note "update_version_app = ${update_version_app}"
848 local update_ks_plist="${update_app_plist}"
849 note "update_ks_plist = ${update_ks_plist}"
850 if ! update_version_ks="$(infoplist_read "${update_ks_plist}" \
851 "${KS_VERSION_KEY}")" ||
852 [[ -z "${update_version_ks}" ]]; then
853 err "couldn't determine update_version_ks"
854 exit 2
856 note "update_version_ks = ${update_version_ks}"
858 if ! product_id="$(infoplist_read "${update_ks_plist}" \
859 "${KS_PRODUCT_KEY}")" ||
860 [[ -z "${product_id}" ]]; then
861 err "couldn't determine product_id"
862 exit 2
864 note "product_id = ${product_id}"
865 else # [[ -n "${is_patch}" ]]
866 # Get some information about the update.
867 note "reading update values"
869 if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
870 [[ -z "${update_version_app_old}" ]]; then
871 err "couldn't determine update_version_app_old"
872 exit 2
874 note "update_version_app_old = ${update_version_app_old}"
876 if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
877 [[ -z "${update_version_app}" ]]; then
878 err "couldn't determine update_version_app"
879 exit 2
881 note "update_version_app = ${update_version_app}"
883 if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
884 [[ -z "${update_version_ks}" ]]; then
885 err "couldn't determine update_version_ks"
886 exit 2
888 note "update_version_ks = ${update_version_ks}"
890 if ! product_id=$(<"${patch_dir}/ks_product") ||
891 [[ -z "${product_id}" ]]; then
892 err "couldn't determine product_id"
893 exit 2
895 note "product_id = ${product_id}"
897 patch_app_dir="${patch_dir}/application.dirpatch"
898 if ! [[ -d "${patch_app_dir}" ]]; then
899 err "couldn't locate patch_app_dir"
900 exit 6
902 note "patch_app_dir = ${patch_app_dir}"
904 patch_versioned_dir=\
905 "${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
906 if ! [[ -d "${patch_versioned_dir}" ]]; then
907 err "couldn't locate patch_versioned_dir"
908 exit 6
910 note "patch_versioned_dir = ${patch_versioned_dir}"
913 # ksadmin is required. Keystone should have set a ${PATH} that includes it.
914 # Check that here, so that more useful feedback can be offered in the
915 # unlikely event that ksadmin is missing.
916 note "checking Keystone"
918 local ksadmin_path
919 if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then
920 err "couldn't locate ksadmin_path"
921 exit 3
923 note "ksadmin_path = ${ksadmin_path}"
925 # Call ksadmin_version once to prime the global state. This is needed
926 # because subsequent calls to ksadmin_version that occur in $(...)
927 # expansions will not affect the global state (although they can read from
928 # the already-initialized global state) and thus will cause a new ksadmin
929 # --ksadmin-version process to run for each check unless the globals have
930 # been properly initialized beforehand.
931 ksadmin_version >& /dev/null || true
932 local ksadmin_version_string
933 ksadmin_version_string="$(ksadmin_version 2> /dev/null || true)"
934 note "ksadmin_version_string = ${ksadmin_version_string}"
936 # Figure out where to install.
937 local installed_app
938 if ! installed_app="$(ksadmin -pP "${product_id}" | sed -Ene \
939 "s%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>\$%\\1%p")" ||
940 [[ -z "${installed_app}" ]]; then
941 err "couldn't locate installed_app"
942 exit 3
944 note "installed_app = ${installed_app}"
946 local want_full_installer_path="${installed_app}/.want_full_installer"
947 note "want_full_installer_path = ${want_full_installer_path}"
949 if [[ "${installed_app:0:1}" != "/" ]] ||
950 ! [[ -d "${installed_app}" ]]; then
951 err "installed_app must be an absolute path to a directory"
952 exit 3
955 # If this script is running as root, it's being driven by a system ticket.
956 # Otherwise, it's being driven by a user ticket.
957 local system_ticket=
958 if [[ ${EUID} -eq 0 ]]; then
959 system_ticket="y"
961 note "system_ticket = ${system_ticket}"
963 # If this script is being driven by a user ticket, but a system ticket is
964 # also present, there's a potential for the two to collide. Both ticket
965 # types might be present if another user on the system promoted the ticket
966 # to system: the other user could not have removed this user's user ticket.
967 # Handle that case here by deleting the user ticket and exiting early with
968 # a discrete exit code.
970 # Current versions of ksadmin will exit 1 (false) when asked to print tickets
971 # and given a specific product ID to print. Older versions of ksadmin would
972 # exit 0 (true), but those same versions did not support -S (meaning to check
973 # the system ticket store) and would exit 1 (false) with this invocation due
974 # to not understanding the question. Therefore, the usage here will only
975 # delete the existing user ticket when running as non-root with access to a
976 # sufficiently recent ksadmin. Older ksadmins are tolerated: the update will
977 # likely fail for another reason and the user ticket will hang around until
978 # something is eventually able to remove it.
979 if [[ -z "${system_ticket}" ]] &&
980 ksadmin -S --print-tickets --productid "${product_id}" >& /dev/null; then
981 ksadmin --delete --productid "${product_id}" || true
982 err "can't update on a user ticket when a system ticket is also present"
983 exit 4
986 # Figure out what the existing installed application is using for its
987 # versioned directory. This will be used later, to avoid removing the
988 # existing installed version's versioned directory in case anything is still
989 # using it.
990 note "reading install values"
992 local installed_app_plist="${installed_app}/${APP_PLIST}"
993 note "installed_app_plist = ${installed_app_plist}"
994 local installed_app_plist_path="${installed_app_plist}.plist"
995 note "installed_app_plist_path = ${installed_app_plist_path}"
996 local old_version_app
997 old_version_app="$(infoplist_read "${installed_app_plist}" \
998 "${APP_VERSION_KEY}" || true)"
999 note "old_version_app = ${old_version_app}"
1001 # old_version_app is not required, because it won't be present in skeleton
1002 # bootstrap installations, which just have an empty .app directory. Only
1003 # require it when doing a patch update, and use it to validate that the
1004 # patch applies to the old installed version. By definition, skeleton
1005 # bootstraps can't be installed with patch updates. They require the full
1006 # application on the disk image.
1007 if [[ -n "${is_patch}" ]]; then
1008 if [[ -z "${old_version_app}" ]]; then
1009 err "old_version_app required for patch"
1010 exit 6
1011 elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
1012 err "this patch does not apply to the installed version"
1013 exit 6
1017 local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
1018 note "installed_versions_dir = ${installed_versions_dir}"
1020 # If the installed application is incredibly old, old_versioned_dir may not
1021 # exist.
1022 local old_versioned_dir
1023 if [[ -n "${old_version_app}" ]]; then
1024 old_versioned_dir="${installed_versions_dir}/${old_version_app}"
1026 note "old_versioned_dir = ${old_versioned_dir}"
1028 # Collect the installed application's brand code, it will be used later. It
1029 # is not an error for the installed application to not have a brand code.
1030 local old_ks_plist="${installed_app_plist}"
1031 note "old_ks_plist = ${old_ks_plist}"
1032 local old_brand
1033 old_brand="$(infoplist_read "${old_ks_plist}" \
1034 "${KS_BRAND_KEY}" 2> /dev/null ||
1035 true)"
1036 note "old_brand = ${old_brand}"
1038 if has_32_bit_only_cpu; then
1039 # On a 32-bit-only system, make sure that the update contains 32-bit code.
1040 note "system is 32-bit-only"
1042 local test_binary
1043 if [[ -z "${is_patch}" ]]; then
1044 # For a full installer, the framework is available, so check it for
1045 # 32-bit code.
1046 local old_framework_dir="${old_versioned_dir}/${FRAMEWORK_DIR}"
1047 test_binary="${old_framework_dir}/${FRAMEWORK_NAME}"
1048 else
1049 # No application code is guaranteed to be available at this point for a
1050 # patch updater, but goobspatch is built alongside and will have the
1051 # same bitness of the product that this updater will install, so it's a
1052 # reasonable proxy.
1053 test_binary="${patch_dir}/goobspatch"
1055 note "test_binary = ${test_binary}"
1057 if ! file "${test_binary}" | grep -q 'i386$'; then
1058 err "can't install non-32-bit update on 32-bit-only system"
1059 mark_32_bit_only_system "${product_id}"
1060 exit 14
1061 else
1062 note "update will run on a 32-bit-only system"
1066 ensure_writable_symlinks_recursive "${installed_app}"
1068 # By copying to ${installed_app}, the existing application name will be
1069 # preserved, if the user has renamed the application on disk. Respecting
1070 # the user's changes is friendly.
1072 # Make sure that ${installed_versions_dir} exists, so that it can receive
1073 # the versioned directory. It may not exist if updating from an older
1074 # version that did not use the versioned layout on disk. Later, during the
1075 # rsync to copy the application directory, the mode bits and timestamp on
1076 # ${installed_versions_dir} will be set to conform to whatever is present in
1077 # the update.
1079 # ${installed_app} is guaranteed to exist at this point, but
1080 # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
1081 # if this update is actually an initial installation from a Keystone
1082 # skeleton bootstrap. The mkdir creates ${installed_app}/${CONTENTS_DIR} if
1083 # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
1084 note "creating installed_versions_dir"
1085 if ! mkdir -p "${installed_versions_dir}"; then
1086 err "mkdir of installed_versions_dir failed"
1087 exit 5
1090 local new_versioned_dir
1091 new_versioned_dir="${installed_versions_dir}/${update_version_app}"
1092 note "new_versioned_dir = ${new_versioned_dir}"
1094 # If there's an entry at ${new_versioned_dir} but it's not a directory
1095 # (or it's a symbolic link, whether or not it points to a directory), rsync
1096 # won't get rid of it. It's never correct to have a non-directory in place
1097 # of the versioned directory, so toss out whatever's there. Don't treat
1098 # this as a critical step: if removal fails, operation can still proceed to
1099 # to the dirpatcher or rsync, which will likely fail.
1100 if [[ -e "${new_versioned_dir}" ]] &&
1101 ([[ -L "${new_versioned_dir}" ]] ||
1102 ! [[ -d "${new_versioned_dir}" ]]); then
1103 note "removing non-directory in place of versioned directory"
1104 rm -f "${new_versioned_dir}" 2> /dev/null || true
1107 local update_versioned_dir
1108 if [[ -z "${is_patch}" ]]; then
1109 update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
1110 note "update_versioned_dir = ${update_versioned_dir}"
1111 else # [[ -n "${is_patch}" ]]
1112 # dirpatcher won't patch into a directory that already exists. Doing so
1113 # would be a bad idea, anyway. If ${new_versioned_dir} already exists,
1114 # it may be something left over from a previous failed or incomplete
1115 # update attempt, or it may be the live versioned directory if this is a
1116 # same-version update intended only to change channels. Since there's no
1117 # way to tell, this case is handled by having dirpatcher produce the new
1118 # versioned directory in a temporary location and then having rsync copy
1119 # it into place as an ${update_versioned_dir}, the same as in a non-patch
1120 # update. If ${new_versioned_dir} doesn't exist, dirpatcher can place the
1121 # new versioned directory at that location directly.
1122 local versioned_dir_target
1123 if ! [[ -e "${new_versioned_dir}" ]]; then
1124 versioned_dir_target="${new_versioned_dir}"
1125 note "versioned_dir_target = ${versioned_dir_target}"
1126 else
1127 ensure_temp_dir
1128 versioned_dir_target="${g_temp_dir}/${update_version_app}"
1129 note "versioned_dir_target = ${versioned_dir_target}"
1130 update_versioned_dir="${versioned_dir_target}"
1131 note "update_versioned_dir = ${update_versioned_dir}"
1134 note "dirpatching versioned directory"
1135 if ! "${dirpatcher}" "${old_versioned_dir}" \
1136 "${patch_versioned_dir}" \
1137 "${versioned_dir_target}"; then
1138 err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
1139 mark_failed_patch_update "${product_id}" \
1140 "${want_full_installer_path}" \
1141 "${old_ks_plist}" \
1142 "${old_version_app}" \
1143 "${system_ticket}"
1144 exit 12
1148 # Copy the versioned directory. The new versioned directory should have a
1149 # different name than any existing one, so this won't harm anything already
1150 # present in ${installed_versions_dir}, including the versioned directory
1151 # being used by any running processes. If this step is interrupted, there
1152 # will be an incomplete versioned directory left behind, but it won't
1153 # won't interfere with anything, and it will be replaced or removed during a
1154 # future update attempt.
1156 # In certain cases, same-version updates are distributed to move users
1157 # between channels; when this happens, the contents of the versioned
1158 # directories are identical and rsync will not render the versioned
1159 # directory unusable even for an instant.
1161 # ${update_versioned_dir} may be empty during a patch update (${is_patch})
1162 # if the dirpatcher above was able to write it into place directly. In
1163 # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
1164 # place.
1165 if [[ -n "${update_versioned_dir}" ]]; then
1166 note "rsyncing versioned directory"
1167 if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
1168 "${new_versioned_dir}"; then
1169 err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
1170 exit 7
1174 if [[ -n "${is_patch}" ]]; then
1175 # If the versioned directory was prepared in a temporary directory and
1176 # then rsynced into place, remove the temporary copy now that it's no
1177 # longer needed.
1178 if [[ -n "${update_versioned_dir}" ]]; then
1179 rm -rf "${update_versioned_dir}" 2> /dev/null || true
1180 update_versioned_dir=
1181 note "update_versioned_dir = ${update_versioned_dir}"
1184 # Prepare ${update_app}. This always needs to be done in a temporary
1185 # location because dirpatcher won't write to a directory that already
1186 # exists, and ${installed_app} needs to be used as input to dirpatcher
1187 # in any event. The new application will be rsynced into place once
1188 # dirpatcher creates it.
1189 ensure_temp_dir
1190 update_app="${g_temp_dir}/${APP_DIR}"
1191 note "update_app = ${update_app}"
1193 note "dirpatching app directory"
1194 if ! "${dirpatcher}" "${installed_app}" \
1195 "${patch_app_dir}" \
1196 "${update_app}"; then
1197 err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
1198 mark_failed_patch_update "${product_id}" \
1199 "${want_full_installer_path}" \
1200 "${old_ks_plist}" \
1201 "${old_version_app}" \
1202 "${system_ticket}"
1203 exit 13
1207 # See if the timestamp of what's currently on disk is newer than the
1208 # update's outer .app's timestamp. rsync will copy the update's timestamp
1209 # over, but if that timestamp isn't as recent as what's already on disk, the
1210 # .app will need to be touched.
1211 local needs_touch=
1212 if [[ "${installed_app}" -nt "${update_app}" ]]; then
1213 needs_touch="y"
1215 note "needs_touch = ${needs_touch}"
1217 # Copy the unversioned files into place, leaving everything in
1218 # ${installed_versions_dir} alone. If this step is interrupted, the
1219 # application will at least remain in a usable state, although it may not
1220 # pass signature validation. Depending on when this step is interrupted,
1221 # the application will either launch the old or the new version. The
1222 # critical point is when the main executable is replaced. There isn't very
1223 # much to copy in this step, because most of the application is in the
1224 # versioned directory. This step only accounts for around 50 files, most of
1225 # which are small localized InfoPlist.strings files. Note that
1226 # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
1227 # contents are excluded, having already been installed above.
1228 note "rsyncing app directory"
1229 if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
1230 "${update_app}/" "${installed_app}"; then
1231 err "rsync of app directory failed, status ${PIPESTATUS[0]}"
1232 exit 8
1235 note "rsyncs complete"
1237 if [[ -n "${is_patch}" ]]; then
1238 # update_app has been rsynced into place and is no longer needed.
1239 rm -rf "${update_app}" 2> /dev/null || true
1240 update_app=
1241 note "update_app = ${update_app}"
1244 if [[ -n "${g_temp_dir}" ]]; then
1245 # The temporary directory, if any, is no longer needed.
1246 rm -rf "${g_temp_dir}" 2> /dev/null || true
1247 g_temp_dir=
1248 note "g_temp_dir = ${g_temp_dir}"
1251 # Clean up any old .want_full_installer files from previous dirpatcher
1252 # failures. This is not considered a critical step, because this file
1253 # normally does not exist at all.
1254 rm -f "${want_full_installer_path}" || true
1256 # If necessary, touch the outermost .app so that it appears to the outside
1257 # world that something was done to the bundle. This will cause
1258 # LaunchServices to invalidate the information it has cached about the
1259 # bundle even if lsregister does not run. This is not done if rsync already
1260 # updated the timestamp to something newer than what had been on disk. This
1261 # is not considered a critical step, and if it fails, this script will not
1262 # exit.
1263 if [[ -n "${needs_touch}" ]]; then
1264 touch -cf "${installed_app}" || true
1267 # Read the new values, such as the version.
1268 note "reading new values"
1270 local new_version_app
1271 if ! new_version_app="$(infoplist_read "${installed_app_plist}" \
1272 "${APP_VERSION_KEY}")" ||
1273 [[ -z "${new_version_app}" ]]; then
1274 err "couldn't determine new_version_app"
1275 exit 9
1277 note "new_version_app = ${new_version_app}"
1279 local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
1280 note "new_versioned_dir = ${new_versioned_dir}"
1282 local new_ks_plist="${installed_app_plist}"
1283 note "new_ks_plist = ${new_ks_plist}"
1285 local new_version_ks
1286 if ! new_version_ks="$(infoplist_read "${new_ks_plist}" \
1287 "${KS_VERSION_KEY}")" ||
1288 [[ -z "${new_version_ks}" ]]; then
1289 err "couldn't determine new_version_ks"
1290 exit 9
1292 note "new_version_ks = ${new_version_ks}"
1294 local update_url
1295 if ! update_url="$(infoplist_read "${new_ks_plist}" "${KS_URL_KEY}")" ||
1296 [[ -z "${update_url}" ]]; then
1297 err "couldn't determine update_url"
1298 exit 9
1300 note "update_url = ${update_url}"
1302 # The channel ID is optional. Suppress stderr to prevent Keystone from
1303 # seeing possible error output.
1304 local channel
1305 channel="$(infoplist_read "${new_ks_plist}" \
1306 "${KS_CHANNEL_KEY}" 2> /dev/null || true)"
1307 note "channel = ${channel}"
1309 local tag="${channel}"
1310 local tag_key="${KS_CHANNEL_KEY}"
1311 if has_32_bit_only_cpu; then
1312 tag="${tag}-32bit"
1313 tag_key="${tag_key}-32bit"
1315 note "tag = ${tag}"
1316 note "tag_key = ${tag_key}"
1318 # Make sure that the update was successful by comparing the version found in
1319 # the update with the version now on disk.
1320 if [[ "${new_version_ks}" != "${update_version_ks}" ]]; then
1321 err "new_version_ks and update_version_ks do not match"
1322 exit 10
1325 # Notify LaunchServices. This is not considered a critical step, and
1326 # lsregister's exit codes shouldn't be confused with this script's own.
1327 # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
1328 # throttling disk i/o" messages that lsregister might print.
1329 note "notifying LaunchServices"
1330 local coreservices="/System/Library/Frameworks/CoreServices.framework"
1331 local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
1332 local lsregister="${launchservices}/Support/lsregister"
1333 note "coreservices = ${coreservices}"
1334 note "launchservices = ${launchservices}"
1335 note "lsregister = ${lsregister}"
1336 "${lsregister}" -f "${installed_app}" > /dev/null || true
1338 # The brand information is stored differently depending on whether this is
1339 # running for a system or user ticket.
1340 note "handling brand code"
1342 local set_brand_file_access=
1343 local brand_plist
1344 if [[ -n "${system_ticket}" ]]; then
1345 # System ticket.
1346 set_brand_file_access="y"
1347 brand_plist="/${UNROOTED_BRAND_PLIST}"
1348 else
1349 # User ticket.
1350 brand_plist=~/"${UNROOTED_BRAND_PLIST}"
1352 local brand_plist_path="${brand_plist}.plist"
1353 note "set_brand_file_access = ${set_brand_file_access}"
1354 note "brand_plist = ${brand_plist}"
1355 note "brand_plist_path = ${brand_plist_path}"
1357 local ksadmin_brand_plist_path
1358 local ksadmin_brand_key
1360 # Only the stable channel, identified by an empty channel string, has a
1361 # brand code. On the beta and dev channels, remove the brand plist if
1362 # present. Its presence means that the ticket used to manage a
1363 # stable-channel Chrome but the user has since replaced it with a beta or
1364 # dev channel version. Since the canary channel can run side-by-side with
1365 # another Chrome installation, don't remove the brand plist on that channel,
1366 # but skip the rest of the brand logic.
1367 if [[ "${channel}" = "beta" ]] || [[ "${channel}" = "dev" ]]; then
1368 note "defeating brand code on channel ${channel}"
1369 rm -f "${brand_plist_path}" 2>/dev/null || true
1370 elif [[ -n "${channel}" ]]; then
1371 # Canary channel.
1372 note "skipping brand code on channel ${channel}"
1373 else
1374 # Stable channel.
1375 # If the user manually updated their copy of Chrome, there might be new
1376 # brand information in the app bundle, and that needs to be copied out
1377 # into the file Keystone looks at.
1378 if [[ -n "${old_brand}" ]]; then
1379 local brand_dir
1380 brand_dir="$(dirname "${brand_plist_path}")"
1381 note "brand_dir = ${brand_dir}"
1382 if ! mkdir -p "${brand_dir}"; then
1383 err "couldn't mkdir brand_dir, continuing"
1384 else
1385 if ! defaults write "${brand_plist}" "${KS_BRAND_KEY}" \
1386 -string "${old_brand}"; then
1387 err "couldn't write brand_plist, continuing"
1388 elif [[ -n "${set_brand_file_access}" ]]; then
1389 if ! chown "root:wheel" "${brand_plist_path}"; then
1390 err "couldn't chown brand_plist_path, continuing"
1391 else
1392 if ! chmod 644 "${brand_plist_path}"; then
1393 err "couldn't chmod brand_plist_path, continuing"
1400 # Confirm that the brand file exists. It's optional.
1401 ksadmin_brand_plist_path="${brand_plist_path}"
1402 ksadmin_brand_key="${KS_BRAND_KEY}"
1404 if [[ ! -f "${ksadmin_brand_plist_path}" ]]; then
1405 # Clear any branding information.
1406 ksadmin_brand_plist_path=
1407 ksadmin_brand_key=
1411 note "ksadmin_brand_plist_path = ${ksadmin_brand_plist_path}"
1412 note "ksadmin_brand_key = ${ksadmin_brand_key}"
1414 note "notifying Keystone"
1416 local ksadmin_args=(
1417 --register
1418 --productid "${product_id}"
1419 --version "${new_version_ks}"
1420 --xcpath "${installed_app}"
1421 --url "${update_url}"
1424 if ksadmin_supports_tag; then
1425 ksadmin_args+=(
1426 --tag "${tag}"
1430 if ksadmin_supports_tagpath_tagkey; then
1431 ksadmin_args+=(
1432 --tag-path "${installed_app_plist_path}"
1433 --tag-key "${tag_key}"
1437 if ksadmin_supports_brandpath_brandkey; then
1438 ksadmin_args+=(
1439 --brand-path "${ksadmin_brand_plist_path}"
1440 --brand-key "${ksadmin_brand_key}"
1444 if ksadmin_supports_versionpath_versionkey; then
1445 ksadmin_args+=(
1446 --version-path "${installed_app_plist_path}"
1447 --version-key "${KS_VERSION_KEY}"
1451 note "ksadmin_args = ${ksadmin_args[*]}"
1453 if ! ksadmin "${ksadmin_args[@]}"; then
1454 err "ksadmin failed"
1455 exit 11
1458 # The remaining steps are not considered critical.
1459 set +e
1461 # Try to clean up old versions that are not in use. The strategy is to keep
1462 # the versioned directory corresponding to the update just applied
1463 # (obviously) and the version that was just replaced, and to use ps and lsof
1464 # to see if it looks like any processes are currently using any other old
1465 # directories. Directories not in use are removed. Old versioned
1466 # directories that are in use are left alone so as to not interfere with
1467 # running processes. These directories can be cleaned up by this script on
1468 # future updates.
1470 # To determine which directories are in use, both ps and lsof are used.
1471 # Each approach has limitations.
1473 # The ps check looks for processes within the versioned directory. Only
1474 # helper processes, such as renderers, are within the versioned directory.
1475 # Browser processes are not, so the ps check will not find them, and will
1476 # assume that a versioned directory is not in use if a browser is open
1477 # without any windows. The ps mechanism can also only detect processes
1478 # running on the system that is performing the update. If network shares
1479 # are involved, all bets are off.
1481 # The lsof check looks to see what processes have the framework dylib open.
1482 # Browser processes will have their versioned framework dylib open, so this
1483 # check is able to catch browsers even if there are no associated helper
1484 # processes. Like the ps check, the lsof check is limited to processes on
1485 # the system that is performing the update. Finally, unless running as
1486 # root, the lsof check can only find processes running as the effective user
1487 # performing the update.
1489 # These limitations are motivations to additionally preserve the versioned
1490 # directory corresponding to the version that was just replaced.
1491 note "cleaning up old versioned directories"
1493 local versioned_dir
1494 for versioned_dir in "${installed_versions_dir}/"*; do
1495 note "versioned_dir = ${versioned_dir}"
1496 if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] || \
1497 [[ "${versioned_dir}" = "${old_versioned_dir}" ]]; then
1498 # This is the versioned directory corresponding to the update that was
1499 # just applied or the version that was previously in use. Leave it
1500 # alone.
1501 note "versioned_dir is new_versioned_dir or old_versioned_dir, skipping"
1502 continue
1505 # Look for any processes whose executables are within this versioned
1506 # directory. They'll be helper processes, such as renderers. Their
1507 # existence indicates that this versioned directory is currently in use.
1508 local ps_string="${versioned_dir}/"
1509 note "ps_string = ${ps_string}"
1511 # Look for any processes using the framework dylib. This will catch
1512 # browser processes where the ps check will not, but it is limited to
1513 # processes running as the effective user.
1514 local lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
1515 note "lsof_file = ${lsof_file}"
1517 # ps -e displays all users' processes, -ww causes ps to not truncate
1518 # lines, -o comm instructs it to only print the command name, and the =
1519 # tells it to not print a header line.
1520 # The cut invocation filters the ps output to only have at most the number
1521 # of characters in ${ps_string}. This is done so that grep can look for
1522 # an exact match.
1523 # grep -F tells grep to look for lines that are exact matches (not regular
1524 # expressions), -q tells it to not print any output and just indicate
1525 # matches by exit status, and -x tells it that the entire line must match
1526 # ${ps_string} exactly, as opposed to matching a substring. A match
1527 # causes grep to exit zero (true).
1529 # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
1530 # process. If the file exists and is open, it will exit zero (true).
1531 if (! ps -ewwo comm= | \
1532 cut -c "1-${#ps_string}" | \
1533 grep -Fqx "${ps_string}") &&
1534 (! lsof "${lsof_file}" >& /dev/null); then
1535 # It doesn't look like anything is using this versioned directory. Get
1536 # rid of it.
1537 note "versioned_dir doesn't appear to be in use, removing"
1538 rm -rf "${versioned_dir}"
1539 else
1540 note "versioned_dir is in use, skipping"
1542 done
1544 # If this script is being driven by a user Keystone ticket, it is not
1545 # running as root. If the application is installed somewhere under
1546 # /Applications, try to make it writable by all admin users. This will
1547 # allow other admin users to update the application from their own user
1548 # Keystone instances.
1550 # If the script is being driven by a user Keystone ticket (not running as
1551 # root) and the application is not installed under /Applications, it might
1552 # not be in a system-wide location, and it probably won't be something that
1553 # other users on the system are running, so err on the side of safety and
1554 # don't make it group-writable.
1556 # If this script is being driven by a system ticket (running as root), it's
1557 # future updates can be expected to be applied the same way, so admin-
1558 # writability is not a concern. Set the entire thing to be owned by root
1559 # in that case, regardless of where it's installed, and drop any group and
1560 # other write permission.
1562 # If this script is running as a user that is not a member of the admin
1563 # group, the chgrp operation will not succeed. Tolerate that case, because
1564 # it's better than the alternative, which is to make the application
1565 # world-writable.
1566 note "setting permissions"
1568 local chmod_mode="a+rX,u+w,go-w"
1569 if [[ -z "${system_ticket}" ]]; then
1570 if [[ "${installed_app:0:14}" = "/Applications/" ]] &&
1571 chgrp -Rh admin "${installed_app}" 2> /dev/null; then
1572 chmod_mode="a+rX,ug+w,o-w"
1574 else
1575 chown -Rh root:wheel "${installed_app}" 2> /dev/null
1578 note "chmod_mode = ${chmod_mode}"
1579 chmod -R "${chmod_mode}" "${installed_app}" 2> /dev/null
1581 # On the Mac, or at least on HFS+, symbolic link permissions are significant,
1582 # but chmod -R and -h can't be used together. Do another pass to fix the
1583 # permissions on any symbolic links.
1584 find "${installed_app}" -type l -exec chmod -h "${chmod_mode}" {} + \
1585 2> /dev/null
1587 # If an update is triggered from within the application itself, the update
1588 # process inherits the quarantine bit (LSFileQuarantineEnabled). Any files
1589 # or directories created during the update will be quarantined in that case,
1590 # which may cause Launch Services to display quarantine UI. That's bad,
1591 # especially if it happens when the outer .app launches a quarantined inner
1592 # helper. If the application is already on the system and is being updated,
1593 # then it can be assumed that it should not be quarantined. Use xattr to
1594 # drop the quarantine attribute.
1596 # TODO(mark): Instead of letting the quarantine attribute be set and then
1597 # dropping it here, figure out a way to get the update process to run
1598 # without LSFileQuarantineEnabled even when triggering an update from within
1599 # the application.
1600 note "lifting quarantine"
1602 if os_xattr_supports_r; then
1603 # On 10.6, xattr supports -r for recursive operation.
1604 xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
1605 else
1606 # On earlier systems, xattr doesn't support -r, so run xattr via find.
1607 find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \
1608 2> /dev/null
1611 # Do Keychain reauthorization. This involves running a stub executable on
1612 # the dmg that loads the newly-updated framework and jumps to it to perform
1613 # the reauthorization. The stub executable can be signed by the old
1614 # certificate even after the rest of Chrome switches to the new certificate,
1615 # so it still has access to the old Keychain items. The stub executable is
1616 # an unbundled flat file executable whose name matches the real
1617 # application's bundle identifier, so it's permitted access to the Keychain
1618 # items. Doing a reauthorization step at update time reauthorizes Keychain
1619 # items for users who never bother restarting Chrome, and provides a
1620 # mechanism to continue doing reauthorizations even after the certificate
1621 # changes. However, it only works for non-system ticket installations of
1622 # Chrome, because the updater runs as root when on a system ticket, and root
1623 # can't access individual user Keychains.
1625 # Even if the reauthorization tool is launched, it doesn't necessarily try
1626 # to do anything. It will only attempt to perform a reauthorization if one
1627 # hasn't yet been done at update time.
1628 note "maybe reauthorizing Keychain"
1630 if [[ -z "${system_ticket}" ]]; then
1631 local new_bundleid_app
1632 new_bundleid_app="$(infoplist_read "${installed_app_plist}" \
1633 "${APP_BUNDLEID_KEY}" || true)"
1634 note "new_bundleid_app = ${new_bundleid_app}"
1636 local keychain_reauthorize_dir="\
1637 ${update_dmg_mount_point}/${KEYCHAIN_REAUTHORIZE_DIR}"
1638 local keychain_reauthorize_path="\
1639 ${keychain_reauthorize_dir}/${new_bundleid_app}"
1640 note "keychain_reauthorize_path = ${keychain_reauthorize_path}"
1642 if [[ -x "${keychain_reauthorize_path}" ]]; then
1643 local framework_dir="${new_versioned_dir}/${FRAMEWORK_DIR}"
1644 local framework_code_path="${framework_dir}/${FRAMEWORK_NAME}"
1645 note "framework_code_path = ${framework_code_path}"
1647 if [[ -f "${framework_code_path}" ]]; then
1648 note "reauthorizing Keychain"
1649 "${keychain_reauthorize_path}" "${framework_code_path}"
1652 else
1653 note "system ticket, not reauthorizing Keychain"
1656 # Great success!
1657 note "done!"
1659 trap - EXIT
1661 return 0
1664 # Check "less than" instead of "not equal to" in case Keystone ever changes to
1665 # pass more arguments.
1666 if [[ ${#} -lt 1 ]]; then
1667 usage
1668 exit 2
1671 main "${@}"
1672 exit ${?}