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