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