Home | History | Annotate | Download | only in mac
      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 
     41 set -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.
     48 export 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.
     56 unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
     57 export -n SHELLOPTS
     58 
     59 set -o pipefail
     60 shopt -s nullglob
     61 
     62 ME="$(basename "${0}")"
     63 readonly ME
     64 
     65 readonly 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:=}
     70 err() {
     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 
     81 note() {
     82   local message="${1}"
     83 
     84   if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
     85     err "${message}"
     86   fi
     87 }
     88 
     89 g_temp_dir=
     90 cleanup() {
     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 
    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]}"
    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.
    128 is_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).
    182 ensure_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.
    257 ensure_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.
    290 is_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.
    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}"
    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".
    365 is_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.
    375 os_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.
    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}"
    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".
    407 is_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.
    417 ksadmin_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.
    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
    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.
    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
    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.
    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
    452 
    453   # The return value of is_ksadmin_version_ge is used as this function's
    454   # return value.
    455 }
    456 
    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 ]]
    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.
    494 infoplist_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.
    515 mark_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.
    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}"
    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 
    691 usage() {
    692   echo "usage: ${ME} update_dmg_mount_point" >& 2
    693 }
    694 
    695 main() {
    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.
   1622 if [[ ${#} -lt 1 ]]; then
   1623   usage
   1624   exit 2
   1625 fi
   1626 
   1627 main "${@}"
   1628 exit ${?}
   1629