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