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# 14  The update is incompatible with the system
35#
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
40
41set -eu
42
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.
48export PATH="${PATH}:/sbin:/usr/sbin"
49
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.
56unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
57export -n SHELLOPTS
58
59set -o pipefail
60shopt -s nullglob
61
62ME="$(basename "${0}")"
63readonly ME
64
65readonly KS_CHANNEL_KEY="KSChannelID"
66
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:=}
70err() {
71  local error="${1}"
72
73  local id=
74  if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
75    id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
76  fi
77
78  echo "${ME}${id}: ${error}" >& 2
79}
80
81note() {
82  local message="${1}"
83
84  if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
85    err "${message}"
86  fi
87}
88
89g_temp_dir=
90cleanup() {
91  local status=${?}
92
93  trap - EXIT
94  trap '' HUP INT QUIT TERM
95
96  if [[ ${status} -ge 128 ]]; then
97    err "Caught signal $((${status} - 128))"
98  fi
99
100  if [[ -n "${g_temp_dir}" ]]; then
101    rm -rf "${g_temp_dir}"
102  fi
103
104  exit ${status}
105}
106
107ensure_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]}"
114    fi
115    if [[ -z "${template}" ]]; then
116      template="keystone_install"
117    fi
118
119    g_temp_dir="$(mktemp -d -t "${template}")"
120    note "g_temp_dir = ${g_temp_dir}"
121  fi
122}
123
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.
128is_writable_symlink() {
129  local symlink="${1}"
130
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
135  fi
136
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
142  fi
143
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
148    fi
149    return 1
150  fi
151
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
160    fi
161  done
162  if [[ -n "${group_match}" ]]; then
163    if [[ "${link_mode:5:1}" = "w" ]]; then
164      return 0
165    fi
166    return 1
167  fi
168
169  # Check the other-write bit.
170  if [[ "${link_mode:8:1}" = "w" ]]; then
171    return 0
172  fi
173
174  return 1
175}
176
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).
182ensure_writable_symlink() {
183  local symlink="${1}"
184
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.
192    #
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.
201
202    local target
203    target="$(readlink "${symlink}" 2> /dev/null || true)"
204    if [[ -z "${target}" ]]; then
205      return 0
206    fi
207
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.
216
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
222    fi
223    temp_link="${temp_link_dir}/$(basename "${symlink}")"
224
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}"
229  fi
230
231  return 0
232}
233
234# ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
235# symbolic link in |directory|, recursively.
236#
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.
240#
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.
245#
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.
257ensure_writable_symlinks_recursive() {
258  local directory="${1}"
259
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
264  fi
265
266  # This step isn't critical.
267  local set_e=
268  if [[ "${-}" =~ e ]]; then
269    set_e="y"
270    set +e
271  fi
272
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)
278
279  # Go back to how things were.
280  if [[ -n "${set_e}" ]]; then
281    set -e
282  fi
283}
284
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.
290is_version_ge() {
291  local left="${1}"
292  local right="${2}"
293
294  local -a left_array right_array
295  IFS=. left_array=(${left})
296  IFS=. right_array=(${right})
297
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}
303  fi
304
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
316    fi
317    ((++index))
318  done
319
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
326    fi
327    ((++index))
328  done
329
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
336    fi
337    ((++index))
338  done
339
340  # Upon reaching this point, the two version numbers are semantically equal.
341  return 0
342}
343
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.
347g_checked_os_version=
348g_os_version=
349os_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}"
354  fi
355  echo "${g_os_version}"
356  return 0
357}
358
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".
365is_os_version_ge() {
366  local check_version="${1}"
367
368  local os_version="$(os_version)"
369  is_version_ge "${os_version}" "${check_version}"
370
371  # The return value of is_version_ge is used as this function's return value.
372}
373
374# Returns 0 (true) if xattr supports -r for recursive operation.
375os_xattr_supports_r() {
376  # xattr -r is supported in Mac OS X 10.6.
377  is_os_version_ge 10.6
378
379  # The return value of is_os_version_ge is used as this function's return
380  # value.
381}
382
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.
388g_checked_ksadmin_version=
389g_ksadmin_version=
390ksadmin_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}"
395  fi
396  echo "${g_ksadmin_version}"
397  return 0
398}
399
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".
407is_ksadmin_version_ge() {
408  local check_version="${1}"
409
410  local ksadmin_version="$(ksadmin_version)"
411  is_version_ge "${ksadmin_version}" "${check_version}"
412
413  # The return value of is_version_ge is used as this function's return value.
414}
415
416# Returns 0 (true) if ksadmin supports --tag.
417ksadmin_supports_tag() {
418  local ksadmin_version
419
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
425  fi
426
427  return 1
428}
429
430# Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
431ksadmin_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
434
435  # The return value of is_ksadmin_version_ge is used as this function's
436  # return value.
437}
438
439# Returns 0 (true) if ksadmin supports --brand-path and --brand-key.
440ksadmin_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
443
444  # The return value of is_ksadmin_version_ge is used as this function's
445  # return value.
446}
447
448# Returns 0 (true) if ksadmin supports --version-path and --version-key.
449ksadmin_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
452
453  # The return value of is_ksadmin_version_ge is used as this function's
454  # return value.
455}
456
457has_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 ]]
460
461  # The return value of the comparison is used as this function's return
462  # value.
463}
464
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.
468#
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.
479#
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.
487#
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.
494infoplist_read() {
495  __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
496}
497
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.
503#
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.
511#
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.
515mark_32_bit_only_system() {
516  local product_id="${1}"
517
518  # This step isn't critical.
519  local set_e=
520  if [[ "${-}" =~ e ]]; then
521    set_e="y"
522    set +e
523  fi
524
525  note "marking 32-bit-only system"
526
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
531    fi
532    return 0
533  fi
534
535  local current_tag="$(ksadmin --productid "${product_id}" --print-tag)"
536  note "current_tag = ${current_tag}"
537
538  if grep -Eq -- '-32bit(-|$)' <<< "${current_tag}"; then
539    note "current tag already has -32bit"
540    if [[ -n "${set_e}" ]]; then
541      set -e
542    fi
543    return 0
544  fi
545
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}"
554
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"
563  fi
564
565  # Go back to how things were.
566  if [[ -n "${set_e}" ]]; then
567    set -e
568  fi
569}
570
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.
576#
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.
583mark_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}"
589
590  # This step isn't critical.
591  local set_e=
592  if [[ "${-}" =~ e ]]; then
593    set_e="y"
594    set +e
595  fi
596
597  note "marking failed patch update"
598
599  local channel
600  channel="$(infoplist_read "${old_ks_plist}" "${KS_CHANNEL_KEY}" 2> /dev/null)"
601
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"
607  fi
608
609  tag="${tag}-full"
610  tag_key="${tag_key}-full"
611
612  note "tag = ${tag}"
613  note "tag_key = ${tag_key}"
614
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
624    fi
625    return 0
626  fi
627
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.
636  #
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}"
644
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"
652  fi
653  note "chmod_mode = ${chmod_mode}"
654  chmod "${chmod_mode}" "${want_full_installer_path}" 2> /dev/null
655
656  local old_ks_plist_path="${old_ks_plist}.plist"
657
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}"
662  )
663
664  if ksadmin_supports_tag; then
665    ksadmin_args+=(
666      --tag "${tag}"
667    )
668  fi
669
670  if ksadmin_supports_tagpath_tagkey; then
671    ksadmin_args+=(
672      --tag-path "${old_ks_plist_path}"
673      --tag-key "${tag_key}"
674    )
675  fi
676
677  note "ksadmin_args = ${ksadmin_args[*]}"
678
679  if ! ksadmin "${ksadmin_args[@]}"; then
680    err "ksadmin failed to mark failed patch update"
681  else
682    note "marked failed patch update"
683  fi
684
685  # Go back to how things were.
686  if [[ -n "${set_e}" ]]; then
687    set -e
688  fi
689}
690
691usage() {
692  echo "usage: ${ME} update_dmg_mount_point" >& 2
693}
694
695main() {
696  local update_dmg_mount_point="${1}"
697
698  # Early steps are critical.  Don't continue past any failure.
699  set -e
700
701  trap cleanup EXIT HUP INT QUIT TERM
702
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"
714
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"
721
722  readonly QUARANTINE_ATTR="com.apple.quarantine"
723
724  # Don't use rsync -a, because -a expands to -rlptgoD.  -g and -o copy owners
725  # and groups, respectively, from the source, and that is undesirable in this
726  # case.  -D copies devices and special files; copying devices only works
727  # when running as root, so for consistency between privileged and
728  # unprivileged operation, this option is omitted as well.
729  #  -I, --ignore-times  don't skip files that match in size and mod-time
730  #  -l, --links         copy symlinks as symlinks
731  #  -r, --recursive     recurse into directories
732  #  -p, --perms         preserve permissions
733  #  -t, --times         preserve times
734  readonly RSYNC_FLAGS="-Ilprt"
735
736  # It's difficult to get GOOGLE_CHROME_UPDATER_DEBUG set in the environment
737  # when this script is called from Keystone.  If a "debug file" exists in
738  # either the root directory or the home directory of the user who owns the
739  # ticket, turn on verbosity.  This may aid debugging.
740  if [[ -e "/${UNROOTED_DEBUG_FILE}" ]] ||
741     [[ -e ~/"${UNROOTED_DEBUG_FILE}" ]]; then
742    export GOOGLE_CHROME_UPDATER_DEBUG="y"
743  fi
744
745  note "update_dmg_mount_point = ${update_dmg_mount_point}"
746
747  # The argument should be the disk image path.  Make sure it exists and that
748  # it's an absolute path.
749  note "checking update"
750
751  if [[ -z "${update_dmg_mount_point}" ]] ||
752     [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
753     ! [[ -d "${update_dmg_mount_point}" ]]; then
754    err "update_dmg_mount_point must be an absolute path to a directory"
755    usage
756    exit 2
757  fi
758
759  local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
760  if [[ "${patch_dir:0:1}" != "/" ]]; then
761    note "patch_dir = ${patch_dir}"
762    err "patch_dir must be an absolute path"
763    exit 2
764  fi
765
766  # Figure out if this is an ordinary installation disk image being used as a
767  # full update, or a patch.  A patch will have a .patch directory at the root
768  # of the disk image containing information about the update, tools to apply
769  # it, and the update contents.
770  local is_patch=
771  local dirpatcher=
772  if [[ -d "${patch_dir}" ]]; then
773    # patch_dir exists and is a directory - this is a patch update.
774    is_patch="y"
775    dirpatcher="${patch_dir}/dirpatcher.sh"
776    if ! [[ -x "${dirpatcher}" ]]; then
777      err "couldn't locate dirpatcher"
778      exit 6
779    fi
780  elif [[ -e "${patch_dir}" ]]; then
781    # patch_dir exists, but is not a directory - what's that mean?
782    note "patch_dir = ${patch_dir}"
783    err "patch_dir must be a directory"
784    exit 2
785  else
786    # patch_dir does not exist - this is a full "installer."
787    patch_dir=
788  fi
789  note "patch_dir = ${patch_dir}"
790  note "is_patch = ${is_patch}"
791  note "dirpatcher = ${dirpatcher}"
792
793  # The update to install.
794
795  # update_app is the path to the new version of the .app.  It will only be
796  # set at this point for a non-patch update.  It is not yet set for a patch
797  # update because no such directory exists yet; it will be set later when
798  # dirpatcher creates it.
799  local update_app=
800
801  # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
802  # be set for patch updates.
803  local update_version_app_old=
804  local patch_app_dir=
805  local patch_versioned_dir=
806
807  local update_version_app update_version_ks product_id
808  if [[ -z "${is_patch}" ]]; then
809    update_app="${update_dmg_mount_point}/${APP_DIR}"
810    note "update_app = ${update_app}"
811
812    # Make sure that it's an absolute path.
813    if [[ "${update_app:0:1}" != "/" ]]; then
814      err "update_app must be an absolute path"
815      exit 2
816    fi
817
818    # Make sure there's something to copy from.
819    if ! [[ -d "${update_app}" ]]; then
820      update_app="${update_dmg_mount_point}/${ALTERNATE_APP_DIR}"
821      note "update_app = ${update_app}"
822
823      if [[ "${update_app:0:1}" != "/" ]]; then
824        err "update_app (alternate) must be an absolute path"
825        exit 2
826      fi
827
828      if ! [[ -d "${update_app}" ]]; then
829        err "update_app must be a directory"
830        exit 2
831      fi
832    fi
833
834    # Get some information about the update.
835    note "reading update values"
836
837    local update_app_plist="${update_app}/${APP_PLIST}"
838    note "update_app_plist = ${update_app_plist}"
839    if ! update_version_app="$(infoplist_read "${update_app_plist}" \
840                                              "${APP_VERSION_KEY}")" ||
841       [[ -z "${update_version_app}" ]]; then
842      err "couldn't determine update_version_app"
843      exit 2
844    fi
845    note "update_version_app = ${update_version_app}"
846
847    local update_ks_plist="${update_app_plist}"
848    note "update_ks_plist = ${update_ks_plist}"
849    if ! update_version_ks="$(infoplist_read "${update_ks_plist}" \
850                                             "${KS_VERSION_KEY}")" ||
851       [[ -z "${update_version_ks}" ]]; then
852      err "couldn't determine update_version_ks"
853      exit 2
854    fi
855    note "update_version_ks = ${update_version_ks}"
856
857    if ! product_id="$(infoplist_read "${update_ks_plist}" \
858                                      "${KS_PRODUCT_KEY}")" ||
859       [[ -z "${product_id}" ]]; then
860      err "couldn't determine product_id"
861      exit 2
862    fi
863    note "product_id = ${product_id}"
864  else  # [[ -n "${is_patch}" ]]
865    # Get some information about the update.
866    note "reading update values"
867
868    if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
869       [[ -z "${update_version_app_old}" ]]; then
870      err "couldn't determine update_version_app_old"
871      exit 2
872    fi
873    note "update_version_app_old = ${update_version_app_old}"
874
875    if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
876       [[ -z "${update_version_app}" ]]; then
877      err "couldn't determine update_version_app"
878      exit 2
879    fi
880    note "update_version_app = ${update_version_app}"
881
882    if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
883       [[ -z "${update_version_ks}" ]]; then
884      err "couldn't determine update_version_ks"
885      exit 2
886    fi
887    note "update_version_ks = ${update_version_ks}"
888
889    if ! product_id=$(<"${patch_dir}/ks_product") ||
890       [[ -z "${product_id}" ]]; then
891      err "couldn't determine product_id"
892      exit 2
893    fi
894    note "product_id = ${product_id}"
895
896    patch_app_dir="${patch_dir}/application.dirpatch"
897    if ! [[ -d "${patch_app_dir}" ]]; then
898      err "couldn't locate patch_app_dir"
899      exit 6
900    fi
901    note "patch_app_dir = ${patch_app_dir}"
902
903    patch_versioned_dir=\
904"${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
905    if ! [[ -d "${patch_versioned_dir}" ]]; then
906      err "couldn't locate patch_versioned_dir"
907      exit 6
908    fi
909    note "patch_versioned_dir = ${patch_versioned_dir}"
910  fi
911
912  # ksadmin is required. Keystone should have set a ${PATH} that includes it.
913  # Check that here, so that more useful feedback can be offered in the
914  # unlikely event that ksadmin is missing.
915  note "checking Keystone"
916
917  local ksadmin_path
918  if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then
919    err "couldn't locate ksadmin_path"
920    exit 3
921  fi
922  note "ksadmin_path = ${ksadmin_path}"
923
924  # Call ksadmin_version once to prime the global state.  This is needed
925  # because subsequent calls to ksadmin_version that occur in $(...)
926  # expansions will not affect the global state (although they can read from
927  # the already-initialized global state) and thus will cause a new ksadmin
928  # --ksadmin-version process to run for each check unless the globals have
929  # been properly initialized beforehand.
930  ksadmin_version >& /dev/null || true
931  local ksadmin_version_string
932  ksadmin_version_string="$(ksadmin_version 2> /dev/null || true)"
933  note "ksadmin_version_string = ${ksadmin_version_string}"
934
935  # Figure out where to install.
936  local installed_app
937  if ! installed_app="$(ksadmin -pP "${product_id}" | sed -Ene \
938      "s%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>\$%\\1%p")" ||
939      [[ -z "${installed_app}" ]]; then
940    err "couldn't locate installed_app"
941    exit 3
942  fi
943  note "installed_app = ${installed_app}"
944
945  local want_full_installer_path="${installed_app}/.want_full_installer"
946  note "want_full_installer_path = ${want_full_installer_path}"
947
948  if [[ "${installed_app:0:1}" != "/" ]] ||
949     ! [[ -d "${installed_app}" ]]; then
950    err "installed_app must be an absolute path to a directory"
951    exit 3
952  fi
953
954  # If this script is running as root, it's being driven by a system ticket.
955  # Otherwise, it's being driven by a user ticket.
956  local system_ticket=
957  if [[ ${EUID} -eq 0 ]]; then
958    system_ticket="y"
959  fi
960  note "system_ticket = ${system_ticket}"
961
962  # If this script is being driven by a user ticket, but a system ticket is
963  # also present, there's a potential for the two to collide.  Both ticket
964  # types might be present if another user on the system promoted the ticket
965  # to system: the other user could not have removed this user's user ticket.
966  # Handle that case here by deleting the user ticket and exiting early with
967  # a discrete exit code.
968  #
969  # Current versions of ksadmin will exit 1 (false) when asked to print tickets
970  # and given a specific product ID to print.  Older versions of ksadmin would
971  # exit 0 (true), but those same versions did not support -S (meaning to check
972  # the system ticket store) and would exit 1 (false) with this invocation due
973  # to not understanding the question.  Therefore, the usage here will only
974  # delete the existing user ticket when running as non-root with access to a
975  # sufficiently recent ksadmin.  Older ksadmins are tolerated: the update will
976  # likely fail for another reason and the user ticket will hang around until
977  # something is eventually able to remove it.
978  if [[ -z "${system_ticket}" ]] &&
979     ksadmin -S --print-tickets --productid "${product_id}" >& /dev/null; then
980    ksadmin --delete --productid "${product_id}" || true
981    err "can't update on a user ticket when a system ticket is also present"
982    exit 4
983  fi
984
985  # Figure out what the existing installed application is using for its
986  # versioned directory.  This will be used later, to avoid removing the
987  # existing installed version's versioned directory in case anything is still
988  # using it.
989  note "reading install values"
990
991  local installed_app_plist="${installed_app}/${APP_PLIST}"
992  note "installed_app_plist = ${installed_app_plist}"
993  local installed_app_plist_path="${installed_app_plist}.plist"
994  note "installed_app_plist_path = ${installed_app_plist_path}"
995  local old_version_app
996  old_version_app="$(infoplist_read "${installed_app_plist}" \
997                                    "${APP_VERSION_KEY}" || true)"
998  note "old_version_app = ${old_version_app}"
999
1000  # old_version_app is not required, because it won't be present in skeleton
1001  # bootstrap installations, which just have an empty .app directory.  Only
1002  # require it when doing a patch update, and use it to validate that the
1003  # patch applies to the old installed version.  By definition, skeleton
1004  # bootstraps can't be installed with patch updates.  They require the full
1005  # application on the disk image.
1006  if [[ -n "${is_patch}" ]]; then
1007    if [[ -z "${old_version_app}" ]]; then
1008      err "old_version_app required for patch"
1009      exit 6
1010    elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
1011      err "this patch does not apply to the installed version"
1012      exit 6
1013    fi
1014  fi
1015
1016  local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
1017  note "installed_versions_dir = ${installed_versions_dir}"
1018
1019  # If the installed application is incredibly old, old_versioned_dir may not
1020  # exist.
1021  local old_versioned_dir
1022  if [[ -n "${old_version_app}" ]]; then
1023    old_versioned_dir="${installed_versions_dir}/${old_version_app}"
1024  fi
1025  note "old_versioned_dir = ${old_versioned_dir}"
1026
1027  # Collect the installed application's brand code, it will be used later.  It
1028  # is not an error for the installed application to not have a brand code.
1029  local old_ks_plist="${installed_app_plist}"
1030  note "old_ks_plist = ${old_ks_plist}"
1031  local old_brand
1032  old_brand="$(infoplist_read "${old_ks_plist}" \
1033                              "${KS_BRAND_KEY}" 2> /dev/null ||
1034               true)"
1035  note "old_brand = ${old_brand}"
1036
1037  local update_versioned_dir=
1038  if [[ -z "${is_patch}" ]]; then
1039    update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
1040    note "update_versioned_dir = ${update_versioned_dir}"
1041  fi
1042
1043  if has_32_bit_only_cpu; then
1044    # On a 32-bit-only system, make sure that the update contains 32-bit code.
1045    note "system is 32-bit-only"
1046
1047    local test_binary
1048    if [[ -z "${is_patch}" ]]; then
1049      # For a full installer, the framework is available, so check it for
1050      # 32-bit code.
1051      local update_framework_dir="${update_versioned_dir}/${FRAMEWORK_DIR}"
1052      test_binary="${update_framework_dir}/${FRAMEWORK_NAME}"
1053    else
1054      # No application code is guaranteed to be available at this point for a
1055      # patch updater, but goobspatch is built alongside and will have the
1056      # same bitness of the product that this updater will install, so it's a
1057      # reasonable proxy.
1058      test_binary="${patch_dir}/goobspatch"
1059    fi
1060    note "test_binary = ${test_binary}"
1061
1062    if ! file "${test_binary}" | grep -q 'i386$'; then
1063      err "can't install non-32-bit update on 32-bit-only system"
1064      mark_32_bit_only_system "${product_id}"
1065      exit 14
1066    else
1067      note "update will run on a 32-bit-only system"
1068    fi
1069  fi
1070
1071  ensure_writable_symlinks_recursive "${installed_app}"
1072
1073  # By copying to ${installed_app}, the existing application name will be
1074  # preserved, if the user has renamed the application on disk.  Respecting
1075  # the user's changes is friendly.
1076
1077  # Make sure that ${installed_versions_dir} exists, so that it can receive
1078  # the versioned directory.  It may not exist if updating from an older
1079  # version that did not use the versioned layout on disk.  Later, during the
1080  # rsync to copy the application directory, the mode bits and timestamp on
1081  # ${installed_versions_dir} will be set to conform to whatever is present in
1082  # the update.
1083  #
1084  # ${installed_app} is guaranteed to exist at this point, but
1085  # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
1086  # if this update is actually an initial installation from a Keystone
1087  # skeleton bootstrap.  The mkdir creates ${installed_app}/${CONTENTS_DIR} if
1088  # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
1089  note "creating installed_versions_dir"
1090  if ! mkdir -p "${installed_versions_dir}"; then
1091    err "mkdir of installed_versions_dir failed"
1092    exit 5
1093  fi
1094
1095  local new_versioned_dir
1096  new_versioned_dir="${installed_versions_dir}/${update_version_app}"
1097  note "new_versioned_dir = ${new_versioned_dir}"
1098
1099  # If there's an entry at ${new_versioned_dir} but it's not a directory
1100  # (or it's a symbolic link, whether or not it points to a directory), rsync
1101  # won't get rid of it.  It's never correct to have a non-directory in place
1102  # of the versioned directory, so toss out whatever's there.  Don't treat
1103  # this as a critical step: if removal fails, operation can still proceed to
1104  # to the dirpatcher or rsync, which will likely fail.
1105  if [[ -e "${new_versioned_dir}" ]] &&
1106     ([[ -L "${new_versioned_dir}" ]] ||
1107      ! [[ -d "${new_versioned_dir}" ]]); then
1108    note "removing non-directory in place of versioned directory"
1109    rm -f "${new_versioned_dir}" 2> /dev/null || true
1110  fi
1111
1112  if [[ -n "${is_patch}" ]]; then
1113    # dirpatcher won't patch into a directory that already exists.  Doing so
1114    # would be a bad idea, anyway.  If ${new_versioned_dir} already exists,
1115    # it may be something left over from a previous failed or incomplete
1116    # update attempt, or it may be the live versioned directory if this is a
1117    # same-version update intended only to change channels.  Since there's no
1118    # way to tell, this case is handled by having dirpatcher produce the new
1119    # versioned directory in a temporary location and then having rsync copy
1120    # it into place as an ${update_versioned_dir}, the same as in a non-patch
1121    # update.  If ${new_versioned_dir} doesn't exist, dirpatcher can place the
1122    # new versioned directory at that location directly.
1123    local versioned_dir_target
1124    if ! [[ -e "${new_versioned_dir}" ]]; then
1125      versioned_dir_target="${new_versioned_dir}"
1126      note "versioned_dir_target = ${versioned_dir_target}"
1127    else
1128      ensure_temp_dir
1129      versioned_dir_target="${g_temp_dir}/${update_version_app}"
1130      note "versioned_dir_target = ${versioned_dir_target}"
1131      update_versioned_dir="${versioned_dir_target}"
1132      note "update_versioned_dir = ${update_versioned_dir}"
1133    fi
1134
1135    note "dirpatching versioned directory"
1136    if ! "${dirpatcher}" "${old_versioned_dir}" \
1137                         "${patch_versioned_dir}" \
1138                         "${versioned_dir_target}"; then
1139      err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
1140      mark_failed_patch_update "${product_id}" \
1141                               "${want_full_installer_path}" \
1142                               "${old_ks_plist}" \
1143                               "${old_version_app}" \
1144                               "${system_ticket}"
1145      exit 12
1146    fi
1147  fi
1148
1149  # Copy the versioned directory.  The new versioned directory should have a
1150  # different name than any existing one, so this won't harm anything already
1151  # present in ${installed_versions_dir}, including the versioned directory
1152  # being used by any running processes.  If this step is interrupted, there
1153  # will be an incomplete versioned directory left behind, but it won't
1154  # won't interfere with anything, and it will be replaced or removed during a
1155  # future update attempt.
1156  #
1157  # In certain cases, same-version updates are distributed to move users
1158  # between channels; when this happens, the contents of the versioned
1159  # directories are identical and rsync will not render the versioned
1160  # directory unusable even for an instant.
1161  #
1162  # ${update_versioned_dir} may be empty during a patch update (${is_patch})
1163  # if the dirpatcher above was able to write it into place directly.  In
1164  # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
1165  # place.
1166  if [[ -n "${update_versioned_dir}" ]]; then
1167    note "rsyncing versioned directory"
1168    if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
1169                                              "${new_versioned_dir}"; then
1170      err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
1171      exit 7
1172    fi
1173  fi
1174
1175  if [[ -n "${is_patch}" ]]; then
1176    # If the versioned directory was prepared in a temporary directory and
1177    # then rsynced into place, remove the temporary copy now that it's no
1178    # longer needed.
1179    if [[ -n "${update_versioned_dir}" ]]; then
1180      rm -rf "${update_versioned_dir}" 2> /dev/null || true
1181      update_versioned_dir=
1182      note "update_versioned_dir = ${update_versioned_dir}"
1183    fi
1184
1185    # Prepare ${update_app}.  This always needs to be done in a temporary
1186    # location because dirpatcher won't write to a directory that already
1187    # exists, and ${installed_app} needs to be used as input to dirpatcher
1188    # in any event.  The new application will be rsynced into place once
1189    # dirpatcher creates it.
1190    ensure_temp_dir
1191    update_app="${g_temp_dir}/${APP_DIR}"
1192    note "update_app = ${update_app}"
1193
1194    note "dirpatching app directory"
1195    if ! "${dirpatcher}" "${installed_app}" \
1196                         "${patch_app_dir}" \
1197                         "${update_app}"; then
1198      err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
1199      mark_failed_patch_update "${product_id}" \
1200                               "${want_full_installer_path}" \
1201                               "${old_ks_plist}" \
1202                               "${old_version_app}" \
1203                               "${system_ticket}"
1204      exit 13
1205    fi
1206  fi
1207
1208  # See if the timestamp of what's currently on disk is newer than the
1209  # update's outer .app's timestamp.  rsync will copy the update's timestamp
1210  # over, but if that timestamp isn't as recent as what's already on disk, the
1211  # .app will need to be touched.
1212  local needs_touch=
1213  if [[ "${installed_app}" -nt "${update_app}" ]]; then
1214    needs_touch="y"
1215  fi
1216  note "needs_touch = ${needs_touch}"
1217
1218  # Copy the unversioned files into place, leaving everything in
1219  # ${installed_versions_dir} alone.  If this step is interrupted, the
1220  # application will at least remain in a usable state, although it may not
1221  # pass signature validation.  Depending on when this step is interrupted,
1222  # the application will either launch the old or the new version.  The
1223  # critical point is when the main executable is replaced.  There isn't very
1224  # much to copy in this step, because most of the application is in the
1225  # versioned directory.  This step only accounts for around 50 files, most of
1226  # which are small localized InfoPlist.strings files.  Note that
1227  # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
1228  # contents are excluded, having already been installed above.
1229  note "rsyncing app directory"
1230  if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
1231       "${update_app}/" "${installed_app}"; then
1232    err "rsync of app directory failed, status ${PIPESTATUS[0]}"
1233    exit 8
1234  fi
1235
1236  note "rsyncs complete"
1237
1238  if [[ -n "${is_patch}" ]]; then
1239    # update_app has been rsynced into place and is no longer needed.
1240    rm -rf "${update_app}" 2> /dev/null || true
1241    update_app=
1242    note "update_app = ${update_app}"
1243  fi
1244
1245  if [[ -n "${g_temp_dir}" ]]; then
1246    # The temporary directory, if any, is no longer needed.
1247    rm -rf "${g_temp_dir}" 2> /dev/null || true
1248    g_temp_dir=
1249    note "g_temp_dir = ${g_temp_dir}"
1250  fi
1251
1252  # Clean up any old .want_full_installer files from previous dirpatcher
1253  # failures. This is not considered a critical step, because this file
1254  # normally does not exist at all.
1255  rm -f "${want_full_installer_path}" || true
1256
1257  # If necessary, touch the outermost .app so that it appears to the outside
1258  # world that something was done to the bundle.  This will cause
1259  # LaunchServices to invalidate the information it has cached about the
1260  # bundle even if lsregister does not run.  This is not done if rsync already
1261  # updated the timestamp to something newer than what had been on disk.  This
1262  # is not considered a critical step, and if it fails, this script will not
1263  # exit.
1264  if [[ -n "${needs_touch}" ]]; then
1265    touch -cf "${installed_app}" || true
1266  fi
1267
1268  # Read the new values, such as the version.
1269  note "reading new values"
1270
1271  local new_version_app
1272  if ! new_version_app="$(infoplist_read "${installed_app_plist}" \
1273                                         "${APP_VERSION_KEY}")" ||
1274     [[ -z "${new_version_app}" ]]; then
1275    err "couldn't determine new_version_app"
1276    exit 9
1277  fi
1278  note "new_version_app = ${new_version_app}"
1279
1280  local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
1281  note "new_versioned_dir = ${new_versioned_dir}"
1282
1283  local new_ks_plist="${installed_app_plist}"
1284  note "new_ks_plist = ${new_ks_plist}"
1285
1286  local new_version_ks
1287  if ! new_version_ks="$(infoplist_read "${new_ks_plist}" \
1288                                        "${KS_VERSION_KEY}")" ||
1289     [[ -z "${new_version_ks}" ]]; then
1290    err "couldn't determine new_version_ks"
1291    exit 9
1292  fi
1293  note "new_version_ks = ${new_version_ks}"
1294
1295  local update_url
1296  if ! update_url="$(infoplist_read "${new_ks_plist}" "${KS_URL_KEY}")" ||
1297     [[ -z "${update_url}" ]]; then
1298    err "couldn't determine update_url"
1299    exit 9
1300  fi
1301  note "update_url = ${update_url}"
1302
1303  # The channel ID is optional.  Suppress stderr to prevent Keystone from
1304  # seeing possible error output.
1305  local channel
1306  channel="$(infoplist_read "${new_ks_plist}" \
1307                            "${KS_CHANNEL_KEY}" 2> /dev/null || true)"
1308  note "channel = ${channel}"
1309
1310  local tag="${channel}"
1311  local tag_key="${KS_CHANNEL_KEY}"
1312  if has_32_bit_only_cpu; then
1313    tag="${tag}-32bit"
1314    tag_key="${tag_key}-32bit"
1315  fi
1316  note "tag = ${tag}"
1317  note "tag_key = ${tag_key}"
1318
1319  # Make sure that the update was successful by comparing the version found in
1320  # the update with the version now on disk.
1321  if [[ "${new_version_ks}" != "${update_version_ks}" ]]; then
1322    err "new_version_ks and update_version_ks do not match"
1323    exit 10
1324  fi
1325
1326  # Notify LaunchServices.  This is not considered a critical step, and
1327  # lsregister's exit codes shouldn't be confused with this script's own.
1328  # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
1329  # throttling disk i/o" messages that lsregister might print.
1330  note "notifying LaunchServices"
1331  local coreservices="/System/Library/Frameworks/CoreServices.framework"
1332  local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
1333  local lsregister="${launchservices}/Support/lsregister"
1334  note "coreservices = ${coreservices}"
1335  note "launchservices = ${launchservices}"
1336  note "lsregister = ${lsregister}"
1337  "${lsregister}" -f "${installed_app}" > /dev/null || true
1338
1339  # The brand information is stored differently depending on whether this is
1340  # running for a system or user ticket.
1341  note "handling brand code"
1342
1343  local set_brand_file_access=
1344  local brand_plist
1345  if [[ -n "${system_ticket}" ]]; then
1346    # System ticket.
1347    set_brand_file_access="y"
1348    brand_plist="/${UNROOTED_BRAND_PLIST}"
1349  else
1350    # User ticket.
1351    brand_plist=~/"${UNROOTED_BRAND_PLIST}"
1352  fi
1353  local brand_plist_path="${brand_plist}.plist"
1354  note "set_brand_file_access = ${set_brand_file_access}"
1355  note "brand_plist = ${brand_plist}"
1356  note "brand_plist_path = ${brand_plist_path}"
1357
1358  local ksadmin_brand_plist_path
1359  local ksadmin_brand_key
1360
1361  # Only the stable channel, identified by an empty channel string, has a
1362  # brand code. On the beta and dev channels, remove the brand plist if
1363  # present. Its presence means that the ticket used to manage a
1364  # stable-channel Chrome but the user has since replaced it with a beta or
1365  # dev channel version. Since the canary channel can run side-by-side with
1366  # another Chrome installation, don't remove the brand plist on that channel,
1367  # but skip the rest of the brand logic.
1368  if [[ "${channel}" = "beta" ]] || [[ "${channel}" = "dev" ]]; then
1369    note "defeating brand code on channel ${channel}"
1370    rm -f "${brand_plist_path}" 2>/dev/null || true
1371  elif [[ -n "${channel}" ]]; then
1372    # Canary channel.
1373    note "skipping brand code on channel ${channel}"
1374  else
1375    # Stable channel.
1376    # If the user manually updated their copy of Chrome, there might be new
1377    # brand information in the app bundle, and that needs to be copied out
1378    # into the file Keystone looks at.
1379    if [[ -n "${old_brand}" ]]; then
1380      local brand_dir
1381      brand_dir="$(dirname "${brand_plist_path}")"
1382      note "brand_dir = ${brand_dir}"
1383      if ! mkdir -p "${brand_dir}"; then
1384        err "couldn't mkdir brand_dir, continuing"
1385      else
1386        if ! defaults write "${brand_plist}" "${KS_BRAND_KEY}" \
1387                            -string "${old_brand}"; then
1388          err "couldn't write brand_plist, continuing"
1389        elif [[ -n "${set_brand_file_access}" ]]; then
1390          if ! chown "root:wheel" "${brand_plist_path}"; then
1391            err "couldn't chown brand_plist_path, continuing"
1392          else
1393            if ! chmod 644 "${brand_plist_path}"; then
1394              err "couldn't chmod brand_plist_path, continuing"
1395            fi
1396          fi
1397        fi
1398      fi
1399    fi
1400
1401    # Confirm that the brand file exists.  It's optional.
1402    ksadmin_brand_plist_path="${brand_plist_path}"
1403    ksadmin_brand_key="${KS_BRAND_KEY}"
1404
1405    if [[ ! -f "${ksadmin_brand_plist_path}" ]]; then
1406      # Clear any branding information.
1407      ksadmin_brand_plist_path=
1408      ksadmin_brand_key=
1409    fi
1410  fi
1411
1412  note "ksadmin_brand_plist_path = ${ksadmin_brand_plist_path}"
1413  note "ksadmin_brand_key = ${ksadmin_brand_key}"
1414
1415  note "notifying Keystone"
1416
1417  local ksadmin_args=(
1418    --register
1419    --productid "${product_id}"
1420    --version "${new_version_ks}"
1421    --xcpath "${installed_app}"
1422    --url "${update_url}"
1423  )
1424
1425  if ksadmin_supports_tag; then
1426    ksadmin_args+=(
1427      --tag "${tag}"
1428    )
1429  fi
1430
1431  if ksadmin_supports_tagpath_tagkey; then
1432    ksadmin_args+=(
1433      --tag-path "${installed_app_plist_path}"
1434      --tag-key "${tag_key}"
1435    )
1436  fi
1437
1438  if ksadmin_supports_brandpath_brandkey; then
1439    ksadmin_args+=(
1440      --brand-path "${ksadmin_brand_plist_path}"
1441      --brand-key "${ksadmin_brand_key}"
1442    )
1443  fi
1444
1445  if ksadmin_supports_versionpath_versionkey; then
1446    ksadmin_args+=(
1447      --version-path "${installed_app_plist_path}"
1448      --version-key "${KS_VERSION_KEY}"
1449    )
1450  fi
1451
1452  note "ksadmin_args = ${ksadmin_args[*]}"
1453
1454  if ! ksadmin "${ksadmin_args[@]}"; then
1455    err "ksadmin failed"
1456    exit 11
1457  fi
1458
1459  # The remaining steps are not considered critical.
1460  set +e
1461
1462  # Try to clean up old versions that are not in use.  The strategy is to keep
1463  # the versioned directory corresponding to the update just applied
1464  # (obviously) and the version that was just replaced, and to use ps and lsof
1465  # to see if it looks like any processes are currently using any other old
1466  # directories.  Directories not in use are removed.  Old versioned
1467  # directories that are in use are left alone so as to not interfere with
1468  # running processes.  These directories can be cleaned up by this script on
1469  # future updates.
1470  #
1471  # To determine which directories are in use, both ps and lsof are used.
1472  # Each approach has limitations.
1473  #
1474  # The ps check looks for processes within the versioned directory.  Only
1475  # helper processes, such as renderers, are within the versioned directory.
1476  # Browser processes are not, so the ps check will not find them, and will
1477  # assume that a versioned directory is not in use if a browser is open
1478  # without any windows.  The ps mechanism can also only detect processes
1479  # running on the system that is performing the update.  If network shares
1480  # are involved, all bets are off.
1481  #
1482  # The lsof check looks to see what processes have the framework dylib open.
1483  # Browser processes will have their versioned framework dylib open, so this
1484  # check is able to catch browsers even if there are no associated helper
1485  # processes.  Like the ps check, the lsof check is limited to processes on
1486  # the system that is performing the update.  Finally, unless running as
1487  # root, the lsof check can only find processes running as the effective user
1488  # performing the update.
1489  #
1490  # These limitations are motivations to additionally preserve the versioned
1491  # directory corresponding to the version that was just replaced.
1492  note "cleaning up old versioned directories"
1493
1494  local versioned_dir
1495  for versioned_dir in "${installed_versions_dir}/"*; do
1496    note "versioned_dir = ${versioned_dir}"
1497    if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] || \
1498       [[ "${versioned_dir}" = "${old_versioned_dir}" ]]; then
1499      # This is the versioned directory corresponding to the update that was
1500      # just applied or the version that was previously in use.  Leave it
1501      # alone.
1502      note "versioned_dir is new_versioned_dir or old_versioned_dir, skipping"
1503      continue
1504    fi
1505
1506    # Look for any processes whose executables are within this versioned
1507    # directory.  They'll be helper processes, such as renderers.  Their
1508    # existence indicates that this versioned directory is currently in use.
1509    local ps_string="${versioned_dir}/"
1510    note "ps_string = ${ps_string}"
1511
1512    # Look for any processes using the framework dylib.  This will catch
1513    # browser processes where the ps check will not, but it is limited to
1514    # processes running as the effective user.
1515    local lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
1516    note "lsof_file = ${lsof_file}"
1517
1518    # ps -e displays all users' processes, -ww causes ps to not truncate
1519    # lines, -o comm instructs it to only print the command name, and the =
1520    # tells it to not print a header line.
1521    # The cut invocation filters the ps output to only have at most the number
1522    # of characters in ${ps_string}.  This is done so that grep can look for
1523    # an exact match.
1524    # grep -F tells grep to look for lines that are exact matches (not regular
1525    # expressions), -q tells it to not print any output and just indicate
1526    # matches by exit status, and -x tells it that the entire line must match
1527    # ${ps_string} exactly, as opposed to matching a substring.  A match
1528    # causes grep to exit zero (true).
1529    #
1530    # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
1531    # process.  If the file exists and is open, it will exit zero (true).
1532    if (! ps -ewwo comm= | \
1533          cut -c "1-${#ps_string}" | \
1534          grep -Fqx "${ps_string}") &&
1535       (! lsof "${lsof_file}" >& /dev/null); then
1536      # It doesn't look like anything is using this versioned directory.  Get
1537      # rid of it.
1538      note "versioned_dir doesn't appear to be in use, removing"
1539      rm -rf "${versioned_dir}"
1540    else
1541      note "versioned_dir is in use, skipping"
1542    fi
1543  done
1544
1545  # If this script is being driven by a user Keystone ticket, it is not
1546  # running as root.  If the application is installed somewhere under
1547  # /Applications, try to make it writable by all admin users.  This will
1548  # allow other admin users to update the application from their own user
1549  # Keystone instances.
1550  #
1551  # If the script is being driven by a user Keystone ticket (not running as
1552  # root) and the application is not installed under /Applications, it might
1553  # not be in a system-wide location, and it probably won't be something that
1554  # other users on the system are running, so err on the side of safety and
1555  # don't make it group-writable.
1556  #
1557  # If this script is being driven by a system ticket (running as root), it's
1558  # future updates can be expected to be applied the same way, so admin-
1559  # writability is not a concern.  Set the entire thing to be owned by root
1560  # in that case, regardless of where it's installed, and drop any group and
1561  # other write permission.
1562  #
1563  # If this script is running as a user that is not a member of the admin
1564  # group, the chgrp operation will not succeed.  Tolerate that case, because
1565  # it's better than the alternative, which is to make the application
1566  # world-writable.
1567  note "setting permissions"
1568
1569  local chmod_mode="a+rX,u+w,go-w"
1570  if [[ -z "${system_ticket}" ]]; then
1571    if [[ "${installed_app:0:14}" = "/Applications/" ]] &&
1572       chgrp -Rh admin "${installed_app}" 2> /dev/null; then
1573      chmod_mode="a+rX,ug+w,o-w"
1574    fi
1575  else
1576    chown -Rh root:wheel "${installed_app}" 2> /dev/null
1577  fi
1578
1579  note "chmod_mode = ${chmod_mode}"
1580  chmod -R "${chmod_mode}" "${installed_app}" 2> /dev/null
1581
1582  # On the Mac, or at least on HFS+, symbolic link permissions are significant,
1583  # but chmod -R and -h can't be used together.  Do another pass to fix the
1584  # permissions on any symbolic links.
1585  find "${installed_app}" -type l -exec chmod -h "${chmod_mode}" {} + \
1586      2> /dev/null
1587
1588  # If an update is triggered from within the application itself, the update
1589  # process inherits the quarantine bit (LSFileQuarantineEnabled).  Any files
1590  # or directories created during the update will be quarantined in that case,
1591  # which may cause Launch Services to display quarantine UI.  That's bad,
1592  # especially if it happens when the outer .app launches a quarantined inner
1593  # helper.  If the application is already on the system and is being updated,
1594  # then it can be assumed that it should not be quarantined.  Use xattr to
1595  # drop the quarantine attribute.
1596  #
1597  # TODO(mark): Instead of letting the quarantine attribute be set and then
1598  # dropping it here, figure out a way to get the update process to run
1599  # without LSFileQuarantineEnabled even when triggering an update from within
1600  # the application.
1601  note "lifting quarantine"
1602
1603  if os_xattr_supports_r; then
1604    # On 10.6, xattr supports -r for recursive operation.
1605    xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
1606  else
1607    # On earlier systems, xattr doesn't support -r, so run xattr via find.
1608    find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \
1609        2> /dev/null
1610  fi
1611
1612  # Great success!
1613  note "done!"
1614
1615  trap - EXIT
1616
1617  return 0
1618}
1619
1620# Check "less than" instead of "not equal to" in case Keystone ever changes to
1621# pass more arguments.
1622if [[ ${#} -lt 1 ]]; then
1623  usage
1624  exit 2
1625fi
1626
1627main "${@}"
1628exit ${?}
1629