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

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

Issue 285002: Make the auto-update script really smart. It no longer replaces the versione... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years, 2 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 | chrome/tools/build/mac/keystone_install_test.sh » ('j') | 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 1 #!/bin/bash
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 # Called by the Keystone system to update the installed application with a new 7 # Called by the Keystone system to update the installed application with a new
8 # version from a disk image. 8 # version from a disk image.
9 9
10 # Return values: 10 # Return values:
11 # 0 Happiness 11 # 0 Happiness
12 # 1 Unknown failure 12 # 1 Unknown failure
13 # 2 Basic sanity check destination failure (e.g. ticket points to nothing) 13 # 2 Basic sanity check destination failure (e.g. ticket points to nothing)
14 # 3 Cannot get version of currently installed Chrome 14 # 3 Cannot get version of currently installed Chrome
15 # 4 No permission to write in destination directory 15 # 4 rsync failed (could not assure presence of Versions directory)
16 # 5 rsync failed 16 # 5 rsync failed (could not copy new versioned directory to Versions)
17 # 6 Cannot get version or update URL of newly installed Chrome 17 # 6 rsync failed (could not update outer .app bundle)
18 # 7 Post-install Chrome has same version as pre-install Chrome 18 # 7 Cannot get version or update URL of newly installed Chrome
19 # 8 ksadmin failure 19 # 8 Post-install Chrome has same version as pre-install Chrome
20 # 9 ksadmin failure
20 # 10 Basic sanity check source failure (e.g. no app on disk image) 21 # 10 Basic sanity check source failure (e.g. no app on disk image)
21 22
22 set -e 23 set -e
23 24
24 # The argument should be the disk image path. Make sure it exists. 25 # The argument should be the disk image path. Make sure it exists.
25 if [ $# -lt 1 ] || [ ! -d "${1}" ]; then 26 if [ $# -lt 1 ] || [ ! -d "${1}" ]; then
26 exit 10 27 exit 10
27 fi 28 fi
28 29
29 # Who we are. 30 # Who we are.
30 PRODUCT_NAME="Google Chrome" 31 PRODUCT_NAME="Google Chrome"
31 APP_NAME="${PRODUCT_NAME}.app" 32 APP_DIR="${PRODUCT_NAME}.app"
32 FRAMEWORK_NAME="${PRODUCT_NAME} Framework.framework" 33 FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
33 SRC="${1}/${APP_NAME}" 34 FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
35 SRC="${1}/${APP_DIR}"
34 36
35 # Sanity, make sure that there's something to copy from. 37 # Make sure that there's something to copy from, and that it's an absolute
36 if [ -z "${SRC}" ] || [ ! -d "${SRC}" ]; then 38 # path.
39 if [ -z "${SRC}" ] || [ "${SRC:0:1}" != "/" ] || [ ! -d "${SRC}" ] ; then
37 exit 10 40 exit 10
38 fi 41 fi
39 42
40 # Figure out where we're going. Determine the application version to be 43 # Figure out where we're going. Determine the application version to be
41 # installed, use that to locate the framework, and then look inside the 44 # installed, use that to locate the framework, and then look inside the
42 # framework for the Keystone product ID. 45 # framework for the Keystone product ID.
43 APP_VERSION_KEY="CFBundleShortVersionString" 46 APP_VERSION_KEY="CFBundleShortVersionString"
44 UPD_VERSION_APP=$(defaults read "${SRC}/Contents/Info" "${APP_VERSION_KEY}" || 47 UPD_VERSION_APP=$(defaults read "${SRC}/Contents/Info" "${APP_VERSION_KEY}" ||
45 exit 10) 48 exit 10)
46 UPD_KS_PLIST="${SRC}/Contents/Versions/${UPD_VERSION_APP}/${FRAMEWORK_NAME}/Reso urces/Info" 49 UPD_KS_PLIST="${SRC}/Contents/Versions/${UPD_VERSION_APP}/${FRAMEWORK_DIR}/Resou rces/Info"
47 PRODUCT_ID=$(defaults read "${UPD_KS_PLIST}" KSProductID || exit 10) 50 PRODUCT_ID=$(defaults read "${UPD_KS_PLIST}" KSProductID || exit 10)
48 DEST=$(ksadmin -pP "${PRODUCT_ID}" | grep xc= | sed -E 's/.+path=(.+)>$/\1/g') 51 DEST=$(ksadmin -pP "${PRODUCT_ID}" |
52 sed -Ene \
53 's%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>$%\1%p')
49 54
50 # More sanity checking. 55 # More sanity checking.
51 if [ -z "${DEST}" ] || [ ! -d "$(dirname "${DEST}")" ]; then 56 if [ -z "${DEST}" ] || [ ! -d "${DEST}" ]; then
52 exit 2 57 exit 2
53 fi 58 fi
54 59
55 # Read old version to help confirm install happiness. Older versions kept 60 # Read old version to help confirm install happiness. Older versions kept
56 # the KSVersion key in the application's Info.plist. Newer versions keep it 61 # the KSVersion key in the application's Info.plist. Newer versions keep it
57 # in the versioned framework's Info.plist. 62 # in the versioned framework's Info.plist.
63 OLD_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
64 true)
65 OLD_VERSIONED_DIR="${DEST}/Contents/Versions/${OLD_VERSION_APP}"
66 OLD_KS_PLIST="${OLD_VERSIONED_DIR}/${FRAMEWORK_DIR}/Resources/Info"
67 if [ -z "${OLD_VERSION_APP}" ] || [ ! -e "${OLD_KS_PLIST}.plist" ] ; then
68 OLD_KS_PLIST="${DEST}/Contents/Info"
69 fi
58 KS_VERSION_KEY="KSVersion" 70 KS_VERSION_KEY="KSVersion"
59 OLD_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
60 defaults read "${DEST}/Contents/Info" "${KS_VERSION_KEY}" ||
61 exit 3)
62 OLD_KS_PLIST="${DEST}/Contents/Versions/${OLD_VERSION_APP}/${FRAMEWORK_NAME}/Res ources/Info"
63 if [ ! -e "${OLD_KS_PLIST}.plist" ] ; then
64 OLD_KS_PLIST="${DEST}/Contents/Info"
65 fi
66 OLD_VERSION_KS=$(defaults read "${OLD_KS_PLIST}" "${KS_VERSION_KEY}" || exit 3) 71 OLD_VERSION_KS=$(defaults read "${OLD_KS_PLIST}" "${KS_VERSION_KEY}" || exit 3)
67 72
68 # Make sure we have permission to write the destination 73 # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners
69 DEST_DIRECTORY="$(dirname "${DEST}")" 74 # and groups, respectively, from the source, and that is undesirable in this
70 if [ ! -w "${DEST_DIRECTORY}" ]; then 75 # case. -D copies devices and special files; copying devices only works
71 exit 4 76 # when running as root, so for consistency between privileged and unprivileged
72 fi 77 # operation, this option is omitted as well.
73 78 # -c, --checksum skip based on checksum, not mod-time & size
74 # This usage will preserve any changes the user made to the application name. 79 # -l, --links copy symlinks as symlinks
75 # TODO(jrg): this may choke a running Chrome.app; be smarter. 80 # -r, --recursive recurse into directories
76 # Note: If the rsync fails we do not update the ticket version. 81 # -p, --perms preserve permissions
77 rsync -ac --delete "${SRC}/" "${DEST}/" || exit 5 82 # -t, --times preserve times
83 RSYNC_FLAGS="-clprt"
84
85 # By copying to ${DEST}, the existing application name will be preserved, even
86 # if the user has renamed the application on disk. Respecting the user's
87 # changes is friendly.
88
89 # Make sure that the Versions directory exists, so that it can receive the
90 # versioned directory. It may not exist if updating from an older version
91 # that did not use the versioned layout on disk. An rsync that excludes all
92 # contents is used to bring the permissions over from the update's Versions
93 # directory, otherwise, this directory would be the only one in the entire
94 # update exempt from getting its permissions copied over. A simple mkdir
95 # wouldn't copy mode bits. This is done even if ${DEST}/Contents/Versions
96 # already does exist to ensure that the mode bits come from the udpate.
97 rsync ${RSYNC_FLAGS} --exclude "*" "${SRC}/Contents/Versions/" \
98 "${DEST}/Contents/Versions" || exit 4
99
100 # Copy the versioned directory. The new versioned directory will have a
101 # different name than any existing one, so this won't harm anything already
102 # present in Contents/Versions, including the versioned directory being used
103 # by any running processes. If this step is interrupted, there will be an
104 # incomplete versioned directory left behind, but it won't interfere with
105 # anything, and it will be replaced or removed during a future update attempt.
106 NEW_VERSIONED_DIR="${DEST}/Contents/Versions/${UPD_VERSION_APP}"
107 rsync ${RSYNC_FLAGS} --delete-before \
108 "${SRC}/Contents/Versions/${UPD_VERSION_APP}/" \
109 "${NEW_VERSIONED_DIR}" || exit 5
110
111 # See if the timestamp of what's currently on disk is newer than the update's
112 # outer .app's timestamp. rsync will copy the update's timestamp over, but
113 # if that timestamp isn't as recent as what's already on disk, the .app will
114 # need to be touched.
115 NEEDS_TOUCH=
116 if [ "${DEST}" -nt "${SRC}" ] ; then
117 NEEDS_TOUCH=1
118 fi
119
120 # Copy the unversioned files into place, leaving everything in
121 # Contents/Versions alone. If this step is interrupted, the application will
122 # at least remain in a usable state, although it may not pass signature
123 # validation. Depending on when this step is interrupted, the application
124 # will either launch the old or the new version. The critical point is when
125 # the main executable is replaced. There isn't very much to copy in this step,
126 # because most of the application is in the versioned directory. This step
127 # only accounts for around 50 files, most of which are small localized
128 # InfoPlist.strings files.
129 rsync ${RSYNC_FLAGS} --delete-after --exclude /Contents/Versions \
130 "${SRC}/" "${DEST}" || exit 6
131
132 # If necessary, touch the outermost .app so that it appears to the outside
133 # world that something was done to the bundle. This will cause LaunchServices
134 # to invalidate the information it has cached about the bundle even if
135 # lsregister does not run. This is not done if rsync already updated the
136 # timestamp to something newer than what had been on disk. This is not
137 # considered a critical step, and if it fails, this script will not exit.
138 if [ -n "${NEEDS_TOUCH}" ] ; then
139 touch -cf "${DEST}" || true
140 fi
78 141
79 # Read the new values (e.g. version). Get the installed application version 142 # Read the new values (e.g. version). Get the installed application version
80 # to get the path to the framework, where the Keystone keys are stored. 143 # to get the path to the framework, where the Keystone keys are stored.
81 NEW_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" || 144 NEW_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
82 exit 6) 145 exit 7)
83 NEW_KS_PLIST="${DEST}/Contents/Versions/${NEW_VERSION_APP}/${FRAMEWORK_NAME}/Res ources/Info" 146 NEW_KS_PLIST="${DEST}/Contents/Versions/${NEW_VERSION_APP}/${FRAMEWORK_DIR}/Reso urces/Info"
84 NEW_VERSION_KS=$(defaults read "${NEW_KS_PLIST}" "${KS_VERSION_KEY}" || exit 6) 147 NEW_VERSION_KS=$(defaults read "${NEW_KS_PLIST}" "${KS_VERSION_KEY}" || exit 7)
85 URL=$(defaults read "${NEW_KS_PLIST}" KSUpdateURL || exit 6) 148 URL=$(defaults read "${NEW_KS_PLIST}" KSUpdateURL || exit 7)
86 # The channel ID is optional. Suppress stderr to prevent Keystone from seeing 149 # The channel ID is optional. Suppress stderr to prevent Keystone from seeing
87 # possible error output. 150 # possible error output.
88 CHANNEL_ID=$(defaults read "${NEW_KS_PLIST}" KSChannelID 2>/dev/null || true) 151 CHANNEL_ID=$(defaults read "${NEW_KS_PLIST}" KSChannelID 2>/dev/null || true)
89 152
90 # Compare old and new versions. If they are equal we failed somewhere. 153 # Compare old and new versions. If they are equal we failed somewhere.
91 if [ "${OLD_VERSION_KS}" = "${NEW_VERSION_KS}" ]; then 154 if [ "${OLD_VERSION_KS}" = "${NEW_VERSION_KS}" ]; then
92 exit 7 155 exit 8
93 fi 156 fi
94 157
95 # Notify LaunchServices. 158 # Notify LaunchServices. This is not considered a critical step, and
96 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.fram ework/Support/lsregister "${DEST}" 159 # lsregister's exit codes shouldn't be confused with this script's own.
97 160 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.fram ework/Support/lsregister "${DEST}" || true
98 # Notify Keystone. Older versions of Keystone don't recognize --tag. If the 161
99 # command with --tag fails, retry without it. In that case, Chrome will set 162 # Notify Keystone.
100 # the tag when it runs. 163 KSADMIN_VERSION=$(ksadmin --ksadmin-version || true)
101 # TODO: The version of Keystone picking up --tag will also include support for 164 if [ -n "${KSADMIN_VERSION}" ] ; then
102 # --ksdamin-version. At that point, we can check to see if ksadmin honors the 165 # If ksadmin recognizes --ksadmin-version, it will recognize --tag.
103 # version check; if not, no --tag, if yes, do a case...esac on the version
104 # patterns for any support checks we need.
105 ksadmin --register \
106 -P "${PRODUCT_ID}" \
107 --version "${NEW_VERSION_KS}" \
108 --xcpath "${DEST}" \
109 --url "${URL}" \
110 --tag "${CHANNEL_ID}" || \
111 ksadmin --register \ 166 ksadmin --register \
112 -P "${PRODUCT_ID}" \ 167 -P "${PRODUCT_ID}" \
113 --version "${NEW_VERSION_KS}" \ 168 --version "${NEW_VERSION_KS}" \
114 --xcpath "${DEST}" \ 169 --xcpath "${DEST}" \
115 --url "${URL}" || exit 8 170 --url "${URL}" \
171 --tag "${CHANNEL_ID}" || exit 9
172 else
173 # Older versions of ksadmin don't recognize --tag. The application will
174 # set the tag when it runs.
175 ksadmin --register \
176 -P "${PRODUCT_ID}" \
177 --version "${NEW_VERSION_KS}" \
178 --xcpath "${DEST}" \
179 --url "${URL}" || exit 9
180 fi
181
182 # The remaining steps are not considered critical.
183 set +e
184
185 # Try to clean up old versions that are not in use. The strategy is to keep
186 # the versioned directory corresponding to the update just applied
187 # (obviously) and the version that was just replaced, and to use ps and lsof
188 # to see if it looks like any processes are currently using any other old
189 # directories. Directories not in use are removed. Old versioned directories
190 # that are in use are left alone so as to not interfere with running
191 # processes. These directories can be cleaned up by this script on future
192 # updates.
193 #
194 # To determine which directories are in use, both ps and lsof are used. Each
195 # approach has limitations.
196 #
197 # The ps check looks for processes within the verisoned directory. Only
198 # helper processes, such as renderers, are within the versioned directory.
199 # Browser processes are not, so the ps check will not find them, and will
200 # assume that a versioned directory is not in use if a browser is open without
201 # any windows. The ps mechanism can also only detect processes running on the
202 # system that is performing the update. If network shares are involved, all
203 # bets are off.
204 #
205 # The lsof check looks to see what processes have the framework dylib open.
206 # Browser processes will have their versioned framework dylib open, so this
207 # check is able to catch browsers even if there are no associated helper
208 # processes. Like the ps check, the lsof check is limited to processes on
209 # the system that is performing the update. Finally, unless running as root,
210 # the lsof check can only find processes running as the effective user
211 # performing the update.
212 #
213 # These limitations are motiviations to additionally preserve the versioned
214 # directory corresponding to the version that was just replaced.
215
216 # Set the nullglob option. This causes a glob pattern that doesn't match
217 # any files to expand to an empty string, instead of expanding to the glob
218 # pattern itself. This means that if /path/* doesn't match anything, it will
219 # expand to "" instead of, literally, "/path/*". The glob used in the loop
220 # below is not expected to expand to nothing, but nullglob will prevent the
221 # loop from trying to remove nonexistent directories by weird names with
222 # funny characters in them.
223 shopt -s nullglob
224
225 for versioned_dir in "${DEST}/Contents/Versions/"* ; do
226 if [ "${versioned_dir}" = "${NEW_VERSIONED_DIR}" ] || \
227 [ "${versioned_dir}" = "${OLD_VERSIONED_DIR}" ] ; then
228 # This is the versioned directory corresponding to the update that was
229 # just applied or the version that was previously in use. Leave it alone.
230 continue
231 fi
232
233 # Look for any processes whose executables are within this versioned
234 # directory. They'll be helper processes, such as renderers. Their
235 # existence indicates that this versioned directory is currently in use.
236 PS_STRING="${versioned_dir}/"
237
238 # Look for any processes using the framework dylib. This will catch
239 # browser processes where the ps check will not, but it is limited to
240 # processes running as the effective user.
241 LSOF_FILE="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
242
243 # ps -e displays all users' processes, -ww causes ps to not truncate lines,
244 # -o comm instructs it to only print the command name, and the = tells it to
245 # not print a header line.
246 # The cut invocation filters the ps output to only have at most the number
247 # of characters in ${PS_STRING}. This is done so that grep can look for an
248 # exact match.
249 # grep -F tells grep to look for lines that are exact matches (not regular
250 # expressions), -q tells it to not print any output and just indicate
251 # matches by exit status, and -x tells it that the entire line must match
252 # ${PS_STRING} exactly, as opposed to matching a substring. A match
253 # causes grep to exit zero (true).
254 #
255 # lsof will exit nonzero if ${LSOF_FILE} does not exist or is open by any
256 # process. If the file exists and is open, it will exit zero (true).
257 if (! ps -ewwo comm= | \
258 cut -c "1-${#PS_STRING}" | \
259 grep -Fqx "${PS_STRING}") &&
260 (! lsof "${LSOF_FILE}" >& /dev/null) ; then
261 # It doesn't look like anything is using this versioned directory. Get rid
262 # of it.
263 rm -rf "${versioned_dir}"
264 fi
265 done
116 266
117 # Great success! 267 # Great success!
118 exit 0 268 exit 0
OLDNEW
« no previous file with comments | « no previous file | chrome/tools/build/mac/keystone_install_test.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698