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