summaryrefslogtreecommitdiffstats
path: root/chrome/installer
diff options
context:
space:
mode:
authorthomasvl@chromium.org <thomasvl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-02 16:54:03 +0000
committerthomasvl@chromium.org <thomasvl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-02 16:54:03 +0000
commit66caf563b438a8cd51ac03a1cdcdc6dbde135c45 (patch)
tree7c72a44dc276ea8b91879811f03febda4536f425 /chrome/installer
parent2290cb86f535d7babd4d84a2a08831404885e78c (diff)
downloadchromium_src-66caf563b438a8cd51ac03a1cdcdc6dbde135c45.zip
chromium_src-66caf563b438a8cd51ac03a1cdcdc6dbde135c45.tar.gz
chromium_src-66caf563b438a8cd51ac03a1cdcdc6dbde135c45.tar.bz2
[Mac] packaging process cleanup: Net of this is the zip off the official builder will have everything for packaging so the final signing/dmg creation is self contained instead of having to live in both places.
- Move all the installer related bits into the installer folder - Add the collection of these bits into a specific target - Collect the internal bits also during the installer target BUG=42635 TEST=none Review URL: http://codereview.chromium.org/2417007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@48732 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/installer')
-rw-r--r--chrome/installer/mac/app_resource_rules.plist.in55
-rwxr-xr-xchrome/installer/mac/keystone_install.sh663
-rwxr-xr-xchrome/installer/mac/make_sign_sh48
-rw-r--r--chrome/installer/mac/sign.sh.in51
4 files changed, 817 insertions, 0 deletions
diff --git a/chrome/installer/mac/app_resource_rules.plist.in b/chrome/installer/mac/app_resource_rules.plist.in
new file mode 100644
index 0000000..d629044
--- /dev/null
+++ b/chrome/installer/mac/app_resource_rules.plist.in
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>rules</key>
+ <dict>
+ <key>^PkgInfo$</key>
+ <true/>
+ <key>^MacOS/</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^Versions/</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^Versions/@VERSION_REGEX@/</key>
+ <dict>
+ <key>weight</key>
+ <real>20</real>
+ </dict>
+ <key>^Resources/.+\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>30</real>
+ </dict>
+ <key>^Versions/@VERSION_REGEX@/.+/Resources/.+\.lproj/</key>
+ <dict>
+ <key>optional</key>
+ <true/>
+ <key>weight</key>
+ <real>30</real>
+ </dict>
+ <key>^Versions/@VERSION_REGEX@/.+/Resources Disabled/</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>40</real>
+ </dict>
+ <key>/\.DS_Store$</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>50</real>
+ </dict>
+ </dict>
+</dict>
+</plist>
diff --git a/chrome/installer/mac/keystone_install.sh b/chrome/installer/mac/keystone_install.sh
new file mode 100755
index 0000000..73f0920
--- /dev/null
+++ b/chrome/installer/mac/keystone_install.sh
@@ -0,0 +1,663 @@
+#!/bin/bash
+
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Called by the Keystone system to update the installed application with a new
+# version from a disk image.
+
+# Return values:
+# 0 Happiness
+# 1 Unknown failure
+# 2 Basic sanity check source failure (e.g. no app on disk image)
+# 3 Basic sanity check destination failure (e.g. ticket points to nothing)
+# 4 Update driven by user ticket when a system ticket is also present
+# 5 Could not prepare existing installed version to receive update
+# 6 rsync failed (could not assure presence of Versions directory)
+# 7 rsync failed (could not copy new versioned directory to Versions)
+# 8 rsync failed (could not update outer .app bundle)
+# 9 Could not get the version, update URL, or channel after update
+# 10 Updated application does not have the version number from the update
+# 11 ksadmin failure
+
+set -e
+
+# http://b/2290916: Keystone runs the installation with a restrictive PATH
+# that only includes the directory containing ksadmin, /bin, and /usr/bin. It
+# does not include /sbin or /usr/sbin. This script uses lsof, which is in
+# /usr/sbin, and it's conceivable that it might want to use other tools in an
+# sbin directory. Adjust the path accordingly.
+export PATH="${PATH}:/sbin:/usr/sbin"
+
+# Returns 0 (true) if the parameter exists, is a symbolic link, and appears
+# writable on the basis of its POSIX permissions. This is used to determine
+# writeability like test's -w primary, but -w resolves symbolic links and this
+# function does not.
+function is_writable_symlink() {
+ SYMLINK=${1}
+ LINKMODE=$(stat -f %Sp "${SYMLINK}" 2> /dev/null || true)
+ if [ -z "${LINKMODE}" ] || [ "${LINKMODE:0:1}" != "l" ] ; then
+ return 1
+ fi
+ LINKUSER=$(stat -f %u "${SYMLINK}" 2> /dev/null || true)
+ LINKGROUP=$(stat -f %g "${SYMLINK}" 2> /dev/null || true)
+ if [ -z "${LINKUSER}" ] || [ -z "${LINKGROUP}" ] ; then
+ return 1
+ fi
+
+ # If the users match, check the owner-write bit.
+ if [ ${EUID} -eq ${LINKUSER} ] ; then
+ if [ "${LINKMODE:2:1}" = "w" ] ; then
+ return 0
+ fi
+ return 1
+ fi
+
+ # If the file's group matches any of the groups that this process is a
+ # member of, check the group-write bit.
+ GROUPMATCH=
+ for group in ${GROUPS[@]} ; do
+ if [ ${group} -eq ${LINKGROUP} ] ; then
+ GROUPMATCH=1
+ break
+ fi
+ done
+ if [ -n "${GROUPMATCH}" ] ; then
+ if [ "${LINKMODE:5:1}" = "w" ] ; then
+ return 0
+ fi
+ return 1
+ fi
+
+ # Check the other-write bit.
+ if [ "${LINKMODE:8:1}" = "w" ] ; then
+ return 0
+ fi
+ return 1
+}
+
+# If SYMLINK exists and is a symbolic link, but is not writable according to
+# is_writable_symlink, this function attempts to replace it with a new
+# writable symbolic link. If FROM does not exist, is not a symbolic link, or
+# is already writable, this function does nothing. This function always
+# returns 0 (true).
+function ensure_writable_symlink() {
+ SYMLINK=${1}
+ if [ -L "${SYMLINK}" ] && ! is_writable_symlink "${SYMLINK}" ; then
+ # If ${SYMLINK} refers to a directory, doing this naively might result in
+ # the new link being placed in that directory, instead of replacing the
+ # existing link. ln -fhs is supposed to handle this case, but it does so
+ # by unlinking (removing) the existing symbolic link before creating a new
+ # one. That leaves a small window during which the symbolic link is not
+ # present on disk at all.
+ #
+ # To avoid that possibility, a new symbolic link is created in a temporary
+ # location and then swapped into place with mv. An extra temporary
+ # directory is used to convince mv to replace the symbolic link: again, if
+ # the existing link refers to a directory, "mv newlink oldlink" will
+ # actually leave oldlink alone and place newlink into the directory.
+ # "mv newlink dirname(oldlink)" works as expected, but in order to replace
+ # oldlink, newlink must have the same basename, hence the temporary
+ # directory.
+
+ TARGET=$(readlink "${SYMLINK}" 2> /dev/null || true)
+ if [ -z "${TARGET}" ] ; then
+ return 0
+ fi
+
+ SYMLINKDIR=$(dirname "${SYMLINK}")
+ TEMPLINKDIR="${SYMLINKDIR}/.symlink_temp.${$}.${RANDOM}"
+ TEMPLINK="${TEMPLINKDIR}/$(basename "${SYMLINK}")"
+
+ # Don't bail out here if this fails. Something else will probably fail.
+ # Let it, it'll probably be easier to understand that failure than this
+ # one.
+ (mkdir "${TEMPLINKDIR}" && \
+ ln -fhs "${TARGET}" "${TEMPLINK}" && \
+ chmod -h 755 "${TEMPLINK}" && \
+ mv -f "${TEMPLINK}" "${SYMLINKDIR}") || true
+ rm -rf "${TEMPLINKDIR}"
+ fi
+
+ return 0
+}
+
+# Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
+# stdout. This function operates with "static" variables: it will only check
+# the ksadmin version once per script run. If ksadmin is old enough to not
+# support --ksadmin-version, or another error occurs, this function prints an
+# empty string.
+G_CHECKED_KSADMIN_VERSION=
+G_KSADMIN_VERSION=
+function ksadmin_version() {
+ if [ -z "${G_CHECKED_KSADMIN_VERSION}" ] ; then
+ G_CHECKED_KSADMIN_VERSION=1
+ G_KSADMIN_VERSION=$(ksadmin --ksadmin-version || true)
+ fi
+ echo "${G_KSADMIN_VERSION}"
+ return 0
+}
+
+# Compares the installed ksadmin version against a supplied version number,
+# and returns 0 (true) if the number to check is the same as or newer than the
+# installed Keystone. Returns 1 (false) if the installed Keystone version
+# number cannot be determined or if the number to check is less than the
+# installed Keystone. The check argument should be a string of the form
+# "major.minor.micro.build".
+function is_ksadmin_version_ge() {
+ CHECK_VERSION=${1}
+ KSADMIN_VERSION=$(ksadmin_version)
+ if [ -n "${KSADMIN_VERSION}" ] ; then
+ VER_RE='^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$'
+
+ KSADMIN_VERSION_MAJOR=$(sed -Ene "s/${VER_RE}/\1/p" <<< ${KSADMIN_VERSION})
+ KSADMIN_VERSION_MINOR=$(sed -Ene "s/${VER_RE}/\2/p" <<< ${KSADMIN_VERSION})
+ KSADMIN_VERSION_MICRO=$(sed -Ene "s/${VER_RE}/\3/p" <<< ${KSADMIN_VERSION})
+ KSADMIN_VERSION_BUILD=$(sed -Ene "s/${VER_RE}/\4/p" <<< ${KSADMIN_VERSION})
+
+ CHECK_VERSION_MAJOR=$(sed -Ene "s/${VER_RE}/\1/p" <<< ${CHECK_VERSION})
+ CHECK_VERSION_MINOR=$(sed -Ene "s/${VER_RE}/\2/p" <<< ${CHECK_VERSION})
+ CHECK_VERSION_MICRO=$(sed -Ene "s/${VER_RE}/\3/p" <<< ${CHECK_VERSION})
+ CHECK_VERSION_BUILD=$(sed -Ene "s/${VER_RE}/\4/p" <<< ${CHECK_VERSION})
+
+ if [ ${KSADMIN_VERSION_MAJOR} -gt ${CHECK_VERSION_MAJOR} ] ||
+ ([ ${KSADMIN_VERSION_MAJOR} -eq ${CHECK_VERSION_MAJOR} ] && (
+ [ ${KSADMIN_VERSION_MINOR} -gt ${CHECK_VERSION_MINOR} ] ||
+ ([ ${KSADMIN_VERSION_MINOR} -eq ${CHECK_VERSION_MINOR} ] && (
+ [ ${KSADMIN_VERSION_MICRO} -gt ${CHECK_VERSION_MICRO} ] ||
+ ([ ${KSADMIN_VERSION_MICRO} -eq ${CHECK_VERSION_MICRO} ] &&
+ [ ${KSADMIN_VERSION_BUILD} -ge ${CHECK_VERSION_BUILD} ])
+ ))
+ )) ; then
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# Returns 0 (true) if ksadmin supports --tag.
+function ksadmin_supports_tag() {
+ KSADMIN_VERSION=$(ksadmin_version)
+ if [ -n "${KSADMIN_VERSION}" ] ; then
+ # A ksadmin that recognizes --ksadmin-version and provides a version
+ # number is new enough to recognize --tag.
+ return 0
+ fi
+ return 1
+}
+
+# Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
+function ksadmin_supports_tagpath_tagkey() {
+ # --tag-path and --tag-key were introduced in Keystone 1.0.7.1306.
+ is_ksadmin_version_ge 1.0.7.1306
+ # The return value of is_ksadmin_version_ge is used as this function's
+ # return value.
+}
+
+# Returns 0 (true) if ksadmin supports --tag-path, --tag-key, --brand-path,
+# and --brand-key.
+function ksadmin_supports_brandpath_brandkey() {
+ # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620.
+ # --tag-path and --tag-key are already supported if the brand arguments are
+ # also supported.
+ is_ksadmin_version_ge 1.0.8.1620
+ # The return value of is_ksadmin_version_ge is used as this function's
+ # return value.
+}
+
+# The argument should be the disk image path. Make sure it exists.
+if [ $# -lt 1 ] || [ ! -d "${1}" ]; then
+ exit 2
+fi
+
+# Who we are.
+PRODUCT_NAME="Google Chrome"
+APP_DIR="${PRODUCT_NAME}.app"
+FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
+FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
+SRC="${1}/${APP_DIR}"
+
+# Make sure that there's something to copy from, and that it's an absolute
+# path.
+if [ -z "${SRC}" ] || [ "${SRC:0:1}" != "/" ] || [ ! -d "${SRC}" ] ; then
+ exit 2
+fi
+
+# Figure out where we're going. Determine the application version to be
+# installed, use that to locate the framework, and then look inside the
+# framework for the Keystone product ID.
+APP_VERSION_KEY="CFBundleShortVersionString"
+UPD_VERSION_APP=$(defaults read "${SRC}/Contents/Info" "${APP_VERSION_KEY}" ||
+ exit 2)
+UPD_KS_PLIST="${SRC}/Contents/Versions/${UPD_VERSION_APP}/${FRAMEWORK_DIR}/Resources/Info"
+KS_VERSION_KEY="KSVersion"
+UPD_VERSION_KS=$(defaults read "${UPD_KS_PLIST}" "${KS_VERSION_KEY}" || exit 2)
+PRODUCT_ID=$(defaults read "${UPD_KS_PLIST}" KSProductID || exit 2)
+if [ -z "${UPD_VERSION_KS}" ] || [ -z "${PRODUCT_ID}" ] ; then
+ exit 3
+fi
+DEST=$(ksadmin -pP "${PRODUCT_ID}" |
+ sed -Ene \
+ 's%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>$%\1%p')
+
+# More sanity checking.
+if [ -z "${DEST}" ] || [ ! -d "${DEST}" ]; then
+ exit 3
+fi
+
+# If this script is not running as root, it's being driven by a user ticket.
+# If a system ticket is also present, there's a potential for the two to
+# collide. Both ticket types might be present if another user on the system
+# promoted the ticket to system: the other user could not have removed this
+# user's user ticket. Handle that case here by deleting the user ticket and
+# exiting early with a discrete exit status.
+#
+# Current versions of ksadmin will exit 1 (false) when asked to print tickets
+# and given a specific product ID to print. Older versions of ksadmin would
+# exit 0 (true), but those same versions did not support -S (meaning to check
+# the system ticket store) and would exit 1 (false) with this invocation due
+# to not understanding the question. Therefore, the usage here will only
+# delete the existing user ticket when running as non-root with access to a
+# sufficiently recent ksadmin. Older ksadmins are tolerated: the update will
+# likely fail for another reason and the user ticket will hang around until
+# something is eventually able to remove it.
+if [ ${EUID} -ne 0 ] &&
+ ksadmin -S --print-tickets -P "${PRODUCT_ID}" >& /dev/null ; then
+ ksadmin --delete -P "${PRODUCT_ID}" || true
+ exit 4
+fi
+
+# Figure out what the existing version is using for its versioned directory.
+# This will be used later, to avoid removing the currently-installed version's
+# versioned directory in case anything is still using it.
+OLD_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
+ true)
+OLD_VERSIONED_DIR="${DEST}/Contents/Versions/${OLD_VERSION_APP}"
+
+# See if the timestamp of what's currently on disk is newer than the update's
+# outer .app's timestamp. rsync will copy the update's timestamp over, but
+# if that timestamp isn't as recent as what's already on disk, the .app will
+# need to be touched.
+NEEDS_TOUCH=
+if [ "${DEST}" -nt "${SRC}" ] ; then
+ NEEDS_TOUCH=1
+fi
+
+# In some very weird and rare cases, it is possible to wind up with a user
+# installation that contains symbolic links that the user does not have write
+# permission over. More on how that might happen later.
+#
+# If a weird and rare case like this is observed, rsync will exit with an
+# error when attempting to update the times on these symbolic links. rsync
+# may not be intelligent enough to try creating a new symbolic link in these
+# cases, but this script can be.
+#
+# This fix-up is not necessary when running as root, because root will always
+# be able to write everything needed.
+#
+# The problem occurs when an administrative user first drag-installs the
+# application to /Applications, resulting in the program's user being set to
+# the user's own ID. If, subsequently, a .pkg package is installed over that,
+# the existing directory ownership will be preserved, but file ownership will
+# be changed to whateer is specified by the package, typically root. This
+# applies to symbolic links as well. On a subsequent update, rsync will
+# be able to copy the new files into place, because the user still has
+# permission to write to the directories. If the symbolic link targets are
+# not changing, though, rsync will not replace them, and they will remain
+# owned by root. The user will not have permission to update the time on
+# the symbolic links, resulting in an rsync error.
+if [ ${EUID} -ne 0 ] ; then
+ # This step isn't critical.
+ set +e
+
+ # Reset ${IFS} to deal with spaces in the for loop by not breaking the
+ # list up when they're encountered.
+ IFS_OLD="${IFS}"
+ IFS=$(printf '\n\t')
+
+ # Only consider symbolic links in ${SRC}. If there are any other links in
+ # ${DEST} not present in ${SRC}, rsync will delete them as needed later.
+ LINKS=$(cd "${SRC}" && find . -type l)
+
+ for link in ${LINKS} ; do
+ # ${link} is relative to ${SRC}. Prepending ${DEST} looks for the same
+ # link already on disk.
+ DESTLINK="${DEST}/${link}"
+ ensure_writable_symlink "${DESTLINK}"
+ done
+
+ # Go back to how things were.
+ IFS="${IFS_OLD}"
+ set -e
+fi
+
+# Collect the current app brand, it will be use later.
+BRAND_ID_KEY=KSBrandID
+APP_BRAND=$(defaults read "${DEST}/Contents/Info" "${BRAND_ID_KEY}" 2>/dev/null ||
+ true)
+
+# Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners
+# and groups, respectively, from the source, and that is undesirable in this
+# case. -D copies devices and special files; copying devices only works
+# when running as root, so for consistency between privileged and unprivileged
+# operation, this option is omitted as well.
+# -I, --ignore-times don't skip files that match in size and mod-time
+# -l, --links copy symlinks as symlinks
+# -r, --recursive recurse into directories
+# -p, --perms preserve permissions
+# -t, --times preserve times
+RSYNC_FLAGS="-Ilprt"
+
+# By copying to ${DEST}, the existing application name will be preserved, even
+# if the user has renamed the application on disk. Respecting the user's
+# changes is friendly.
+
+# Make sure that the Versions directory exists, so that it can receive the
+# versioned directory. It may not exist if updating from an older version
+# that did not use the versioned layout on disk. An rsync that excludes all
+# contents is used to bring the permissions over from the update's Versions
+# directory, otherwise, this directory would be the only one in the entire
+# update exempt from getting its permissions copied over. A simple mkdir
+# wouldn't copy mode bits. This is done even if ${DEST}/Contents/Versions
+# already does exist to ensure that the mode bits come from the update.
+#
+# ${DEST} is guaranteed to exist at this point, but ${DEST}/Contents may not
+# if things are severely broken or if this update is actually an initial
+# installation from a Keystone skeleton bootstrap. The mkdir creates
+# ${DEST}/Contents if it doesn't exist; its mode bits will be fixed up in a
+# subsequent rsync.
+mkdir -p "${DEST}/Contents" || exit 5
+rsync ${RSYNC_FLAGS} --exclude "*" "${SRC}/Contents/Versions/" \
+ "${DEST}/Contents/Versions" || exit 6
+
+# Copy the versioned directory. The new versioned directory will have a
+# different name than any existing one, so this won't harm anything already
+# present in Contents/Versions, including the versioned directory being used
+# by any running processes. If this step is interrupted, there will be an
+# incomplete versioned directory left behind, but it won't interfere with
+# anything, and it will be replaced or removed during a future update attempt.
+NEW_VERSIONED_DIR="${DEST}/Contents/Versions/${UPD_VERSION_APP}"
+rsync ${RSYNC_FLAGS} --delete-before \
+ "${SRC}/Contents/Versions/${UPD_VERSION_APP}/" \
+ "${NEW_VERSIONED_DIR}" || exit 7
+
+# Copy the unversioned files into place, leaving everything in
+# Contents/Versions alone. If this step is interrupted, the application will
+# at least remain in a usable state, although it may not pass signature
+# validation. Depending on when this step is interrupted, the application
+# will either launch the old or the new version. The critical point is when
+# the main executable is replaced. There isn't very much to copy in this step,
+# because most of the application is in the versioned directory. This step
+# only accounts for around 50 files, most of which are small localized
+# InfoPlist.strings files.
+rsync ${RSYNC_FLAGS} --delete-after --exclude /Contents/Versions \
+ "${SRC}/" "${DEST}" || exit 8
+
+# If necessary, touch the outermost .app so that it appears to the outside
+# world that something was done to the bundle. This will cause LaunchServices
+# to invalidate the information it has cached about the bundle even if
+# lsregister does not run. This is not done if rsync already updated the
+# timestamp to something newer than what had been on disk. This is not
+# considered a critical step, and if it fails, this script will not exit.
+if [ -n "${NEEDS_TOUCH}" ] ; then
+ touch -cf "${DEST}" || true
+fi
+
+# Read the new values (e.g. version). Get the installed application version
+# to get the path to the framework, where the Keystone keys are stored.
+NEW_VERSION_APP=$(defaults read "${DEST}/Contents/Info" "${APP_VERSION_KEY}" ||
+ exit 9)
+NEW_KS_PLIST="${DEST}/Contents/Versions/${NEW_VERSION_APP}/${FRAMEWORK_DIR}/Resources/Info"
+NEW_VERSION_KS=$(defaults read "${NEW_KS_PLIST}" "${KS_VERSION_KEY}" || exit 9)
+URL=$(defaults read "${NEW_KS_PLIST}" KSUpdateURL || exit 9)
+# The channel ID is optional. Suppress stderr to prevent Keystone from seeing
+# possible error output.
+CHANNEL_ID_KEY=KSChannelID
+CHANNEL_ID=$(defaults read "${NEW_KS_PLIST}" "${CHANNEL_ID_KEY}" 2>/dev/null ||
+ true)
+
+# Make sure that the update was successful by comparing the version found in
+# the update with the version now on disk.
+if [ "${NEW_VERSION_KS}" != "${UPD_VERSION_KS}" ]; then
+ exit 10
+fi
+
+# Notify LaunchServices. This is not considered a critical step, and
+# lsregister's exit codes shouldn't be confused with this script's own.
+/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister "${DEST}" || true
+
+# Call ksadmin_version once to prime the global state. This is needed because
+# subsequent calls to ksadmin_version that occur in $(...) expansions will not
+# affect the global state (although they can read from the already-initialized
+# global state) and thus will cause a new ksadmin --ksadmin-version process to
+# run for each check unless the globals have been properly initialized
+# beforehand.
+ksadmin_version >& /dev/null || true
+
+# The brand information is stored differently depending on whether this is
+# running for a system or user ticket.
+BRAND_PATH_PLIST=
+SET_BRAND_FILE_ACCESS=no
+if [ ${EUID} -ne 0 ] ; then
+ # Using a user level ticket.
+ BRAND_PATH_PLIST=~/"Library/Google/Google Chrome Brand"
+else
+ # Using a system level ticket.
+ BRAND_PATH_PLIST="/Library/Google/Google Chrome Brand"
+ SET_BRAND_FILE_ACCESS=yes
+fi
+# If the user manually updated their copy of Chrome, there might be new brand
+# information in the app bundle, and that needs to be copied out into the
+# file Keystone looks at.
+BRAND_PATH="${BRAND_PATH_PLIST}.plist"
+if [ -n "${APP_BRAND}" ] ; then
+ BRAND_PATH_DIR=$(dirname "${BRAND_PATH}")
+ if [ ! -e "${BRAND_PATH_DIR}" ] ; then
+ mkdir -p "${BRAND_PATH_DIR}"
+ fi
+ defaults write "${BRAND_PATH_PLIST}" "${BRAND_ID_KEY}" -string "${APP_BRAND}"
+ if [ "${SET_BRAND_FILE_ACCESS}" = "yes" ] ; then
+ chown "root:wheel" "${BRAND_PATH}" >& /dev/null
+ chmod "a+r,u+w,go-w" "${BRAND_PATH}" >& /dev/null
+ fi
+fi
+# Confirm that the brand file exists (it is optional)
+if [ ! -f "${BRAND_PATH}" ] ; then
+ BRAND_PATH=
+ # ksadmin reports an error if brand-path is cleared but brand-key still has a
+ # value, so if there is no path, clear the key also.
+ BRAND_ID_KEY=
+fi
+
+# Notify Keystone.
+if ksadmin_supports_brandpath_brandkey ; then
+ ksadmin --register \
+ -P "${PRODUCT_ID}" \
+ --version "${NEW_VERSION_KS}" \
+ --xcpath "${DEST}" \
+ --url "${URL}" \
+ --tag "${CHANNEL_ID}" \
+ --tag-path "${DEST}/Contents/Info.plist" \
+ --tag-key "${CHANNEL_ID_KEY}" \
+ --brand-path "${BRAND_PATH}" \
+ --brand-key "${BRAND_ID_KEY}" || exit 11
+elif ksadmin_supports_tagpath_tagkey ; then
+ ksadmin --register \
+ -P "${PRODUCT_ID}" \
+ --version "${NEW_VERSION_KS}" \
+ --xcpath "${DEST}" \
+ --url "${URL}" \
+ --tag "${CHANNEL_ID}" \
+ --tag-path "${DEST}/Contents/Info.plist" \
+ --tag-key "${CHANNEL_ID_KEY}" || exit 11
+elif ksadmin_supports_tag ; then
+ ksadmin --register \
+ -P "${PRODUCT_ID}" \
+ --version "${NEW_VERSION_KS}" \
+ --xcpath "${DEST}" \
+ --url "${URL}" \
+ --tag "${CHANNEL_ID}" || exit 11
+else
+ ksadmin --register \
+ -P "${PRODUCT_ID}" \
+ --version "${NEW_VERSION_KS}" \
+ --xcpath "${DEST}" \
+ --url "${URL}" || exit 11
+fi
+
+# The remaining steps are not considered critical.
+set +e
+
+# Try to clean up old versions that are not in use. The strategy is to keep
+# the versioned directory corresponding to the update just applied
+# (obviously) and the version that was just replaced, and to use ps and lsof
+# to see if it looks like any processes are currently using any other old
+# directories. Directories not in use are removed. Old versioned directories
+# that are in use are left alone so as to not interfere with running
+# processes. These directories can be cleaned up by this script on future
+# updates.
+#
+# To determine which directories are in use, both ps and lsof are used. Each
+# approach has limitations.
+#
+# The ps check looks for processes within the verisoned directory. Only
+# helper processes, such as renderers, are within the versioned directory.
+# Browser processes are not, so the ps check will not find them, and will
+# assume that a versioned directory is not in use if a browser is open without
+# any windows. The ps mechanism can also only detect processes running on the
+# system that is performing the update. If network shares are involved, all
+# bets are off.
+#
+# The lsof check looks to see what processes have the framework dylib open.
+# Browser processes will have their versioned framework dylib open, so this
+# check is able to catch browsers even if there are no associated helper
+# processes. Like the ps check, the lsof check is limited to processes on
+# the system that is performing the update. Finally, unless running as root,
+# the lsof check can only find processes running as the effective user
+# performing the update.
+#
+# These limitations are motiviations to additionally preserve the versioned
+# directory corresponding to the version that was just replaced.
+
+# Set the nullglob option. This causes a glob pattern that doesn't match
+# any files to expand to an empty string, instead of expanding to the glob
+# pattern itself. This means that if /path/* doesn't match anything, it will
+# expand to "" instead of, literally, "/path/*". The glob used in the loop
+# below is not expected to expand to nothing, but nullglob will prevent the
+# loop from trying to remove nonexistent directories by weird names with
+# funny characters in them.
+shopt -s nullglob
+
+for versioned_dir in "${DEST}/Contents/Versions/"* ; do
+ if [ "${versioned_dir}" = "${NEW_VERSIONED_DIR}" ] || \
+ [ "${versioned_dir}" = "${OLD_VERSIONED_DIR}" ] ; then
+ # This is the versioned directory corresponding to the update that was
+ # just applied or the version that was previously in use. Leave it alone.
+ continue
+ fi
+
+ # Look for any processes whose executables are within this versioned
+ # directory. They'll be helper processes, such as renderers. Their
+ # existence indicates that this versioned directory is currently in use.
+ PS_STRING="${versioned_dir}/"
+
+ # Look for any processes using the framework dylib. This will catch
+ # browser processes where the ps check will not, but it is limited to
+ # processes running as the effective user.
+ LSOF_FILE="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
+
+ # ps -e displays all users' processes, -ww causes ps to not truncate lines,
+ # -o comm instructs it to only print the command name, and the = tells it to
+ # not print a header line.
+ # The cut invocation filters the ps output to only have at most the number
+ # of characters in ${PS_STRING}. This is done so that grep can look for an
+ # exact match.
+ # grep -F tells grep to look for lines that are exact matches (not regular
+ # expressions), -q tells it to not print any output and just indicate
+ # matches by exit status, and -x tells it that the entire line must match
+ # ${PS_STRING} exactly, as opposed to matching a substring. A match
+ # causes grep to exit zero (true).
+ #
+ # lsof will exit nonzero if ${LSOF_FILE} does not exist or is open by any
+ # process. If the file exists and is open, it will exit zero (true).
+ if (! ps -ewwo comm= | \
+ cut -c "1-${#PS_STRING}" | \
+ grep -Fqx "${PS_STRING}") &&
+ (! lsof "${LSOF_FILE}" >& /dev/null) ; then
+ # It doesn't look like anything is using this versioned directory. Get rid
+ # of it.
+ rm -rf "${versioned_dir}"
+ fi
+done
+
+# If this script is not running as root (indicating an update driven by a user
+# Keystone ticket) and the application is installed somewhere under
+# /Applications, try to make it writable by all admin users. This will allow
+# other admin users to update the application from their own user Keystone
+# instances.
+#
+# If the script is not running as root and the application is not installed
+# under /Applications, it might not be in a system-wide location, and it
+# probably won't be something that other users on the system are running, so
+# err on the side of safety and don't make it group-writable.
+#
+# If this script is running as root, it's driven by a system Keystone ticket,
+# and future updates can be expected to be applied the same way, so
+# admin-writeability is not a concern. Set the entire thing to be owned by
+# root in that case, regardless of where it's installed, and drop any group
+# and other write permission.
+#
+# If this script is running as a user that is not a member of the admin group,
+# the chgrp operation will not succeed. Tolerate that case, because it's
+# better than the alternative, which is to make the application
+# world-writable.
+CHMOD_MODE="a+rX,u+w,go-w"
+if [ ${EUID} -ne 0 ] ; then
+ if [ "${DEST:0:14}" = "/Applications/" ] &&
+ chgrp -Rh admin "${DEST}" >& /dev/null ; then
+ CHMOD_MODE="a+rX,ug+w,o-w"
+ fi
+else
+ chown -Rh root:wheel "${DEST}" >& /dev/null
+fi
+
+chmod -R "${CHMOD_MODE}" "${DEST}" >& /dev/null
+
+# On the Mac, or at least on HFS+, symbolic link permissions are significant,
+# but chmod -R and -h can't be used together. Do another pass to fix the
+# permissions on any symbolic links.
+find "${DEST}" -type l -exec chmod -h "${CHMOD_MODE}" {} + >& /dev/null
+
+# Host OS version check, to be able to take advantage of features on newer
+# systems and fall back to slow ways of doing things on older systems.
+OS_VERSION=$(sw_vers -productVersion)
+OS_MAJOR=$(sed -Ene 's/^([0-9]+).*/\1/p' <<< ${OS_VERSION})
+OS_MINOR=$(sed -Ene 's/^([0-9]+)\.([0-9]+).*/\2/p' <<< ${OS_VERSION})
+
+# If an update is triggered from within the application itself, the update
+# process inherits the quarantine bit (LSFileQuarantineEnabled). Any files or
+# directories created during the update will be quarantined in that case,
+# which may cause Launch Services to display quarantine UI. That's bad,
+# especially if it happens when the outer .app launches a quarantined inner
+# helper. If the application is already on the system and is being updated,
+# then it can be assumed that it should not be quarantined. Use xattr to drop
+# the quarantine attribute.
+#
+# TODO(mark): Instead of letting the quarantine attribute be set and then
+# dropping it here, figure out a way to get the update process to run without
+# LSFileQuarantineEnabled even when triggering an update from within the
+# application.
+QUARANTINE_ATTR=com.apple.quarantine
+if [ ${OS_MAJOR} -gt 10 ] ||
+ ([ ${OS_MAJOR} -eq 10 ] && [ ${OS_MINOR} -ge 6 ]) ; then
+ # On 10.6, xattr supports -r for recursive operation.
+ xattr -d -r "${QUARANTINE_ATTR}" "${DEST}" >& /dev/null
+else
+ # On earlier systems, xattr doesn't support -r, so run xattr via find.
+ find "${DEST}" -exec xattr -d "${QUARANTINE_ATTR}" {} + >& /dev/null
+fi
+
+# Great success!
+exit 0
diff --git a/chrome/installer/mac/make_sign_sh b/chrome/installer/mac/make_sign_sh
new file mode 100755
index 0000000..db6e1c2
--- /dev/null
+++ b/chrome/installer/mac/make_sign_sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script creates sign.sh, the script that will be used to sign the
+# application bundle and inner bundles. It also creates auxiliary files that
+# sign.sh needs to do its job, such as the custom resource rules used to sign
+# the outermost application bundle. These files are placed in the Packaging
+# directory next to the .app bundle. The packaging system is expected to run
+# sign.sh to sign everything.
+
+set -e
+
+if [ $# -ne 3 ] ; then
+ echo "usage: ${0} PACKAGING_DIR MAC_PRODUCT_NAME VERSION" >& 2
+ exit 1
+fi
+
+PACKAGING_DIR="${1}"
+MAC_PRODUCT_NAME="${2}"
+VERSION="${3}"
+
+INPUT_DIR="$(dirname "${0}")"
+SIGN_SH_IN_FILE="${INPUT_DIR}/sign.sh.in"
+SIGN_SH_FILE="${PACKAGING_DIR}/sign.sh"
+BROWSER_APP_RULES_IN_FILE="${INPUT_DIR}/app_resource_rules.plist.in"
+BROWSER_APP_RULES_FILE="${PACKAGING_DIR}/app_resource_rules.plist"
+
+# Double-backslash each dot: one backslash belongs in the regular expression,
+# and the other backslash tells sed not to treat the first backslash
+# specially.
+VERSION_REGEX="$(echo "${VERSION}" | sed -e 's/\./\\\\./g')"
+
+mkdir -p "${PACKAGING_DIR}"
+sed -e "s/@MAC_PRODUCT_NAME@/${MAC_PRODUCT_NAME}/g" \
+ -e "s/@VERSION@/${VERSION}/g" \
+ -e "s/@VERSION_REGEX@/${VERSION_REGEX}/g" \
+ < "${SIGN_SH_IN_FILE}" \
+ > "${SIGN_SH_FILE}"
+chmod +x "${SIGN_SH_FILE}"
+
+sed -e "s/@MAC_PRODUCT_NAME@/${MAC_PRODUCT_NAME}/g" \
+ -e "s/@VERSION@/${VERSION}/g" \
+ -e "s/@VERSION_REGEX@/${VERSION_REGEX}/g" \
+ < "${BROWSER_APP_RULES_IN_FILE}" \
+ > "${BROWSER_APP_RULES_FILE}"
diff --git a/chrome/installer/mac/sign.sh.in b/chrome/installer/mac/sign.sh.in
new file mode 100644
index 0000000..18c8197
--- /dev/null
+++ b/chrome/installer/mac/sign.sh.in
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+# Copyright (c) 2009 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Using codesign, sign the application. Inner components are signed as needed,
+# then the outermost bundle is signed, and everything is verified.
+
+set -e
+
+if [ $# -ne 3 ] ; then
+ echo "usage: ${0} APP_PATH CODESIGN_KEYCHAIN CODESIGN_ID" >& 2
+ exit 1
+fi
+
+APP_PATH="${1}"
+CODESIGN_KEYCHAIN="${2}"
+CODESIGN_ID="${3}"
+
+# Use custom resource rules for the browser application.
+BROWSER_APP_RULES="$(dirname "${0}")/app_resource_rules.plist"
+
+# An .app bundle to be signed can be signed directly. Normally, signging a
+# framework bundle requires that each version within be signed individually.
+# http://developer.apple.com/mac/library/technotes/tn2007/tn2206.html#TNTAG13
+# In Chrome's case, the framework bundle is unversioned, so it too can be
+# signed directly. See copy_framework_unversioned.
+
+BROWSER_APP="${APP_PATH}"
+FRAMEWORK="${BROWSER_APP}/Contents/Versions/@VERSION@/@MAC_PRODUCT_NAME@ Framework.framework"
+HELPER_APP="${BROWSER_APP}/Contents/Versions/@VERSION@/@MAC_PRODUCT_NAME@ Helper.app"
+
+echo "${0}: signing..."
+
+# Sign the outer .app last so that its seal includes the signed inner
+# components.
+
+codesign -s "${CODESIGN_ID}" --keychain "${CODESIGN_KEYCHAIN}" "${FRAMEWORK}"
+codesign -s "${CODESIGN_ID}" --keychain "${CODESIGN_KEYCHAIN}" "${HELPER_APP}"
+codesign -s "${CODESIGN_ID}" --keychain "${CODESIGN_KEYCHAIN}" \
+ "${BROWSER_APP}" --resource-rules "${BROWSER_APP_RULES}"
+
+# Verify everything to ensure that signing the outer bundle didn't break an
+# inner bundle.
+
+echo "${0}: verifying..."
+
+codesign -v "${FRAMEWORK}"
+codesign -v "${HELPER_APP}"
+codesign -v "${BROWSER_APP}"