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

Side by Side Diff: chrome/tools/build/mac/keystone_install.sh

Issue 2417007: [Mac] packaging process cleanup: Net of this is the zip off the official buil... (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
OLDNEW
(Empty)
1 #!/bin/bash
2
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
5 # found in the LICENSE file.
6
7 # Called by the Keystone system to update the installed application with a new
8 # version from a disk image.
9
10 # Return values:
11 # 0 Happiness
12 # 1 Unknown failure
13 # 2 Basic sanity check source failure (e.g. no app on disk image)
14 # 3 Basic sanity check destination failure (e.g. ticket points to nothing)
15 # 4 Update driven by user ticket when a system ticket is also present
16 # 5 Could not prepare existing installed version to receive update
17 # 6 rsync failed (could not assure presence of Versions directory)
18 # 7 rsync failed (could not copy new versioned directory to Versions)
19 # 8 rsync failed (could not update outer .app bundle)
20 # 9 Could not get the version, update URL, or channel after update
21 # 10 Updated application does not have the version number from the update
22 # 11 ksadmin failure
23
24 set -e
25
26 # http://b/2290916: Keystone runs the installation with a restrictive PATH
27 # that only includes the directory containing ksadmin, /bin, and /usr/bin. It
28 # does not include /sbin or /usr/sbin. This script uses lsof, which is in
29 # /usr/sbin, and it's conceivable that it might want to use other tools in an
30 # sbin directory. Adjust the path accordingly.
31 export PATH="${PATH}:/sbin:/usr/sbin"
32
33 # Returns 0 (true) if the parameter exists, is a symbolic link, and appears
34 # writable on the basis of its POSIX permissions. This is used to determine
35 # writeability like test's -w primary, but -w resolves symbolic links and this
36 # function does not.
37 function is_writable_symlink() {
38 SYMLINK=${1}
39 LINKMODE=$(stat -f %Sp "${SYMLINK}" 2> /dev/null || true)
40 if [ -z "${LINKMODE}" ] || [ "${LINKMODE:0:1}" != "l" ] ; then
41 return 1
42 fi
43 LINKUSER=$(stat -f %u "${SYMLINK}" 2> /dev/null || true)
44 LINKGROUP=$(stat -f %g "${SYMLINK}" 2> /dev/null || true)
45 if [ -z "${LINKUSER}" ] || [ -z "${LINKGROUP}" ] ; then
46 return 1
47 fi
48
49 # If the users match, check the owner-write bit.
50 if [ ${EUID} -eq ${LINKUSER} ] ; then
51 if [ "${LINKMODE:2:1}" = "w" ] ; then
52 return 0
53 fi
54 return 1
55 fi
56
57 # If the file's group matches any of the groups that this process is a
58 # member of, check the group-write bit.
59 GROUPMATCH=
60 for group in ${GROUPS[@]} ; do
61 if [ ${group} -eq ${LINKGROUP} ] ; then
62 GROUPMATCH=1
63 break
64 fi
65 done
66 if [ -n "${GROUPMATCH}" ] ; then
67 if [ "${LINKMODE:5:1}" = "w" ] ; then
68 return 0
69 fi
70 return 1
71 fi
72
73 # Check the other-write bit.
74 if [ "${LINKMODE:8:1}" = "w" ] ; then
75 return 0
76 fi
77 return 1
78 }
79
80 # If SYMLINK exists and is a symbolic link, but is not writable according to
81 # is_writable_symlink, this function attempts to replace it with a new
82 # writable symbolic link. If FROM does not exist, is not a symbolic link, or
83 # is already writable, this function does nothing. This function always
84 # returns 0 (true).
85 function ensure_writable_symlink() {
86 SYMLINK=${1}
87 if [ -L "${SYMLINK}" ] && ! is_writable_symlink "${SYMLINK}" ; then
88 # If ${SYMLINK} refers to a directory, doing this naively might result in
89 # the new link being placed in that directory, instead of replacing the
90 # existing link. ln -fhs is supposed to handle this case, but it does so
91 # by unlinking (removing) the existing symbolic link before creating a new
92 # one. That leaves a small window during which the symbolic link is not
93 # present on disk at all.
94 #
95 # To avoid that possibility, a new symbolic link is created in a temporary
96 # location and then swapped into place with mv. An extra temporary
97 # directory is used to convince mv to replace the symbolic link: again, if
98 # the existing link refers to a directory, "mv newlink oldlink" will
99 # actually leave oldlink alone and place newlink into the directory.
100 # "mv newlink dirname(oldlink)" works as expected, but in order to replace
101 # oldlink, newlink must have the same basename, hence the temporary
102 # directory.
103
104 TARGET=$(readlink "${SYMLINK}" 2> /dev/null || true)
105 if [ -z "${TARGET}" ] ; then
106 return 0
107 fi
108
109 SYMLINKDIR=$(dirname "${SYMLINK}")
110 TEMPLINKDIR="${SYMLINKDIR}/.symlink_temp.${$}.${RANDOM}"
111 TEMPLINK="${TEMPLINKDIR}/$(basename "${SYMLINK}")"
112
113 # Don't bail out here if this fails. Something else will probably fail.
114 # Let it, it'll probably be easier to understand that failure than this
115 # one.
116 (mkdir "${TEMPLINKDIR}" && \
117 ln -fhs "${TARGET}" "${TEMPLINK}" && \
118 chmod -h 755 "${TEMPLINK}" && \
119 mv -f "${TEMPLINK}" "${SYMLINKDIR}") || true
120 rm -rf "${TEMPLINKDIR}"
121 fi
122
123 return 0
124 }
125
126 # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
127 # stdout. This function operates with "static" variables: it will only check
128 # the ksadmin version once per script run. If ksadmin is old enough to not
129 # support --ksadmin-version, or another error occurs, this function prints an
130 # empty string.
131 G_CHECKED_KSADMIN_VERSION=
132 G_KSADMIN_VERSION=
133 function ksadmin_version() {
134 if [ -z "${G_CHECKED_KSADMIN_VERSION}" ] ; then
135 G_CHECKED_KSADMIN_VERSION=1
136 G_KSADMIN_VERSION=$(ksadmin --ksadmin-version || true)
137 fi
138 echo "${G_KSADMIN_VERSION}"
139 return 0
140 }
141
142 # Compares the installed ksadmin version against a supplied version number,
143 # and returns 0 (true) if the number to check is the same as or newer than the
144 # installed Keystone. Returns 1 (false) if the installed Keystone version
145 # number cannot be determined or if the number to check is less than the
146 # installed Keystone. The check argument should be a string of the form
147 # "major.minor.micro.build".
148 function is_ksadmin_version_ge() {
149 CHECK_VERSION=${1}
150 KSADMIN_VERSION=$(ksadmin_version)
151 if [ -n "${KSADMIN_VERSION}" ] ; then
152 VER_RE='^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$'
153
154 KSADMIN_VERSION_MAJOR=$(sed -Ene "s/${VER_RE}/\1/p" <<< ${KSADMIN_VERSION})
155 KSADMIN_VERSION_MINOR=$(sed -Ene "s/${VER_RE}/\2/p" <<< ${KSADMIN_VERSION})
156 KSADMIN_VERSION_MICRO=$(sed -Ene "s/${VER_RE}/\3/p" <<< ${KSADMIN_VERSION})
157 KSADMIN_VERSION_BUILD=$(sed -Ene "s/${VER_RE}/\4/p" <<< ${KSADMIN_VERSION})
158
159 CHECK_VERSION_MAJOR=$(sed -Ene "s/${VER_RE}/\1/p" <<< ${CHECK_VERSION})
160 CHECK_VERSION_MINOR=$(sed -Ene "s/${VER_RE}/\2/p" <<< ${CHECK_VERSION})
161 CHECK_VERSION_MICRO=$(sed -Ene "s/${VER_RE}/\3/p" <<< ${CHECK_VERSION})
162 CHECK_VERSION_BUILD=$(sed -Ene "s/${VER_RE}/\4/p" <<< ${CHECK_VERSION})
163
164 if [ ${KSADMIN_VERSION_MAJOR} -gt ${CHECK_VERSION_MAJOR} ] ||
165 ([ ${KSADMIN_VERSION_MAJOR} -eq ${CHECK_VERSION_MAJOR} ] && (
166 [ ${KSADMIN_VERSION_MINOR} -gt ${CHECK_VERSION_MINOR} ] ||
167 ([ ${KSADMIN_VERSION_MINOR} -eq ${CHECK_VERSION_MINOR} ] && (
168 [ ${KSADMIN_VERSION_MICRO} -gt ${CHECK_VERSION_MICRO} ] ||
169 ([ ${KSADMIN_VERSION_MICRO} -eq ${CHECK_VERSION_MICRO} ] &&
170 [ ${KSADMIN_VERSION_BUILD} -ge ${CHECK_VERSION_BUILD} ])
171 ))
172 )) ; then
173 return 0
174 fi
175 fi
176
177 return 1
178 }
179
180 # Returns 0 (true) if ksadmin supports --tag.
181 function ksadmin_supports_tag() {
182 KSADMIN_VERSION=$(ksadmin_version)
183 if [ -n "${KSADMIN_VERSION}" ] ; then
184 # A ksadmin that recognizes --ksadmin-version and provides a version
185 # number is new enough to recognize --tag.
186 return 0
187 fi
188 return 1
189 }
190
191 # Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
192 function ksadmin_supports_tagpath_tagkey() {
193 # --tag-path and --tag-key were introduced in Keystone 1.0.7.1306.
194 is_ksadmin_version_ge 1.0.7.1306
195 # The return value of is_ksadmin_version_ge is used as this function's
196 # return value.
197 }
198
199 # Returns 0 (true) if ksadmin supports --tag-path, --tag-key, --brand-path,
200 # and --brand-key.
201 function ksadmin_supports_brandpath_brandkey() {
202 # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620.
203 # --tag-path and --tag-key are already supported if the brand arguments are
204 # also supported.
205 is_ksadmin_version_ge 1.0.8.1620
206 # The return value of is_ksadmin_version_ge is used as this function's
207 # return value.
208 }
209
210 # The argument should be the disk image path. Make sure it exists.
211 if [ $# -lt 1 ] || [ ! -d "${1}" ]; then
212 exit 2
213 fi
214
215 # Who we are.
216 PRODUCT_NAME="Google Chrome"
217 APP_DIR="${PRODUCT_NAME}.app"
218 FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
219 FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
220 SRC="${1}/${APP_DIR}"
221
222 # Make sure that there's something to copy from, and that it's an absolute
223 # path.
224 if [ -z "${SRC}" ] || [ "${SRC:0:1}" != "/" ] || [ ! -d "${SRC}" ] ; then
225 exit 2
226 fi
227
228 # Figure out where we're going. Determine the application version to be
229 # installed, use that to locate the framework, and then look inside the
230 # framework for the Keystone product ID.
231 APP_VERSION_KEY="CFBundleShortVersionString"
232 UPD_VERSION_APP=$(defaults read "${SRC}/Contents/Info" "${APP_VERSION_KEY}" ||
233 exit 2)
234 UPD_KS_PLIST="${SRC}/Contents/Versions/${UPD_VERSION_APP}/${FRAMEWORK_DIR}/Resou rces/Info"
235 KS_VERSION_KEY="KSVersion"
236 UPD_VERSION_KS=$(defaults read "${UPD_KS_PLIST}" "${KS_VERSION_KEY}" || exit 2)
237 PRODUCT_ID=$(defaults read "${UPD_KS_PLIST}" KSProductID || exit 2)
238 if [ -z "${UPD_VERSION_KS}" ] || [ -z "${PRODUCT_ID}" ] ; then
239 exit 3
240 fi
241 DEST=$(ksadmin -pP "${PRODUCT_ID}" |
242 sed -Ene \
243 's%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>$%\1%p')
244
245 # More sanity checking.
246 if [ -z "${DEST}" ] || [ ! -d "${DEST}" ]; then
247 exit 3
248 fi
249
250 # If this script is not running as root, it's being driven by a user ticket.
251 # If a system ticket is also present, there's a potential for the two to
252 # collide. Both ticket types might be present if another user on the system
253 # promoted the ticket to system: the other user could not have removed this
254 # user's user ticket. Handle that case here by deleting the user ticket and
255 # exiting early with a discrete exit status.
256 #
257 # Current versions of ksadmin will exit 1 (false) when asked to print tickets
258 # and given a specific product ID to print. Older versions of ksadmin would
259 # exit 0 (true), but those same versions did not support -S (meaning to check
260 # the system ticket store) and would exit 1 (false) with this invocation due
261 # to not understanding the question. Therefore, the usage here will only
262 # delete the existing user ticket when running as non-root with access to a
263 # sufficiently recent ksadmin. Older ksadmins are tolerated: the update will
264 # likely fail for another reason and the user ticket will hang around until
265 # something is eventually able to remove it.
266 if [ ${EUID} -ne 0 ] &&
267 ksadmin -S --print-tickets -P "${PRODUCT_ID}" >& /dev/null ; then
268 ksadmin --delete -P "${PRODUCT_ID}" || true
269 exit 4
270 fi
271
272 # Figure out what the existing version is using for its versioned directory.
273 # This will be used later, to avoid removing the currently-installed version's
274 # versioned directory in case anything is still using it.
275 OLD_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
276 true)
277 OLD_VERSIONED_DIR="${DEST}/Contents/Versions/${OLD_VERSION_APP}"
278
279 # See if the timestamp of what's currently on disk is newer than the update's
280 # outer .app's timestamp. rsync will copy the update's timestamp over, but
281 # if that timestamp isn't as recent as what's already on disk, the .app will
282 # need to be touched.
283 NEEDS_TOUCH=
284 if [ "${DEST}" -nt "${SRC}" ] ; then
285 NEEDS_TOUCH=1
286 fi
287
288 # In some very weird and rare cases, it is possible to wind up with a user
289 # installation that contains symbolic links that the user does not have write
290 # permission over. More on how that might happen later.
291 #
292 # If a weird and rare case like this is observed, rsync will exit with an
293 # error when attempting to update the times on these symbolic links. rsync
294 # may not be intelligent enough to try creating a new symbolic link in these
295 # cases, but this script can be.
296 #
297 # This fix-up is not necessary when running as root, because root will always
298 # be able to write everything needed.
299 #
300 # The problem occurs when an administrative user first drag-installs the
301 # application to /Applications, resulting in the program's user being set to
302 # the user's own ID. If, subsequently, a .pkg package is installed over that,
303 # the existing directory ownership will be preserved, but file ownership will
304 # be changed to whateer is specified by the package, typically root. This
305 # applies to symbolic links as well. On a subsequent update, rsync will
306 # be able to copy the new files into place, because the user still has
307 # permission to write to the directories. If the symbolic link targets are
308 # not changing, though, rsync will not replace them, and they will remain
309 # owned by root. The user will not have permission to update the time on
310 # the symbolic links, resulting in an rsync error.
311 if [ ${EUID} -ne 0 ] ; then
312 # This step isn't critical.
313 set +e
314
315 # Reset ${IFS} to deal with spaces in the for loop by not breaking the
316 # list up when they're encountered.
317 IFS_OLD="${IFS}"
318 IFS=$(printf '\n\t')
319
320 # Only consider symbolic links in ${SRC}. If there are any other links in
321 # ${DEST} not present in ${SRC}, rsync will delete them as needed later.
322 LINKS=$(cd "${SRC}" && find . -type l)
323
324 for link in ${LINKS} ; do
325 # ${link} is relative to ${SRC}. Prepending ${DEST} looks for the same
326 # link already on disk.
327 DESTLINK="${DEST}/${link}"
328 ensure_writable_symlink "${DESTLINK}"
329 done
330
331 # Go back to how things were.
332 IFS="${IFS_OLD}"
333 set -e
334 fi
335
336 # Collect the current app brand, it will be use later.
337 BRAND_ID_KEY=KSBrandID
338 APP_BRAND=$(defaults read "${DEST}/Contents/Info" "${BRAND_ID_KEY}" 2>/dev/null ||
339 true)
340
341 # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners
342 # and groups, respectively, from the source, and that is undesirable in this
343 # case. -D copies devices and special files; copying devices only works
344 # when running as root, so for consistency between privileged and unprivileged
345 # operation, this option is omitted as well.
346 # -I, --ignore-times don't skip files that match in size and mod-time
347 # -l, --links copy symlinks as symlinks
348 # -r, --recursive recurse into directories
349 # -p, --perms preserve permissions
350 # -t, --times preserve times
351 RSYNC_FLAGS="-Ilprt"
352
353 # By copying to ${DEST}, the existing application name will be preserved, even
354 # if the user has renamed the application on disk. Respecting the user's
355 # changes is friendly.
356
357 # Make sure that the Versions directory exists, so that it can receive the
358 # versioned directory. It may not exist if updating from an older version
359 # that did not use the versioned layout on disk. An rsync that excludes all
360 # contents is used to bring the permissions over from the update's Versions
361 # directory, otherwise, this directory would be the only one in the entire
362 # update exempt from getting its permissions copied over. A simple mkdir
363 # wouldn't copy mode bits. This is done even if ${DEST}/Contents/Versions
364 # already does exist to ensure that the mode bits come from the update.
365 #
366 # ${DEST} is guaranteed to exist at this point, but ${DEST}/Contents may not
367 # if things are severely broken or if this update is actually an initial
368 # installation from a Keystone skeleton bootstrap. The mkdir creates
369 # ${DEST}/Contents if it doesn't exist; its mode bits will be fixed up in a
370 # subsequent rsync.
371 mkdir -p "${DEST}/Contents" || exit 5
372 rsync ${RSYNC_FLAGS} --exclude "*" "${SRC}/Contents/Versions/" \
373 "${DEST}/Contents/Versions" || exit 6
374
375 # Copy the versioned directory. The new versioned directory will have a
376 # different name than any existing one, so this won't harm anything already
377 # present in Contents/Versions, including the versioned directory being used
378 # by any running processes. If this step is interrupted, there will be an
379 # incomplete versioned directory left behind, but it won't interfere with
380 # anything, and it will be replaced or removed during a future update attempt.
381 NEW_VERSIONED_DIR="${DEST}/Contents/Versions/${UPD_VERSION_APP}"
382 rsync ${RSYNC_FLAGS} --delete-before \
383 "${SRC}/Contents/Versions/${UPD_VERSION_APP}/" \
384 "${NEW_VERSIONED_DIR}" || exit 7
385
386 # Copy the unversioned files into place, leaving everything in
387 # Contents/Versions alone. If this step is interrupted, the application will
388 # at least remain in a usable state, although it may not pass signature
389 # validation. Depending on when this step is interrupted, the application
390 # will either launch the old or the new version. The critical point is when
391 # the main executable is replaced. There isn't very much to copy in this step,
392 # because most of the application is in the versioned directory. This step
393 # only accounts for around 50 files, most of which are small localized
394 # InfoPlist.strings files.
395 rsync ${RSYNC_FLAGS} --delete-after --exclude /Contents/Versions \
396 "${SRC}/" "${DEST}" || exit 8
397
398 # If necessary, touch the outermost .app so that it appears to the outside
399 # world that something was done to the bundle. This will cause LaunchServices
400 # to invalidate the information it has cached about the bundle even if
401 # lsregister does not run. This is not done if rsync already updated the
402 # timestamp to something newer than what had been on disk. This is not
403 # considered a critical step, and if it fails, this script will not exit.
404 if [ -n "${NEEDS_TOUCH}" ] ; then
405 touch -cf "${DEST}" || true
406 fi
407
408 # Read the new values (e.g. version). Get the installed application version
409 # to get the path to the framework, where the Keystone keys are stored.
410 NEW_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
411 exit 9)
412 NEW_KS_PLIST="${DEST}/Contents/Versions/${NEW_VERSION_APP}/${FRAMEWORK_DIR}/Reso urces/Info"
413 NEW_VERSION_KS=$(defaults read "${NEW_KS_PLIST}" "${KS_VERSION_KEY}" || exit 9)
414 URL=$(defaults read "${NEW_KS_PLIST}" KSUpdateURL || exit 9)
415 # The channel ID is optional. Suppress stderr to prevent Keystone from seeing
416 # possible error output.
417 CHANNEL_ID_KEY=KSChannelID
418 CHANNEL_ID=$(defaults read "${NEW_KS_PLIST}" "${CHANNEL_ID_KEY}" 2>/dev/null ||
419 true)
420
421 # Make sure that the update was successful by comparing the version found in
422 # the update with the version now on disk.
423 if [ "${NEW_VERSION_KS}" != "${UPD_VERSION_KS}" ]; then
424 exit 10
425 fi
426
427 # Notify LaunchServices. This is not considered a critical step, and
428 # lsregister's exit codes shouldn't be confused with this script's own.
429 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.fram ework/Support/lsregister "${DEST}" || true
430
431 # Call ksadmin_version once to prime the global state. This is needed because
432 # subsequent calls to ksadmin_version that occur in $(...) expansions will not
433 # affect the global state (although they can read from the already-initialized
434 # global state) and thus will cause a new ksadmin --ksadmin-version process to
435 # run for each check unless the globals have been properly initialized
436 # beforehand.
437 ksadmin_version >& /dev/null || true
438
439 # The brand information is stored differently depending on whether this is
440 # running for a system or user ticket.
441 BRAND_PATH_PLIST=
442 SET_BRAND_FILE_ACCESS=no
443 if [ ${EUID} -ne 0 ] ; then
444 # Using a user level ticket.
445 BRAND_PATH_PLIST=~/"Library/Google/Google Chrome Brand"
446 else
447 # Using a system level ticket.
448 BRAND_PATH_PLIST="/Library/Google/Google Chrome Brand"
449 SET_BRAND_FILE_ACCESS=yes
450 fi
451 # If the user manually updated their copy of Chrome, there might be new brand
452 # information in the app bundle, and that needs to be copied out into the
453 # file Keystone looks at.
454 BRAND_PATH="${BRAND_PATH_PLIST}.plist"
455 if [ -n "${APP_BRAND}" ] ; then
456 BRAND_PATH_DIR=$(dirname "${BRAND_PATH}")
457 if [ ! -e "${BRAND_PATH_DIR}" ] ; then
458 mkdir -p "${BRAND_PATH_DIR}"
459 fi
460 defaults write "${BRAND_PATH_PLIST}" "${BRAND_ID_KEY}" -string "${APP_BRAND}"
461 if [ "${SET_BRAND_FILE_ACCESS}" = "yes" ] ; then
462 chown "root:wheel" "${BRAND_PATH}" >& /dev/null
463 chmod "a+r,u+w,go-w" "${BRAND_PATH}" >& /dev/null
464 fi
465 fi
466 # Confirm that the brand file exists (it is optional)
467 if [ ! -f "${BRAND_PATH}" ] ; then
468 BRAND_PATH=
469 # ksadmin reports an error if brand-path is cleared but brand-key still has a
470 # value, so if there is no path, clear the key also.
471 BRAND_ID_KEY=
472 fi
473
474 # Notify Keystone.
475 if ksadmin_supports_brandpath_brandkey ; then
476 ksadmin --register \
477 -P "${PRODUCT_ID}" \
478 --version "${NEW_VERSION_KS}" \
479 --xcpath "${DEST}" \
480 --url "${URL}" \
481 --tag "${CHANNEL_ID}" \
482 --tag-path "${DEST}/Contents/Info.plist" \
483 --tag-key "${CHANNEL_ID_KEY}" \
484 --brand-path "${BRAND_PATH}" \
485 --brand-key "${BRAND_ID_KEY}" || exit 11
486 elif ksadmin_supports_tagpath_tagkey ; then
487 ksadmin --register \
488 -P "${PRODUCT_ID}" \
489 --version "${NEW_VERSION_KS}" \
490 --xcpath "${DEST}" \
491 --url "${URL}" \
492 --tag "${CHANNEL_ID}" \
493 --tag-path "${DEST}/Contents/Info.plist" \
494 --tag-key "${CHANNEL_ID_KEY}" || exit 11
495 elif ksadmin_supports_tag ; then
496 ksadmin --register \
497 -P "${PRODUCT_ID}" \
498 --version "${NEW_VERSION_KS}" \
499 --xcpath "${DEST}" \
500 --url "${URL}" \
501 --tag "${CHANNEL_ID}" || exit 11
502 else
503 ksadmin --register \
504 -P "${PRODUCT_ID}" \
505 --version "${NEW_VERSION_KS}" \
506 --xcpath "${DEST}" \
507 --url "${URL}" || exit 11
508 fi
509
510 # The remaining steps are not considered critical.
511 set +e
512
513 # Try to clean up old versions that are not in use. The strategy is to keep
514 # the versioned directory corresponding to the update just applied
515 # (obviously) and the version that was just replaced, and to use ps and lsof
516 # to see if it looks like any processes are currently using any other old
517 # directories. Directories not in use are removed. Old versioned directories
518 # that are in use are left alone so as to not interfere with running
519 # processes. These directories can be cleaned up by this script on future
520 # updates.
521 #
522 # To determine which directories are in use, both ps and lsof are used. Each
523 # approach has limitations.
524 #
525 # The ps check looks for processes within the verisoned directory. Only
526 # helper processes, such as renderers, are within the versioned directory.
527 # Browser processes are not, so the ps check will not find them, and will
528 # assume that a versioned directory is not in use if a browser is open without
529 # any windows. The ps mechanism can also only detect processes running on the
530 # system that is performing the update. If network shares are involved, all
531 # bets are off.
532 #
533 # The lsof check looks to see what processes have the framework dylib open.
534 # Browser processes will have their versioned framework dylib open, so this
535 # check is able to catch browsers even if there are no associated helper
536 # processes. Like the ps check, the lsof check is limited to processes on
537 # the system that is performing the update. Finally, unless running as root,
538 # the lsof check can only find processes running as the effective user
539 # performing the update.
540 #
541 # These limitations are motiviations to additionally preserve the versioned
542 # directory corresponding to the version that was just replaced.
543
544 # Set the nullglob option. This causes a glob pattern that doesn't match
545 # any files to expand to an empty string, instead of expanding to the glob
546 # pattern itself. This means that if /path/* doesn't match anything, it will
547 # expand to "" instead of, literally, "/path/*". The glob used in the loop
548 # below is not expected to expand to nothing, but nullglob will prevent the
549 # loop from trying to remove nonexistent directories by weird names with
550 # funny characters in them.
551 shopt -s nullglob
552
553 for versioned_dir in "${DEST}/Contents/Versions/"* ; do
554 if [ "${versioned_dir}" = "${NEW_VERSIONED_DIR}" ] || \
555 [ "${versioned_dir}" = "${OLD_VERSIONED_DIR}" ] ; then
556 # This is the versioned directory corresponding to the update that was
557 # just applied or the version that was previously in use. Leave it alone.
558 continue
559 fi
560
561 # Look for any processes whose executables are within this versioned
562 # directory. They'll be helper processes, such as renderers. Their
563 # existence indicates that this versioned directory is currently in use.
564 PS_STRING="${versioned_dir}/"
565
566 # Look for any processes using the framework dylib. This will catch
567 # browser processes where the ps check will not, but it is limited to
568 # processes running as the effective user.
569 LSOF_FILE="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
570
571 # ps -e displays all users' processes, -ww causes ps to not truncate lines,
572 # -o comm instructs it to only print the command name, and the = tells it to
573 # not print a header line.
574 # The cut invocation filters the ps output to only have at most the number
575 # of characters in ${PS_STRING}. This is done so that grep can look for an
576 # exact match.
577 # grep -F tells grep to look for lines that are exact matches (not regular
578 # expressions), -q tells it to not print any output and just indicate
579 # matches by exit status, and -x tells it that the entire line must match
580 # ${PS_STRING} exactly, as opposed to matching a substring. A match
581 # causes grep to exit zero (true).
582 #
583 # lsof will exit nonzero if ${LSOF_FILE} does not exist or is open by any
584 # process. If the file exists and is open, it will exit zero (true).
585 if (! ps -ewwo comm= | \
586 cut -c "1-${#PS_STRING}" | \
587 grep -Fqx "${PS_STRING}") &&
588 (! lsof "${LSOF_FILE}" >& /dev/null) ; then
589 # It doesn't look like anything is using this versioned directory. Get rid
590 # of it.
591 rm -rf "${versioned_dir}"
592 fi
593 done
594
595 # If this script is not running as root (indicating an update driven by a user
596 # Keystone ticket) and the application is installed somewhere under
597 # /Applications, try to make it writable by all admin users. This will allow
598 # other admin users to update the application from their own user Keystone
599 # instances.
600 #
601 # If the script is not running as root and the application is not installed
602 # under /Applications, it might not be in a system-wide location, and it
603 # probably won't be something that other users on the system are running, so
604 # err on the side of safety and don't make it group-writable.
605 #
606 # If this script is running as root, it's driven by a system Keystone ticket,
607 # and future updates can be expected to be applied the same way, so
608 # admin-writeability is not a concern. Set the entire thing to be owned by
609 # root in that case, regardless of where it's installed, and drop any group
610 # and other write permission.
611 #
612 # If this script is running as a user that is not a member of the admin group,
613 # the chgrp operation will not succeed. Tolerate that case, because it's
614 # better than the alternative, which is to make the application
615 # world-writable.
616 CHMOD_MODE="a+rX,u+w,go-w"
617 if [ ${EUID} -ne 0 ] ; then
618 if [ "${DEST:0:14}" = "/Applications/" ] &&
619 chgrp -Rh admin "${DEST}" >& /dev/null ; then
620 CHMOD_MODE="a+rX,ug+w,o-w"
621 fi
622 else
623 chown -Rh root:wheel "${DEST}" >& /dev/null
624 fi
625
626 chmod -R "${CHMOD_MODE}" "${DEST}" >& /dev/null
627
628 # On the Mac, or at least on HFS+, symbolic link permissions are significant,
629 # but chmod -R and -h can't be used together. Do another pass to fix the
630 # permissions on any symbolic links.
631 find "${DEST}" -type l -exec chmod -h "${CHMOD_MODE}" {} + >& /dev/null
632
633 # Host OS version check, to be able to take advantage of features on newer
634 # systems and fall back to slow ways of doing things on older systems.
635 OS_VERSION=$(sw_vers -productVersion)
636 OS_MAJOR=$(sed -Ene 's/^([0-9]+).*/\1/p' <<< ${OS_VERSION})
637 OS_MINOR=$(sed -Ene 's/^([0-9]+)\.([0-9]+).*/\2/p' <<< ${OS_VERSION})
638
639 # If an update is triggered from within the application itself, the update
640 # process inherits the quarantine bit (LSFileQuarantineEnabled). Any files or
641 # directories created during the update will be quarantined in that case,
642 # which may cause Launch Services to display quarantine UI. That's bad,
643 # especially if it happens when the outer .app launches a quarantined inner
644 # helper. If the application is already on the system and is being updated,
645 # then it can be assumed that it should not be quarantined. Use xattr to drop
646 # the quarantine attribute.
647 #
648 # TODO(mark): Instead of letting the quarantine attribute be set and then
649 # dropping it here, figure out a way to get the update process to run without
650 # LSFileQuarantineEnabled even when triggering an update from within the
651 # application.
652 QUARANTINE_ATTR=com.apple.quarantine
653 if [ ${OS_MAJOR} -gt 10 ] ||
654 ([ ${OS_MAJOR} -eq 10 ] && [ ${OS_MINOR} -ge 6 ]) ; then
655 # On 10.6, xattr supports -r for recursive operation.
656 xattr -d -r "${QUARANTINE_ATTR}" "${DEST}" >& /dev/null
657 else
658 # On earlier systems, xattr doesn't support -r, so run xattr via find.
659 find "${DEST}" -exec xattr -d "${QUARANTINE_ATTR}" {} + >& /dev/null
660 fi
661
662 # Great success!
663 exit 0
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698