Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: chrome/installer/mac/keystone_install.sh

Issue 2832005: Binary differential (delta) updates: keystone_install dmgpatcher support (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/bin/bash -p 1 #!/bin/bash -p
2 2
3 # Copyright (c) 2009 The Chromium Authors. All rights reserved. 3 # Copyright (c) 2009 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 # usage: keystone_install.sh update_dmg_mount_point 7 # usage: keystone_install.sh update_dmg_mount_point
8 # 8 #
9 # Called by the Keystone system to update the installed application with a new 9 # Called by the Keystone system to update the installed application with a new
10 # version from a disk image. 10 # version from a disk image.
11 # 11 #
12 # Environment variables: 12 # Environment variables:
13 # GOOGLE_CHROME_UPDATER_DEBUG 13 # GOOGLE_CHROME_UPDATER_DEBUG
14 # When set to a non-empty value, additional information about this script's 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 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 16 # also be enabled when "Library/Google/Google Chrome Updater Debug" in the
17 # root directory or in ${HOME} exists. 17 # root directory or in ${HOME} exists.
18 # 18 #
19 # Exit codes: 19 # Exit codes:
20 # 0 Happiness 20 # 0 Happiness
21 # 1 Unknown failure 21 # 1 Unknown failure
22 # 2 Basic sanity check source failure (e.g. no app on disk image) 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) 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 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 25 # 5 Could not prepare existing installed version to receive update
26 # 6 rsync failed (could not assure presence of Versions directory) 26 # 6 Patch sanity check failure
27 # 7 rsync failed (could not copy new versioned directory to Versions) 27 # 7 rsync failed (could not copy new versioned directory to Versions)
28 # 8 rsync failed (could not update outer .app bundle) 28 # 8 rsync failed (could not update outer .app bundle)
29 # 9 Could not get the version, update URL, or channel after update 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 30 # 10 Updated application does not have the version number from the update
31 # 11 ksadmin failure 31 # 11 ksadmin failure
32 # 12 dirpatcher failed for versioned directory
33 # 13 dirpatcher failed for outer .app bundle
34 #
35 # The following exit codes are not used by this script, but can be used to
36 # convey special meaning to Keystone:
37 # 66 (unused) success, request reboot
38 # 77 (unused) try installation again later
32 39
33 set -eu 40 set -eu
34 41
35 # http://b/2290916: Keystone runs the installation with a restrictive PATH 42 # http://b/2290916: Keystone runs the installation with a restrictive PATH
36 # that only includes the directory containing ksadmin, /bin, and /usr/bin. It 43 # that only includes the directory containing ksadmin, /bin, and /usr/bin. It
37 # does not include /sbin or /usr/sbin. This script uses lsof, which is in 44 # does not include /sbin or /usr/sbin. This script uses lsof, which is in
38 # /usr/sbin, and it's conceivable that it might want to use other tools in an 45 # /usr/sbin, and it's conceivable that it might want to use other tools in an
39 # sbin directory. Adjust the path accordingly. 46 # sbin directory. Adjust the path accordingly.
40 export PATH="${PATH}:/sbin:/usr/sbin" 47 export PATH="${PATH}:/sbin:/usr/sbin"
41 48
(...skipping 25 matching lines...) Expand all
67 } 74 }
68 75
69 note() { 76 note() {
70 local message="${1}" 77 local message="${1}"
71 78
72 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then 79 if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
73 err "${message}" 80 err "${message}"
74 fi 81 fi
75 } 82 }
76 83
84 declare g_temp_dir
85 cleanup() {
86 local status=${?}
87
88 trap - EXIT
89 trap '' HUP INT QUIT TERM
90
91 if [[ ${status} -ge 128 ]]; then
92 err "Caught signal $((${status} - 128))"
93 fi
94
95 if [[ -n "${g_temp_dir}" ]]; then
96 rm -rf "${g_temp_dir}"
97 fi
98
99 exit ${status}
100 }
101
102 ensure_temp_dir() {
103 if [[ -z "${g_temp_dir}" ]]; then
104 # Choose a template that won't be a dot directory. Make it safe by
105 # removing leading hyphens, too.
106 local template="${ME}"
107 if [[ "${template}" =~ ^[-.]+(.*)$ ]]; then
108 template="${BASH_REMATCH[1]}"
109 fi
110 if [[ -z "${template}" ]]; then
111 template="keystone_install"
112 fi
113
114 g_temp_dir="$(mktemp -d -t "${template}")"
115 note "g_temp_dir = ${g_temp_dir}"
116 fi
117 }
118
77 # Returns 0 (true) if |symlink| exists, is a symbolic link, and appears 119 # Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
78 # writable on the basis of its POSIX permissions. This is used to determine 120 # writable on the basis of its POSIX permissions. This is used to determine
79 # writability like test's -w primary, but -w resolves symbolic links and this 121 # writability like test's -w primary, but -w resolves symbolic links and this
80 # function does not. 122 # function does not.
81 is_writable_symlink() { 123 is_writable_symlink() {
82 local symlink="${1}" 124 local symlink="${1}"
83 125
84 local link_mode 126 local link_mode
85 link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)" 127 link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
86 if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then 128 if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
177 219
178 (ln -fhs "${target}" "${temp_link}" && \ 220 (ln -fhs "${target}" "${temp_link}" && \
179 chmod -h 755 "${temp_link}" && \ 221 chmod -h 755 "${temp_link}" && \
180 mv -f "${temp_link}" "${symlink_dir}/") || true 222 mv -f "${temp_link}" "${symlink_dir}/") || true
181 rm -rf "${temp_link_dir}" 223 rm -rf "${temp_link_dir}"
182 fi 224 fi
183 225
184 return 0 226 return 0
185 } 227 }
186 228
229 # ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
230 # symbolic link in |directory|, recursivley.
231 #
232 # In some very weird and rare cases, it is possible to wind up with a user
233 # installation that contains symbolic links that the user does not have write
234 # permission over. More on how that might happen later.
235 #
236 # If a weird and rare case like this is observed, rsync will exit with an
237 # error when attempting to update the times on these symbolic links. rsync
238 # may not be intelligent enough to try creating a new symbolic link in these
239 # cases, but this script can be.
240 #
241 # The problem occurs when an administrative user first drag-installs the
242 # application to /Applications, resulting in the program's user being set to
243 # the user's own ID. If, subsequently, a .pkg package is installed over that,
244 # the existing directory ownership will be preserved, but file ownership will
245 # be changed to whateer is specified by the package, typically root. This
246 # applies to symbolic links as well. On a subsequent update, rsync will be
247 # able to copy the new files into place, because the user still has permission
248 # to write to the directories. If the symbolic link targets are not changing,
249 # though, rsync will not replace them, and they will remain owned by root.
250 # The user will not have permission to update the time on the symbolic links,
251 # resulting in an rsync error.
252 ensure_writable_symlinks_recursive() {
253 local directory="${1}"
254
255 # This fix-up is not necessary when running as root, because root will
256 # always be able to write everything needed.
257 if [[ ${EUID} -eq 0 ]]; then
258 return 0
259 fi
260
261 # This step isn't critical.
262 local set_e=
263 if [[ "${-}" =~ e ]]; then
264 set_e="y"
265 set +e
266 fi
267
268 # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
269 local symlink
270 while IFS= read -r -d $'\0' symlink; do
271 ensure_writable_symlink "${symlink}"
272 done < <(find "${directory}" -type l -print0)
273
274 # Go back to how things were.
275 if [[ -n "${set_e}" ]]; then
276 set -e
277 fi
278 }
279
187 # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to 280 # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
188 # stdout. This function operates with "static" variables: it will only check 281 # stdout. This function operates with "static" variables: it will only check
189 # the ksadmin version once per script run. If ksadmin is old enough to not 282 # the ksadmin version once per script run. If ksadmin is old enough to not
190 # support --ksadmin-version, or another error occurs, this function prints an 283 # support --ksadmin-version, or another error occurs, this function prints an
191 # empty string. 284 # empty string.
192 g_checked_ksadmin_version= 285 g_checked_ksadmin_version=
193 g_ksadmin_version= 286 g_ksadmin_version=
194 ksadmin_version() { 287 ksadmin_version() {
195 if [[ -z "${g_checked_ksadmin_version}" ]]; then 288 if [[ -z "${g_checked_ksadmin_version}" ]]; then
196 g_checked_ksadmin_version="y" 289 g_checked_ksadmin_version="y"
197 g_ksadmin_version="$(ksadmin --ksadmin-version || true)" 290 g_ksadmin_version="$(ksadmin --ksadmin-version || true)"
198 fi 291 fi
199 echo "${g_ksadmin_version}" 292 echo "${g_ksadmin_version}"
200 return 0 293 return 0
201 } 294 }
202 295
203 # Compares the installed ksadmin version against a supplied version number, 296 # Compares the installed ksadmin version against a supplied version number,
204 # |check_version|, and returns 0 (true) if the installed Keystone version is 297 # |check_version|, and returns 0 (true) if the installed Keystone version is
205 # greater than or equal to |check_version| according to a piece-wise 298 # greater than or equal to |check_version| according to a piece-wise
206 # comparison. Returns 1 (false) if the installed Keystone version number 299 # comparison. Returns 1 (false) if the installed Keystone version number
207 # cannot be determined or if |check_version| is greater than the installed 300 # cannot be determined or if |check_version| is greater than the installed
208 # Keystone version. |check_version| should be a string of the form 301 # Keystone version. |check_version| should be a string of the form
209 # "major.minor.micro.build". Returns 1 (false) if either |check_version| or 302 # "major.minor.micro.build". Returns 1 (false) if either |check_version| or
210 # the Keystone version do not match this format. 303 # the Keystone version do not match this format.
211 readonly VER_RE="^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\$" 304 readonly KSADMIN_VERSION_RE="^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\$"
212 is_ksadmin_version_ge() { 305 is_ksadmin_version_ge() {
213 local check_version="${1}" 306 local check_version="${1}"
214 307
215 if ! [[ "${check_version}" =~ ${VER_RE} ]]; then 308 if ! [[ "${check_version}" =~ ${KSADMIN_VERSION_RE} ]]; then
216 return 1 309 return 1
217 fi 310 fi
218 311
219 local check_components=("${BASH_REMATCH[1]}" 312 local check_components=("${BASH_REMATCH[1]}"
220 "${BASH_REMATCH[2]}" 313 "${BASH_REMATCH[2]}"
221 "${BASH_REMATCH[3]}" 314 "${BASH_REMATCH[3]}"
222 "${BASH_REMATCH[4]}") 315 "${BASH_REMATCH[4]}")
223 316
224 local ksadmin_version 317 local ksadmin_version
225 ksadmin_version="$(ksadmin_version)" 318 ksadmin_version="$(ksadmin_version)"
226 319
227 if ! [[ "${ksadmin_version}" =~ ${VER_RE} ]]; then 320 if ! [[ "${ksadmin_version}" =~ ${KSADMIN_VERSION_RE} ]]; then
228 return 1 321 return 1
229 fi 322 fi
230 323
231 local ksadmin_components=("${BASH_REMATCH[1]}" 324 local ksadmin_components=("${BASH_REMATCH[1]}"
232 "${BASH_REMATCH[2]}" 325 "${BASH_REMATCH[2]}"
233 "${BASH_REMATCH[3]}" 326 "${BASH_REMATCH[3]}"
234 "${BASH_REMATCH[4]}") 327 "${BASH_REMATCH[4]}")
235 328
236 local i 329 local i
237 for i in 0 1 2 3; do 330 for i in 0 1 2 3; do
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
287 usage() { 380 usage() {
288 echo "usage: ${ME} update_dmg_mount_point" >& 2 381 echo "usage: ${ME} update_dmg_mount_point" >& 2
289 } 382 }
290 383
291 main() { 384 main() {
292 local update_dmg_mount_point="${1}" 385 local update_dmg_mount_point="${1}"
293 386
294 # Early steps are critical. Don't continue past any failure. 387 # Early steps are critical. Don't continue past any failure.
295 set -e 388 set -e
296 389
390 trap cleanup EXIT HUP INT QUIT TERM
391
297 readonly PRODUCT_NAME="Google Chrome" 392 readonly PRODUCT_NAME="Google Chrome"
298 readonly APP_DIR="${PRODUCT_NAME}.app" 393 readonly APP_DIR="${PRODUCT_NAME}.app"
299 readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework" 394 readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
300 readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework" 395 readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
396 readonly PATCH_DIR=".patch"
301 readonly CONTENTS_DIR="Contents" 397 readonly CONTENTS_DIR="Contents"
302 readonly APP_PLIST="${CONTENTS_DIR}/Info" 398 readonly APP_PLIST="${CONTENTS_DIR}/Info"
303 readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions" 399 readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions"
304 readonly UNROOTED_BRAND_PLIST="Library/Google/Google Chrome Brand" 400 readonly UNROOTED_BRAND_PLIST="Library/Google/Google Chrome Brand"
305 readonly UNROOTED_DEBUG_FILE="Library/Google/Google Chrome Updater Debug" 401 readonly UNROOTED_DEBUG_FILE="Library/Google/Google Chrome Updater Debug"
306 402
307 readonly APP_VERSION_KEY="CFBundleShortVersionString" 403 readonly APP_VERSION_KEY="CFBundleShortVersionString"
308 readonly KS_VERSION_KEY="KSVersion" 404 readonly KS_VERSION_KEY="KSVersion"
309 readonly KS_PRODUCT_KEY="KSProductID" 405 readonly KS_PRODUCT_KEY="KSProductID"
310 readonly KS_URL_KEY="KSUpdateURL" 406 readonly KS_URL_KEY="KSUpdateURL"
(...skipping 30 matching lines...) Expand all
341 note "checking update" 437 note "checking update"
342 438
343 if [[ -z "${update_dmg_mount_point}" ]] || 439 if [[ -z "${update_dmg_mount_point}" ]] ||
344 [[ "${update_dmg_mount_point:0:1}" != "/" ]] || 440 [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
345 ! [[ -d "${update_dmg_mount_point}" ]]; then 441 ! [[ -d "${update_dmg_mount_point}" ]]; then
346 err "update_dmg_mount_point must be an absolute path to a directory" 442 err "update_dmg_mount_point must be an absolute path to a directory"
347 usage 443 usage
348 exit 2 444 exit 2
349 fi 445 fi
350 446
351 # The update to install. 447 local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
352 local update_app="${update_dmg_mount_point}/${APP_DIR}" 448 if [[ "${patch_dir:0:1}" != "/" ]]; then
353 note "update_app = ${update_app}" 449 note "patch_dir = ${patch_dir}"
354 450 err "patch_dir must be an absolute path"
355 # Make sure that there's something to copy from, and that it's an absolute
356 # path.
357 if [[ "${update_app:0:1}" != "/" ]] ||
358 ! [[ -d "${update_app}" ]]; then
359 err "update_app must be an absolute path to a directory"
360 exit 2 451 exit 2
361 fi 452 fi
362 453
363 # Get some information about the update. 454 # Figure out if this is an ordinary installation disk image being used as a
364 note "reading update values" 455 # full update, or a patch. A patch will have a .patch directory at the root
456 # of the disk image containing information about the update, tools to apply
457 # it, and the update contents.
458 local is_patch=
459 local dirpatcher=
460 if [[ -d "${patch_dir}" ]]; then
461 # patch_dir exists and is a directory - this is a patch update.
462 is_patch="y"
463 dirpatcher="${patch_dir}/dirpatcher.sh"
464 if ! [[ -x "${dirpatcher}" ]]; then
465 err "couldn't locate dirpatcher"
466 exit 6
467 fi
468 elif [[ -e "${patch_dir}" ]]; then
469 # patch_dir exists, but is not a directory - what's that mean?
470 note "patch_dir = ${patch_dir}"
471 err "patch_dir must be a directory"
472 exit 2
473 else
474 # patch_dir does not exist - this is a full "installer."
475 patch_dir=
476 fi
477 note "patch_dir = ${patch_dir}"
478 note "is_patch = ${is_patch}"
479 note "dirpatcher = ${dirpatcher}"
365 480
366 local update_app_plist="${update_app}/${APP_PLIST}" 481 # The update to install.
367 note "update_app_plist = ${update_app_plist}" 482
368 local update_version_app 483 # update_app is the path to the new version of the .app. It will only be
369 if ! update_version_app="$(defaults read "${update_app_plist}" \ 484 # set at this point for a non-patch update. It is not yet set for a patch
370 "${APP_VERSION_KEY}")" || 485 # update because no such directory exists yet; it will be set later when
371 [[ -z "${update_version_app}" ]]; then 486 # dirpatcher creates it.
372 err "couldn't determine update_version_app" 487 local update_app=
373 exit 2 488
489 # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
490 # be set for patch updates.
491 local update_version_app_old=
492 local patch_app_dir=
493 local patch_versioned_dir=
494
495 local update_version_app update_version_ks product_id
496 if [[ -z "${is_patch}" ]]; then
497 update_app="${update_dmg_mount_point}/${APP_DIR}"
498 note "update_app = ${update_app}"
499
500 # Make sure that there's something to copy from, and that it's an absolute
501 # path.
502 if [[ "${update_app:0:1}" != "/" ]] ||
503 ! [[ -d "${update_app}" ]]; then
504 err "update_app must be an absolute path to a directory"
505 exit 2
506 fi
507
508 # Get some information about the update.
509 note "reading update values"
510
511 local update_app_plist="${update_app}/${APP_PLIST}"
512 note "update_app_plist = ${update_app_plist}"
513 if ! update_version_app="$(defaults read "${update_app_plist}" \
514 "${APP_VERSION_KEY}")" ||
515 [[ -z "${update_version_app}" ]]; then
516 err "couldn't determine update_version_app"
517 exit 2
518 fi
519 note "update_version_app = ${update_version_app}"
520
521 local update_ks_plist="${update_app_plist}"
522 note "update_ks_plist = ${update_ks_plist}"
523 if ! update_version_ks="$(defaults read "${update_ks_plist}" \
524 "${KS_VERSION_KEY}")" ||
525 [[ -z "${update_version_ks}" ]]; then
526 err "couldn't determine update_version_ks"
527 exit 2
528 fi
529 note "update_version_ks = ${update_version_ks}"
530
531 if ! product_id="$(defaults read "${update_ks_plist}" \
532 "${KS_PRODUCT_KEY}")" ||
533 [[ -z "${product_id}" ]]; then
534 err "couldn't determine product_id"
535 exit 2
536 fi
537 note "product_id = ${product_id}"
538 else # [[ -n "${is_patch}" ]]
539 # Get some information about the update.
540 note "reading update values"
541
542 if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
543 [[ -z "${update_version_app_old}" ]]; then
544 err "couldn't determine update_version_app_old"
545 exit 2
546 fi
547 note "update_version_app_old = ${update_version_app_old}"
548
549 if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
550 [[ -z "${update_version_app}" ]]; then
551 err "couldn't determine update_version_app"
552 exit 2
553 fi
554 note "update_version_app = ${update_version_app}"
555
556 if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
557 [[ -z "${update_version_ks}" ]]; then
558 err "couldn't determine update_version_ks"
559 exit 2
560 fi
561 note "update_version_ks = ${update_version_ks}"
562
563 if ! product_id=$(<"${patch_dir}/ks_product") ||
564 [[ -z "${product_id}" ]]; then
565 err "couldn't determine product_id"
566 exit 2
567 fi
568 note "product_id = ${product_id}"
569
570 patch_app_dir="${patch_dir}/application.dirpatch"
571 if ! [[ -d "${patch_app_dir}" ]]; then
572 err "couldn't locate patch_app_dir"
573 exit 6
574 fi
575 note "patch_app_dir = ${patch_app_dir}"
576
577 patch_versioned_dir=\
578 "${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
579 if ! [[ -d "${patch_versioned_dir}" ]]; then
580 err "couldn't locate patch_versioned_dir"
581 exit 6
582 fi
583 note "patch_versioned_dir = ${patch_versioned_dir}"
374 fi 584 fi
375 note "update_version_app = ${update_version_app}"
376
377 local update_ks_plist="${update_app_plist}"
378 note "update_ks_plist = ${update_ks_plist}"
379 local update_version_ks
380 if ! update_version_ks="$(defaults read "${update_ks_plist}" \
381 "${KS_VERSION_KEY}")" ||
382 [[ -z "${update_version_ks}" ]]; then
383 err "couldn't determine update_version_ks"
384 exit 2
385 fi
386 note "update_version_ks = ${update_version_ks}"
387
388 local product_id
389 if ! product_id="$(defaults read "${update_ks_plist}" \
390 "${KS_PRODUCT_KEY}")" ||
391 [[ -z "${product_id}" ]]; then
392 err "couldn't determine product_id"
393 exit 2
394 fi
395 note "product_id = ${product_id}"
396 585
397 # ksadmin is required. Keystone should have set a ${PATH} that includes it. 586 # ksadmin is required. Keystone should have set a ${PATH} that includes it.
398 # Check that here, so that more useful feedback can be offered in the 587 # Check that here, so that more useful feedback can be offered in the
399 # unlikely event that ksadmin is missing. 588 # unlikely event that ksadmin is missing.
400 note "checking Keystone" 589 note "checking Keystone"
401 590
402 local ksadmin_path 591 local ksadmin_path
403 if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then 592 if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then
404 err "couldn't locate ksadmin_path" 593 err "couldn't locate ksadmin_path"
405 exit 3 594 exit 3
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
472 661
473 local installed_app_plist="${installed_app}/${APP_PLIST}" 662 local installed_app_plist="${installed_app}/${APP_PLIST}"
474 note "installed_app_plist = ${installed_app_plist}" 663 note "installed_app_plist = ${installed_app_plist}"
475 local installed_app_plist_path="${installed_app_plist}.plist" 664 local installed_app_plist_path="${installed_app_plist}.plist"
476 note "installed_app_plist_path = ${installed_app_plist_path}" 665 note "installed_app_plist_path = ${installed_app_plist_path}"
477 local old_version_app 666 local old_version_app
478 old_version_app="$(defaults read "${installed_app_plist}" \ 667 old_version_app="$(defaults read "${installed_app_plist}" \
479 "${APP_VERSION_KEY}" || true)" 668 "${APP_VERSION_KEY}" || true)"
480 note "old_version_app = ${old_version_app}" 669 note "old_version_app = ${old_version_app}"
481 670
671 # old_version_app is not required, because it won't be present in skeleton
672 # bootstrap installations, which just have an empty .app directory. Only
673 # require it when doing a patch update, and use it to validate that the
674 # patch applies to the old installed version. By definition, skeleton
675 # bootstraps can't be installed with patch udpates. They require the full
676 # application on the disk image.
677 if [[ -n "${is_patch}" ]]; then
678 if [[ -z "${old_version_app}" ]]; then
679 err "old_version_app required for patch"
680 exit 6
681 elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
682 err "this patch does not apply to the installed version"
683 exit 6
684 fi
685 fi
686
482 local installed_versions_dir="${installed_app}/${VERSIONS_DIR}" 687 local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
483 note "installed_versions_dir = ${installed_versions_dir}" 688 note "installed_versions_dir = ${installed_versions_dir}"
484 689
485 # If the installed application is incredibly old, old_versioned_dir may not 690 # If the installed application is incredibly old, old_versioned_dir may not
486 # exist. 691 # exist.
487 local old_versioned_dir 692 local old_versioned_dir
488 if [[ -n "${old_version_app}" ]]; then 693 if [[ -n "${old_version_app}" ]]; then
489 old_versioned_dir="${installed_versions_dir}/${old_version_app}" 694 old_versioned_dir="${installed_versions_dir}/${old_version_app}"
490 fi 695 fi
491 note "old_versioned_dir = ${old_versioned_dir}" 696 note "old_versioned_dir = ${old_versioned_dir}"
492 697
493 # Collect the installed application's brand code, it will be used later. It 698 # Collect the installed application's brand code, it will be used later. It
494 # is not an error for the installed application to not have a brand code. 699 # is not an error for the installed application to not have a brand code.
495 local old_ks_plist="${installed_app_plist}" 700 local old_ks_plist="${installed_app_plist}"
496 note "old_ks_plist = ${old_ks_plist}" 701 note "old_ks_plist = ${old_ks_plist}"
497 local old_brand 702 local old_brand
498 old_brand="$(defaults read "${old_ks_plist}" \ 703 old_brand="$(defaults read "${old_ks_plist}" \
499 "${KS_BRAND_KEY}" 2> /dev/null || 704 "${KS_BRAND_KEY}" 2> /dev/null ||
500 true)" 705 true)"
501 note "old_brand = ${old_brand}" 706 note "old_brand = ${old_brand}"
502 707
708 ensure_writable_symlinks_recursive "${installed_app}"
709
710 # By copying to ${installed_app}, the existing application name will be
711 # preserved, if the user has renamed the application on disk. Respecting
712 # the user's changes is friendly.
713
714 # Make sure that ${installed_versions_dir} exists, so that it can receive
715 # the versioned directory. It may not exist if updating from an older
716 # version that did not use the versioned layout on disk. Later, during the
717 # rsync to copy the applciation directory, the mode bits and timestamp on
718 # ${installed_versions_dir} will be set to conform to whatever is present in
719 # the update.
720 #
721 # ${installed_app} is guaranteed to exist at this point, but
722 # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
723 # if this update is actually an initial installation from a Keystone
724 # skeleton bootstrap. The mkdir creates ${installed_app}/${CONTENTS_DIR} if
725 # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
726 note "creating installed_versions_dir"
727 if ! mkdir -p "${installed_versions_dir}"; then
728 err "mkdir of installed_versions_dir failed"
729 exit 5
730 fi
731
732 local new_versioned_dir
733 new_versioned_dir="${installed_versions_dir}/${update_version_app}"
734 note "new_versioned_dir = ${new_versioned_dir}"
735
736 # If there's an entry at ${new_versioned_dir} but it's not a directory
737 # (or it's a symbolic link, whether or not it points to a directory), rsync
738 # won't get rid of it. It's never correct to have a non-directory in place
739 # of the versioned directory, so toss out whatever's there. Don't treat
740 # this as a critical step: if removal fails, operation can still proceed to
741 # to the dirpatcher or rsync, which will likely fail.
742 if [[ -e "${new_versioned_dir}" ]] &&
743 ([[ -L "${new_versioned_dir}" ]] ||
744 ! [[ -d "${new_versioned_dir}" ]]); then
745 note "removing non-directory in place of versioned directory"
746 rm -f "${new_versioned_dir}" 2> /dev/null || true
747 fi
748
749 local update_versioned_dir
750 if [[ -z "${is_patch}" ]]; then
751 update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
752 note "update_versioned_dir = ${update_versioned_dir}"
753 else # [[ -n "${is_patch}" ]]
754 # dirpatcher won't patch into a directory that already exists. Doing so
755 # would be a bad idea, anyway. If ${new_versioned_dir} already exists,
756 # it may be something left over from a previous failed or incomplete
757 # update attempt, or it may be the live versioned directory if this is a
758 # same-version update intended only to change channels. Since there's no
759 # way to tell, this case is handled by having dirpatcher produce the new
760 # versioned directory in a temporary location and then having rsync copy
761 # it into place as an ${update_versioned_dir}, the same as in a non-patch
762 # update. If ${new_versioned_dir} doesn't exist, dirpatcher can place the
763 # new versioned directory at that location directly.
764 local versioned_dir_target
765 if ! [[ -e "${new_versioned_dir}" ]]; then
766 versioned_dir_target="${new_versioned_dir}"
767 note "versioned_dir_target = ${versioned_dir_target}"
768 else
769 ensure_temp_dir
770 versioned_dir_target="${g_temp_dir}/${update_version_app}"
771 note "versioned_dir_target = ${versioned_dir_target}"
772 update_versioned_dir="${versioned_dir_target}"
773 note "update_versioned_dir = ${update_versioned_dir}"
774 fi
775
776 note "dirpatching versioned directory"
777 if ! "${dirpatcher}" "${old_versioned_dir}" \
778 "${patch_versioned_dir}" \
779 "${versioned_dir_target}"; then
780 err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
781 exit 12
782 fi
783 fi
784
785 # Copy the versioned directory. The new versioned directory should have a
786 # different name than any existing one, so this won't harm anything already
787 # present in ${installed_versions_dir}, including the versioned directory
788 # being used by any running processes. If this step is interrupted, there
789 # will be an incomplete versioned directory left behind, but it won't
790 # won't interfere with anything, and it will be replaced or removed during a
791 # future update attempt.
792 #
793 # In certain cases, same-version updates are distributed to move users
794 # between channels; when this happens, the contents of the versioned
795 # directories are identical and rsync will not render the versioned
796 # directory unusable even for an instant.
797 #
798 # ${update_versioned_dir} may be empty during a patch update (${is_patch})
799 # if the dirpatcher above was able to write it into place directly. In
800 # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
801 # place.
802 if [[ -n "${update_versioned_dir}" ]]; then
803 note "rsyncing versioned directory"
804 if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
805 "${new_versioned_dir}"; then
806 err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
807 exit 7
808 fi
809 fi
810
811 if [[ -n "${is_patch}" ]]; then
812 # If the versioned directory was prepared in a temporary directory and
813 # then rsynced into place, remove the temporary copy now that it's no
814 # longer needed.
815 if [[ -n "${update_versioned_dir}" ]]; then
816 rm -rf "${update_versioned_dir}" 2> /dev/null || true
817 update_versioned_dir=
818 note "update_versioned_dir = ${update_versioned_dir}"
819 fi
820
821 # Prepare ${update_app}. This always needs to be done in a temporary
822 # location because dirpatcher won't write to a directory that already
823 # exists, and ${installed_app} needs to be used as input to dirpatcher
824 # in any event. The new application will be rsynced into place once
825 # dirpatcher creates it.
826 ensure_temp_dir
827 update_app="${g_temp_dir}/${APP_DIR}"
828 note "update_app = ${update_app}"
829
830 note "dirpatching app directory"
831 if ! "${dirpatcher}" "${installed_app}" \
832 "${patch_app_dir}" \
833 "${update_app}"; then
834 err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
835 exit 13
836 fi
837 fi
838
503 # See if the timestamp of what's currently on disk is newer than the 839 # See if the timestamp of what's currently on disk is newer than the
504 # update's outer .app's timestamp. rsync will copy the update's timestamp 840 # update's outer .app's timestamp. rsync will copy the update's timestamp
505 # over, but if that timestamp isn't as recent as what's already on disk, the 841 # over, but if that timestamp isn't as recent as what's already on disk, the
506 # .app will need to be touched. 842 # .app will need to be touched.
507 local needs_touch= 843 local needs_touch=
508 if [[ "${installed_app}" -nt "${update_app}" ]]; then 844 if [[ "${installed_app}" -nt "${update_app}" ]]; then
509 needs_touch="y" 845 needs_touch="y"
510 fi 846 fi
511 note "needs_touch = ${needs_touch}" 847 note "needs_touch = ${needs_touch}"
512 848
513 # In some very weird and rare cases, it is possible to wind up with a user
514 # installation that contains symbolic links that the user does not have
515 # write permission over. More on how that might happen later.
516 #
517 # If a weird and rare case like this is observed, rsync will exit with an
518 # error when attempting to update the times on these symbolic links. rsync
519 # may not be intelligent enough to try creating a new symbolic link in these
520 # cases, but this script can be.
521 #
522 # This fix-up is not necessary when running as root, because root will
523 # always be able to write everything needed.
524 #
525 # The problem occurs when an administrative user first drag-installs the
526 # application to /Applications, resulting in the program's user being set to
527 # the user's own ID. If, subsequently, a .pkg package is installed over
528 # that, the existing directory ownership will be preserved, but file
529 # ownership will be changed to whateer is specified by the package,
530 # typically root. This applies to symbolic links as well. On a subsequent
531 # update, rsync will be able to copy the new files into place, because the
532 # user still has permission to write to the directories. If the symbolic
533 # link targets are not changing, though, rsync will not replace them, and
534 # they will remain owned by root. The user will not have permission to
535 # update the time on the symbolic links, resulting in an rsync error.
536 if [[ ${EUID} -ne 0 ]]; then
537 # This step isn't critical.
538 set +e
539 note "fixing installed symbolic links"
540
541 # Only consider symbolic links in ${update_app}. If there are any other
542 # links in ${installed_app} not present in ${update_app}, rsync will
543 # delete them as needed later. Use find -print0 with read -d $'\0' to
544 # handle even the weirdest paths.
545 local update_link
546 while IFS= read -r -d $'\0' update_link; do
547 # ${update_link} is relative to ${update_app}. Prepending
548 # ${installed_app} looks for the same link already on disk.
549 local installed_link="${installed_app}/${update_link}"
550 note "ensure_writable_symlink ${installed_link}"
551 ensure_writable_symlink "${installed_link}"
552 done < <(cd "${update_app}" && find . -type l -print0)
553
554 # Go back to how things were.
555 set -e
556 fi
557
558 # By copying to ${installed_app}, the existing application name will be
559 # preserved, if the user has renamed the application on disk. Respecting
560 # the user's changes is friendly.
561
562 # Make sure that ${installed_versions_dir} exists, so that it can receive
563 # the versioned directory. It may not exist if updating from an older
564 # version that did not use the versioned layout on disk. An rsync that
565 # excludes all contents is used to bring the permissions over from
566 # ${update_versions_dir}, otherwise, this directory would be the only one in
567 # the entire update exempt from getting its permissions copied over. A
568 # simple mkdir wouldn't copy mode bits. This is done even if
569 # ${installed_versions_dir} already does exist to ensure that the mode bits
570 # come from the update.
571 #
572 # ${installed_app} is guaranteed to exist at this point, but
573 # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
574 # if this update is actually an initial installation from a Keystone
575 # skeleton bootstrap. The mkdir creates ${installed_app}/${CONTENTS_DIR} if
576 # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
577 note "creating CONTENTS_DIR"
578 if ! mkdir -p "${installed_app}/${CONTENTS_DIR}"; then
579 err "mkdir of CONTENTS_DIR failed"
580 exit 5
581 fi
582
583 local update_versions_dir="${update_app}/${VERSIONS_DIR}"
584 note "update_versions_dir = ${update_versions_dir}"
585
586 note "rsyncing VERSIONS_DIR"
587 if ! rsync ${RSYNC_FLAGS} --exclude "*" "${update_versions_dir}/" \
588 "${installed_versions_dir}"; then
589 err "rsync of VERSIONS_DIR failed, status ${PIPESTATUS[0]}"
590 exit 6
591 fi
592
593 # Copy the versioned directory. The new versioned directory should have a
594 # different name than any existing one, so this won't harm anything already
595 # present in ${installed_versions_dir}, including the versioned directory
596 # being used by any running processes. If this step is interrupted, there
597 # will be an incomplete versioned directory left behind, but it won't
598 # won't interfere with anything, and it will be replaced or removed during a
599 # future update attempt. Note that in certain cases, same-version updates
600 # are distributed to move users between channels; when this happens, the
601 # contents of the versioned directories are identical and rsync will not
602 # render the versioned directory unusable even for an instant.
603 local update_versioned_dir new_versioned_dir
604 update_versioned_dir="${update_versions_dir}/${update_version_app}"
605 note "update_versioned_dir = ${update_versioned_dir}"
606 new_versioned_dir="${installed_versions_dir}/${update_version_app}"
607 note "new_versioned_dir = ${new_versioned_dir}"
608
609 note "rsyncing versioned directory"
610 if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
611 "${new_versioned_dir}"; then
612 err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
613 exit 7
614 fi
615
616 # Copy the unversioned files into place, leaving everything in 849 # Copy the unversioned files into place, leaving everything in
617 # ${installed_versions_dir} alone. If this step is interrupted, the 850 # ${installed_versions_dir} alone. If this step is interrupted, the
618 # application will at least remain in a usable state, although it may not 851 # application will at least remain in a usable state, although it may not
619 # pass signature validation. Depending on when this step is interrupted, 852 # pass signature validation. Depending on when this step is interrupted,
620 # the application will either launch the old or the new version. The 853 # the application will either launch the old or the new version. The
621 # critical point is when the main executable is replaced. There isn't very 854 # critical point is when the main executable is replaced. There isn't very
622 # much to copy in this step, because most of the application is in the 855 # much to copy in this step, because most of the application is in the
623 # versioned directory. This step only accounts for around 50 files, most of 856 # versioned directory. This step only accounts for around 50 files, most of
624 # which are small localized InfoPlist.strings files. 857 # which are small localized InfoPlist.strings files. Note that
858 # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
859 # contents are excluded, having already been installed above.
625 note "rsyncing app directory" 860 note "rsyncing app directory"
626 if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}" \ 861 if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
627 "${update_app}/" "${installed_app}"; then 862 "${update_app}/" "${installed_app}"; then
628 err "rsync of app directory failed, status ${PIPESTATUS[0]}" 863 err "rsync of app directory failed, status ${PIPESTATUS[0]}"
629 exit 8 864 exit 8
630 fi 865 fi
631 866
632 note "rsyncs complete" 867 note "rsyncs complete"
633 868
869 if [[ -n "${is_patch}" ]]; then
870 # update_app has been rsynced into place and is no longer needed.
871 rm -rf "${update_app}" 2> /dev/null || true
872 update_app=
873 note "update_app = ${update_app}"
874 fi
875
876 if [[ -n "${g_temp_dir}" ]]; then
877 # The temporary directory, if any, is no longer needed.
878 rm -rf "${g_temp_dir}" 2> /dev/null || true
879 g_temp_dir=
880 note "g_temp_dir = ${g_temp_dir}"
881 fi
882
634 # If necessary, touch the outermost .app so that it appears to the outside 883 # If necessary, touch the outermost .app so that it appears to the outside
635 # world that something was done to the bundle. This will cause 884 # world that something was done to the bundle. This will cause
636 # LaunchServices to invalidate the information it has cached about the 885 # LaunchServices to invalidate the information it has cached about the
637 # bundle even if lsregister does not run. This is not done if rsync already 886 # bundle even if lsregister does not run. This is not done if rsync already
638 # updated the timestamp to something newer than what had been on disk. This 887 # updated the timestamp to something newer than what had been on disk. This
639 # is not considered a critical step, and if it fails, this script will not 888 # is not considered a critical step, and if it fails, this script will not
640 # exit. 889 # exit.
641 if [[ -n "${needs_touch}" ]]; then 890 if [[ -n "${needs_touch}" ]]; then
642 touch -cf "${installed_app}" || true 891 touch -cf "${installed_app}" || true
643 fi 892 fi
(...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after
960 # On 10.6, xattr supports -r for recursive operation. 1209 # On 10.6, xattr supports -r for recursive operation.
961 xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null 1210 xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
962 else 1211 else
963 # On earlier systems, xattr doesn't support -r, so run xattr via find. 1212 # On earlier systems, xattr doesn't support -r, so run xattr via find.
964 find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \ 1213 find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \
965 2> /dev/null 1214 2> /dev/null
966 fi 1215 fi
967 1216
968 # Great success! 1217 # Great success!
969 note "done!" 1218 note "done!"
1219
1220 trap - EXIT
1221
970 return 0 1222 return 0
971 } 1223 }
972 1224
973 # Check "less than" instead of "not equal to" in case Keystone ever changes to 1225 # Check "less than" instead of "not equal to" in case Keystone ever changes to
974 # pass more arguments. 1226 # pass more arguments.
975 if [[ ${#} -lt 1 ]]; then 1227 if [[ ${#} -lt 1 ]]; then
976 usage 1228 usage
977 exit 2 1229 exit 2
978 fi 1230 fi
979 1231
980 main "${@}" 1232 main "${@}"
981 exit ${?} 1233 exit ${?}
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698