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