summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-03 23:37:25 +0000
committermark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-03 23:37:25 +0000
commit366986e3ba2b792ca701689747900480fa36fbba (patch)
tree1817e520c041f707bd87f5f3d18be28abb4aff36
parent5baaeb28091dd6c386ab6535fe9f8c41793976e1 (diff)
downloadchromium_src-366986e3ba2b792ca701689747900480fa36fbba.zip
chromium_src-366986e3ba2b792ca701689747900480fa36fbba.tar.gz
chromium_src-366986e3ba2b792ca701689747900480fa36fbba.tar.bz2
Expand the Keystone tag to contain the system's CPU's bitness and whether a
full installer is desired. Formerly, the tag identified only the channel that Chrome was on. The tag is being enhanced to detect the CPU's bitness (adding "-32bit" for 32-bit-only, non-64-bit-capable CPUs) and whether a full (as opposed to binary diff patch) update is requested (adding "-full"). CPU bitness detection ought to be a feature of Keystone, but Keystone uses the NXGetLocalArchInfo to determine the CPU type, and winds up always reporting "i486". The "-32bit" tag suffix will be present whenever the "hw.cpu64bit_capable" sysctl name is not found or has value 0. This enables proper detection of users who are capable of running 64-bit Chrome on the server side. When a binary diff patch update application fails in dirpatcher, typically the result of modifications made to existing installations, the "-full" tag suffix will be set. On a subsequent update attempt, the server can detect this value and provide the client with a full updater package, which does not depend on the existing installation. The "-full" tag suffix is cleared on successful update installation. BUG=18323,54047,225352,303280,316916 R=thakis@chromium.org Review URL: https://codereview.chromium.org/102963007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@242964 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xbuild/mac/tweak_info_plist.py28
-rw-r--r--chrome/browser/mac/keystone_glue.mm83
-rwxr-xr-xchrome/installer/mac/keystone_install.sh162
3 files changed, 261 insertions, 12 deletions
diff --git a/build/mac/tweak_info_plist.py b/build/mac/tweak_info_plist.py
index eb38ace..0f65e4a 100755
--- a/build/mac/tweak_info_plist.py
+++ b/build/mac/tweak_info_plist.py
@@ -204,6 +204,24 @@ def _RemoveBreakpadKeys(plist):
'BreakpadSkipConfirm')
+def _TagSuffixes():
+ # Keep this list sorted in the order that tag suffix components are to
+ # appear in a tag value. That is to say, it should be sorted per ASCII.
+ components = ('32bit', 'full')
+ assert tuple(sorted(components)) == components
+
+ components_len = len(components)
+ combinations = 1 << components_len
+ tag_suffixes = []
+ for combination in xrange(0, combinations):
+ tag_suffix = ''
+ for component_index in xrange(0, components_len):
+ if combination & (1 << component_index):
+ tag_suffix += '-' + components[component_index]
+ tag_suffixes.append(tag_suffix)
+ return tag_suffixes
+
+
def _AddKeystoneKeys(plist, bundle_identifier):
"""Adds the Keystone keys. This must be called AFTER _AddVersionKeys() and
also requires the |bundle_identifier| argument (com.example.product)."""
@@ -211,6 +229,11 @@ def _AddKeystoneKeys(plist, bundle_identifier):
plist['KSProductID'] = bundle_identifier
plist['KSUpdateURL'] = 'https://tools.google.com/service/update2'
+ _RemoveKeys(plist, 'KSChannelID')
+ for tag_suffix in _TagSuffixes():
+ if tag_suffix:
+ plist['KSChannelID' + tag_suffix] = tag_suffix
+
def _RemoveKeystoneKeys(plist):
"""Removes any set Keystone keys."""
@@ -219,6 +242,11 @@ def _RemoveKeystoneKeys(plist):
'KSProductID',
'KSUpdateURL')
+ tag_keys = []
+ for tag_suffix in _TagSuffixes():
+ tag_keys.append('KSChannelID' + tag_suffix)
+ _RemoveKeys(plist, *tag_keys)
+
def Main(argv):
parser = optparse.OptionParser('%prog [options]')
diff --git a/chrome/browser/mac/keystone_glue.mm b/chrome/browser/mac/keystone_glue.mm
index 3a4d59e..dea4bb4 100644
--- a/chrome/browser/mac/keystone_glue.mm
+++ b/chrome/browser/mac/keystone_glue.mm
@@ -7,6 +7,8 @@
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
#include <vector>
@@ -22,6 +24,7 @@
#include "base/memory/ref_counted.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/worker_pool.h"
+#include "build/build_config.h"
#import "chrome/browser/mac/keystone_registration.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_version_info.h"
@@ -188,6 +191,22 @@ class PerformBridge : public base::RefCountedThreadSafe<PerformBridge> {
// Returns the brand file path to use for Keystone.
- (NSString*)brandFilePath;
+// YES if the system's CPU is 32-bit-only, NO if it's 64-bit-capable.
+- (BOOL)has32BitOnlyCPU;
+
+// YES if no update installation has succeeded since a binary diff patch
+// installation failed. This signals the need to attempt a full installer
+// which does not depend on applying a patch to existing files.
+- (BOOL)wantsFullInstaller;
+
+// Returns an NSString* suitable for appending to a Chrome Keystone tag value
+// or tag key. If the system has a 32-bit-only CPU, the tag suffix will
+// contain the string "-32bit". If a full installer (as opposed to a binary
+// diff/delta patch) is required, the tag suffix will contain the string
+// "-full". If no special treatment is required, the tag suffix will be an
+// empty string.
+- (NSString*)tagSuffix;
+
@end // @interface KeystoneGlue (Private)
NSString* const kAutoupdateStatusNotification = @"AutoupdateStatusNotification";
@@ -453,6 +472,13 @@ NSString* const kVersionKey = @"KSVersion";
brandKey = @"";
}
+ // Note that channel_ is permitted to be an empty string, but it must not be
+ // nil.
+ DCHECK(channel_);
+ NSString* tagSuffix = [self tagSuffix];
+ NSString* tagValue = [channel_ stringByAppendingString:tagSuffix];
+ NSString* tagKey = [kChannelKey stringByAppendingString:tagSuffix];
+
return [NSDictionary dictionaryWithObjectsAndKeys:
version_, ksr::KSRegistrationVersionKey,
appInfoPlistPath, ksr::KSRegistrationVersionPathKey,
@@ -461,9 +487,9 @@ NSString* const kVersionKey = @"KSVersion";
appPath_, ksr::KSRegistrationExistenceCheckerStringKey,
url_, ksr::KSRegistrationServerURLStringKey,
preserveTTToken, ksr::KSRegistrationPreserveTrustedTesterTokenKey,
- channel_, ksr::KSRegistrationTagKey,
+ tagValue, ksr::KSRegistrationTagKey,
appInfoPlistPath, ksr::KSRegistrationTagPathKey,
- kChannelKey, ksr::KSRegistrationTagKeyKey,
+ tagKey, ksr::KSRegistrationTagKeyKey,
brandPath, ksr::KSRegistrationBrandPathKey,
brandKey, ksr::KSRegistrationBrandKeyKey,
nil];
@@ -983,6 +1009,59 @@ NSString* const kVersionKey = @"KSVersion";
}
}
+- (BOOL)has32BitOnlyCPU {
+#if defined(ARCH_CPU_64_BITS)
+ return NO;
+#else // ARCH_CPU_64_BITS
+ int value;
+ size_t valueSize = sizeof(value);
+ if (sysctlbyname("hw.cpu64bit_capable", &value, &valueSize, NULL, 0) != 0) {
+ return YES;
+ }
+ return value == 0;
+#endif // ARCH_CPU_64_BITS
+}
+
+- (BOOL)wantsFullInstaller {
+ // It's difficult to check the tag prior to Keystone registration, and
+ // performing registration replaces the tag. keystone_install.sh
+ // communicates a need for a full installer with Chrome in this file,
+ // .want_full_installer.
+ NSString* wantFullInstallerPath =
+ [appPath_ stringByAppendingPathComponent:@".want_full_installer"];
+ NSString* wantFullInstallerContents =
+ [NSString stringWithContentsOfFile:wantFullInstallerPath
+ encoding:NSUTF8StringEncoding
+ error:NULL];
+ if (!wantFullInstallerContents) {
+ return NO;
+ }
+
+ NSString* wantFullInstallerVersion =
+ [wantFullInstallerContents stringByTrimmingCharactersInSet:
+ [NSCharacterSet newlineCharacterSet]];
+ return [wantFullInstallerVersion isEqualToString:version_];
+}
+
+- (NSString*)tagSuffix {
+ // Tag suffix components are not entirely arbitrary: all possible tag keys
+ // must be present in the application's Info.plist, there must be
+ // server-side agreement on the processing and meaning of tag suffix
+ // components, and other code that manipulates tag values (such as the
+ // Keystone update installation script) must be tag suffix-aware. To reduce
+ // the number of tag suffix combinations that need to be listed in
+ // Info.plist, tag suffix components should only be appended to the tag
+ // suffix in ASCII sort order.
+ NSString* tagSuffix = @"";
+ if ([self has32BitOnlyCPU]) {
+ tagSuffix = [tagSuffix stringByAppendingString:@"-32bit"];
+ }
+ if ([self wantsFullInstaller]) {
+ tagSuffix = [tagSuffix stringByAppendingString:@"-full"];
+ }
+ return tagSuffix;
+}
+
@end // @implementation KeystoneGlue
namespace {
diff --git a/chrome/installer/mac/keystone_install.sh b/chrome/installer/mac/keystone_install.sh
index 1dbb6ed..401e65f 100755
--- a/chrome/installer/mac/keystone_install.sh
+++ b/chrome/installer/mac/keystone_install.sh
@@ -29,13 +29,10 @@
# 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
-# 12 dirpatcher failed for versioned directory
-# 13 dirpatcher failed for outer .app bundle
#
-# The following exit codes are not used by this script, but can be used to
-# convey special meaning to Keystone:
+# The following exit codes can be used to convey special meaning to Keystone:
# 66 (unused) success, request reboot
-# 77 (unused) try installation again later
+# 77 try installation again later
set -eu
@@ -61,6 +58,8 @@ shopt -s nullglob
ME="$(basename "${0}")"
readonly ME
+readonly KS_CHANNEL_KEY="KSChannelID"
+
# Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3
# In bash 4.0, "declare VAR" no longer initializes VAR if not already set.
: ${GOOGLE_CHROME_UPDATER_DEBUG:=}
@@ -451,6 +450,14 @@ ksadmin_supports_versionpath_versionkey() {
# return value.
}
+has_32_bit_only_cpu() {
+ local cpu_64_bit_capable="$(sysctl -n hw.cpu64bit_capable 2>/dev/null)"
+ [[ -z "${cpu_64_bit_capable}" || "${cpu_64_bit_capable}" -eq 0 ]]
+
+ # The return value of the comparison is used as this function's return
+ # value.
+}
+
# Runs "defaults read" to obtain the value of a key in a property list. As
# with "defaults read", an absolute path to a plist is supplied, without the
# ".plist" extension.
@@ -484,6 +491,115 @@ infoplist_read() {
__CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
}
+# When a patch update fails because the old installed copy doesn't match the
+# expected state, mark_failed_patch_update updates the Keystone ticket by
+# adding "-full" to the tag. The server will see this on a subsequent update
+# attempt and will provide a full update (as opposed to a patch) to the
+# client.
+#
+# Even if mark_failed_patch_update fails to modify the tag, the user will
+# eventually be updated. Patch updates are only provided for successive
+# releases on a particular channel, to update version o to version o+1. If a
+# patch update fails in this case, eventually version o+2 will be released,
+# and no patch update will exist to update o to o+2, so the server will
+# provide a full update package.
+mark_failed_patch_update() {
+ local product_id="${1}"
+ local want_full_installer_path="${2}"
+ local old_ks_plist="${3}"
+ local old_version_app="${4}"
+ local system_ticket="${5}"
+
+ set +e
+
+ note "marking failed patch update"
+
+ local channel
+ channel="$(infoplist_read "${old_ks_plist}" "${KS_CHANNEL_KEY}" 2> /dev/null)"
+
+ local tag="${channel}"
+ local tag_key="${KS_CHANNEL_KEY}"
+ if has_32_bit_only_cpu; then
+ tag="${tag}-32bit"
+ tag_key="${tag_key}-32bit"
+ fi
+
+ tag="${tag}-full"
+ tag_key="${tag_key}-full"
+
+ note "tag = ${tag}"
+ note "tag_key = ${tag_key}"
+
+ # ${old_ks_plist}, used for --tag-path, is the Info.plist for the old
+ # version of Chrome. It may not contain the keys for the "-full" tag suffix.
+ # If it doesn't, just bail out without marking the patch update as failed.
+ local read_tag="$(infoplist_read "${old_ks_plist}" "${tag_key}" 2> /dev/null)"
+ note "read_tag = ${read_tag}"
+ if [[ -z "${read_tag}" ]]; then
+ note "couldn't mark failed patch update"
+ return 0
+ fi
+
+ # Chrome can't easily read its Keystone ticket prior to registration, and
+ # when Chrome registers with Keystone, it obliterates old tag values in its
+ # ticket. Therefore, an alternative mechanism is provided to signal to
+ # Chrome that a full installer is desired. If the .want_full_installer file
+ # is present and it contains Chrome's current version number, Chrome will
+ # include "-full" in its tag when it registers with Keystone. This allows
+ # "-full" to persist in the tag even after Chrome is relaunched, which on a
+ # user ticket, triggers a re-registration.
+ #
+ # .want_full_installer is placed immediately inside the .app bundle as a
+ # sibling to the Contents directory. In this location, it's outside of the
+ # view of the code signing and code signature verification machinery. This
+ # file can safely be added, modified, and removed without affecting the
+ # signature.
+ rm -f "${want_full_installer_path}" 2> /dev/null
+ echo "${old_version_app}" > "${want_full_installer_path}"
+
+ # See the comment below in the "setting permissions" section for an
+ # explanation of the groups and modes selected here.
+ local chmod_mode="644"
+ if [[ -z "${system_ticket}" ]] &&
+ [[ "${want_full_installer_path:0:14}" = "/Applications/" ]] &&
+ chgrp admin "${want_full_installer_path}" 2> /dev/null; then
+ chmod_mode="664"
+ fi
+ note "chmod_mode = ${chmod_mode}"
+ chmod "${chmod_mode}" "${want_full_installer_path}" 2> /dev/null
+
+ local old_ks_plist_path="${old_ks_plist}.plist"
+
+ # Using ksadmin without --register only updates specified values in the
+ # ticket, without changing other existing values.
+ local ksadmin_args=(
+ --productid "${product_id}"
+ )
+
+ if ksadmin_supports_tag; then
+ ksadmin_args+=(
+ --tag "${tag}"
+ )
+ fi
+
+ if ksadmin_supports_tagpath_tagkey; then
+ ksadmin_args+=(
+ --tag-path "${old_ks_plist_path}"
+ --tag-key "${tag_key}"
+ )
+ fi
+
+ note "ksadmin_args = ${ksadmin_args[*]}"
+
+ if ! ksadmin "${ksadmin_args[@]}"; then
+ err "ksadmin failed"
+ fi
+
+ note "marked failed patch update"
+
+ set -e
+}
+
usage() {
echo "usage: ${ME} update_dmg_mount_point" >& 2
}
@@ -513,7 +629,6 @@ main() {
readonly KS_VERSION_KEY="KSVersion"
readonly KS_PRODUCT_KEY="KSProductID"
readonly KS_URL_KEY="KSUpdateURL"
- readonly KS_CHANNEL_KEY="KSChannelID"
readonly KS_BRAND_KEY="KSBrandID"
readonly QUARANTINE_ATTR="com.apple.quarantine"
@@ -740,6 +855,9 @@ main() {
fi
note "installed_app = ${installed_app}"
+ local want_full_installer_path="${installed_app}/.want_full_installer"
+ note "want_full_installer_path = ${want_full_installer_path}"
+
if [[ "${installed_app:0:1}" != "/" ]] ||
! [[ -d "${installed_app}" ]]; then
err "installed_app must be an absolute path to a directory"
@@ -902,7 +1020,12 @@ main() {
"${patch_versioned_dir}" \
"${versioned_dir_target}"; then
err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
- exit 12
+ mark_failed_patch_update "${product_id}" \
+ "${want_full_installer_path}" \
+ "${old_ks_plist}" \
+ "${old_version_app}" \
+ "${system_ticket}"
+ exit 77
fi
fi
@@ -956,7 +1079,12 @@ main() {
"${patch_app_dir}" \
"${update_app}"; then
err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
- exit 13
+ mark_failed_patch_update "${product_id}" \
+ "${want_full_installer_path}" \
+ "${old_ks_plist}" \
+ "${old_version_app}" \
+ "${system_ticket}"
+ exit 77
fi
fi
@@ -1004,6 +1132,11 @@ main() {
note "g_temp_dir = ${g_temp_dir}"
fi
+ # Clean up any old .want_full_installer files from previous dirpatcher
+ # failures. This is not considered a critical step, because this file
+ # normally does not exist at all.
+ rm -f "${want_full_installer_path}" || true
+
# 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
@@ -1057,6 +1190,15 @@ main() {
"${KS_CHANNEL_KEY}" 2> /dev/null || true)"
note "channel = ${channel}"
+ local tag="${channel}"
+ local tag_key="${KS_CHANNEL_KEY}"
+ if has_32_bit_only_cpu; then
+ tag="${tag}-32bit"
+ tag_key="${tag_key}-32bit"
+ fi
+ note "tag = ${tag}"
+ note "tag_key = ${tag_key}"
+
# 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}" != "${update_version_ks}" ]]; then
@@ -1165,14 +1307,14 @@ main() {
if ksadmin_supports_tag; then
ksadmin_args+=(
- --tag "${channel}"
+ --tag "${tag}"
)
fi
if ksadmin_supports_tagpath_tagkey; then
ksadmin_args+=(
--tag-path "${installed_app_plist_path}"
- --tag-key "${KS_CHANNEL_KEY}"
+ --tag-key "${tag_key}"
)
fi