summaryrefslogtreecommitdiffstats
path: root/chrome/installer/gcapi_mac
diff options
context:
space:
mode:
authorthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-24 16:34:37 +0000
committerthakis@chromium.org <thakis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-24 16:34:37 +0000
commit4a80767ed12331099fe3b1b464875d318d1da895 (patch)
treee3771f06cc76ff4cfa61726da2b8fa589aec86ed /chrome/installer/gcapi_mac
parented2e8624d702924934de4c180fbe555450ecdfd8 (diff)
downloadchromium_src-4a80767ed12331099fe3b1b464875d318d1da895.zip
chromium_src-4a80767ed12331099fe3b1b464875d318d1da895.tar.gz
chromium_src-4a80767ed12331099fe3b1b464875d318d1da895.tar.bz2
mac gcapi: Add a InstallGoogleChrome() function.
BUG=128462 TEST=none Review URL: https://chromiumcodereview.appspot.com/10407047 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138813 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/installer/gcapi_mac')
-rw-r--r--chrome/installer/gcapi_mac/gcapi.h32
-rw-r--r--chrome/installer/gcapi_mac/gcapi.mm260
2 files changed, 269 insertions, 23 deletions
diff --git a/chrome/installer/gcapi_mac/gcapi.h b/chrome/installer/gcapi_mac/gcapi.h
index 28a23aa..ec454b1 100644
--- a/chrome/installer/gcapi_mac/gcapi.h
+++ b/chrome/installer/gcapi_mac/gcapi.h
@@ -18,19 +18,31 @@
extern "C" {
#endif
-// This function returns YES if Google Chrome should be offered.
-// If the return is NO, |reasons| explains why. If you don't care for the
+// This function returns nonzero if Google Chrome should be offered.
+// If the return value is 0, |reasons| explains why. If you don't care for the
// reason, you can pass NULL for |reasons|.
-// |set_flag| indicates whether a flag should be set indicating that Chrome was
-// offered within the last six months; if passed NO, this method will not
-// set the flag even if Chrome can be offered. If passed TRUE, this method
-// will set the flag only if Chrome can be offered.
-// |shell_mode| should be set to one of GCAPI_INVOKED_STANDARD_SHELL or
-// GCAPI_INVOKED_UAC_ELEVATION depending on whether this method is invoked
-// from an elevated or non-elevated process. TODO(thakis): This doesn't make
-// sense on mac, change comment.
int GoogleChromeCompatibilityCheck(unsigned* reasons);
+// This function installs Google Chrome in the application folder and optionally
+// sets up the brand code and master prefs.
+// |source_path| Path to an uninstalled Google Chrome.app directory, for example
+// in a mounted dmg, in file system representation.
+// |brand_code| If not NULL, a string containing the brand code Google Chrome
+// should use. Has no effect if Google Chrome has an embedded brand
+// code. Overwrites existing brand files.
+// |master_prefs_contents| If not NULL, the _contents_ of a master prefs file
+// Google Chrome should use. This is not a path.
+// Overwrites existing master pref files.
+// Returns nonzero if Google Chrome was successfully copied. If copying
+// succeeded but writing of master prefs, brand code, or other noncrucial
+// setup tasks fail, this still returns nonzero.
+// Returns 0 if the installation failed, for example if Google Chrome was
+// already installed, or no disk space was left.
+int InstallGoogleChrome(const char* source_path,
+ const char* brand_code,
+ const char* master_prefs_contents,
+ unsigned master_prefs_contents_size);
+
// This function launches Google Chrome after a successful install.
int LaunchGoogleChrome();
diff --git a/chrome/installer/gcapi_mac/gcapi.mm b/chrome/installer/gcapi_mac/gcapi.mm
index 12413f5..226fb46 100644
--- a/chrome/installer/gcapi_mac/gcapi.mm
+++ b/chrome/installer/gcapi_mac/gcapi.mm
@@ -5,10 +5,34 @@
#include "chrome/installer/gcapi_mac/gcapi.h"
#import <Cocoa/Cocoa.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/utsname.h>
namespace {
+NSString* const kChromeInstallPath = @"/Applications/Google Chrome.app";
+
+NSString* const kBrandKey = @"KSBrandID";
+NSString* const kSystemBrandPath = @"/Library/Google/Google Chrome Brand.plist";
+NSString* const kUserBrandPath = @"~/Library/Google/Google Chrome Brand.plist";
+
+NSString* const kSystemKsadminPath =
+ @"/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
+ "Contents/MacOS/ksadmin";
+
+NSString* const kUserKsadminPath =
+ @"~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
+ "Contents/MacOS/ksadmin";
+
+NSString* const kSystemMasterPrefsPath =
+ @"/Library/Google/Google Chrome Master Preferences";
+NSString* const kUserMasterPrefsPath =
+ @"~/Library/Application Support/Google/Google Chrome Master Preferences";
+
+NSString* const kChannelKey = @"KSChannelID";
+NSString* const kVersionKey = @"KSVersion";
+
// Condensed from chromium's base/mac/mac_util.mm.
bool IsOSXLeopardOrLater() {
// On 10.6, Gestalt() was observed to be able to spawn threads (see
@@ -35,14 +59,6 @@ bool IsOSXLeopardOrLater() {
return mac_os_x_minor_version >= 5;
}
-NSString* const kSystemKsadminPath =
- @"/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
- "Contents/MacOS/ksadmin";
-
-NSString* const kUserKsadminPath =
- @"~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
- "Contents/MacOS/ksadmin";
-
enum TicketKind {
kSystemTicket, kUserTicket
};
@@ -50,9 +66,9 @@ enum TicketKind {
BOOL HasChromeTicket(TicketKind kind) {
// Don't use Objective-C 2 loop syntax, in case an installer runs on 10.4.
NSMutableArray* keystonePaths =
- [NSMutableArray arrayWithObject:kUserKsadminPath];
- if (kind == kSystemTicket)
- [keystonePaths insertObject:kSystemKsadminPath atIndex:0];
+ [NSMutableArray arrayWithObject:kSystemKsadminPath];
+ if (kind == kUserTicket && geteuid() != 0)
+ [keystonePaths insertObject:kUserKsadminPath atIndex:0];
NSEnumerator* e = [keystonePaths objectEnumerator];
id ksPath;
while ((ksPath = [e nextObject])) {
@@ -98,6 +114,133 @@ BOOL HasChromeTicket(TicketKind kind) {
return NO;
}
+// Returns the file permission mask for files created by gcapi.
+mode_t Permissions() {
+ return 0755;
+}
+
+BOOL CreatePathToFile(NSString* path) {
+ path = [path stringByDeletingLastPathComponent];
+
+ // Default owner, group, permissions:
+ // * Permissions are set according to the umask of the current process. For
+ // more information, see umask.
+ // * The owner ID is set to the effective user ID of the process.
+ // * The group ID is set to that of the parent directory.
+ // The default group ID is fine. Owner ID is fine too, since user directory
+ // paths won't be created if euid is 0. Do set permissions explicitly; for
+ // admin paths all admins can write, for user paths just the owner may.
+ NSMutableDictionary* attributes = [NSMutableDictionary
+ dictionaryWithObject:[NSNumber numberWithShort:Permissions()]
+ forKey:NSFilePosixPermissions];
+ if (geteuid() == 0)
+ [attributes setObject:@"wheel" forKey:NSFileGroupOwnerAccountName];
+
+ NSFileManager* manager = [NSFileManager defaultManager];
+ return [manager createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:attributes
+ error:nil];
+}
+
+// Tries to write |data| at |system_path| or if that fails and geteuid() is not
+// 0 at |user_path|. Returns the path where it wrote, or nil on failure.
+NSString* WriteData(NSData* data, NSString* system_path, NSString* user_path) {
+ // Try system first.
+ if (CreatePathToFile(system_path) &&
+ [data writeToFile:system_path atomically:YES]) {
+ // files are created with group of parent dir (good), owner of euid (good).
+ chmod([system_path fileSystemRepresentation], Permissions() & ~0111);
+ return system_path;
+ }
+
+ // Failed, try user.
+ // -stringByExpandingTildeInPath returns root's home directory if this is run
+ // setuid root, and in that case the kSystemBrandPath path above should have
+ // worked anyway. So only try user if geteuid() isn't root.
+ if (geteuid() != 0) {
+ NSString* user_path = [user_path stringByExpandingTildeInPath];
+ if (CreatePathToFile(user_path) &&
+ [data writeToFile:user_path atomically:YES]) {
+ chmod([user_path fileSystemRepresentation], Permissions() & ~0111);
+ return user_path;
+ }
+ }
+ return nil;
+}
+
+NSString* WriteBrandCode(const char* brand_code) {
+ NSDictionary* brand_dict = @{
+ kBrandKey: [NSString stringWithUTF8String:brand_code],
+ };
+ NSData* contents = [NSPropertyListSerialization
+ dataFromPropertyList:brand_dict
+ format:NSPropertyListBinaryFormat_v1_0
+ errorDescription:nil];
+
+ return WriteData(contents, kSystemBrandPath, kUserBrandPath);
+}
+
+BOOL WriteMasterPrefs(const char* master_prefs_contents,
+ size_t master_prefs_contents_size) {
+ NSData* contents = [NSData dataWithBytes:master_prefs_contents
+ length:master_prefs_contents_size];
+ return
+ WriteData(contents, kSystemMasterPrefsPath, kUserMasterPrefsPath) != nil;
+}
+
+NSString* PathToFramework(NSString* app_path, NSDictionary* info_plist) {
+ NSString* version = [info_plist objectForKey:(NSString*)kCFBundleVersionKey];
+ if (!version)
+ return nil;
+ return [[[app_path
+ stringByAppendingPathComponent:@"Contents/Versions"]
+ stringByAppendingPathComponent:version]
+ stringByAppendingPathComponent:@"Google Chrome Framework.framework"];
+}
+
+NSString* PathToInstallScript(NSString* app_path, NSDictionary* info_plist) {
+ return [PathToFramework(app_path, info_plist) stringByAppendingPathComponent:
+ @"Contents/Resources/install.sh"];
+}
+
+NSString* PathToKeystoneResources(
+ NSString* app_path, NSDictionary* info_plist) {
+ return [PathToFramework(app_path, info_plist) stringByAppendingPathComponent:
+ @"Frameworks/KeystoneRegistration.framework/Resources"];
+}
+
+NSString* FindOrInstallKeystone(NSString* app_path, NSDictionary* info_plist) {
+ NSString* ks_path = geteuid() == 0 ?
+ kSystemKsadminPath : [kUserKsadminPath stringByExpandingTildeInPath];
+
+ // Always run install.py. It won't overwrite an existing keystone, but
+ // it might update it or repair a broken existing installation.
+ NSString* ks_resources = PathToKeystoneResources(app_path, info_plist);
+ NSString* ks_install =
+ [ks_resources stringByAppendingPathComponent:@"install.py"];
+ NSString* ks_tbz =
+ [ks_resources stringByAppendingPathComponent:@"Keystone.tbz"];
+ @try {
+ NSTask* task = [[[NSTask alloc] init] autorelease];
+ [task setLaunchPath:ks_install];
+ [task setArguments:@[ks_tbz]];
+ [task launch];
+ [task waitUntilExit];
+ if ([task terminationStatus] == 0)
+ return ks_path;
+ }
+ @catch (id exception) {
+ // Ignore.
+ }
+ return nil;
+}
+
+bool isbrandchar(int c) {
+ // Always four upper-case alpha chars.
+ return c >= 'A' && c <= 'Z';
+}
+
} // namespace
int GoogleChromeCompatibilityCheck(unsigned* reasons) {
@@ -114,6 +257,9 @@ int GoogleChromeCompatibilityCheck(unsigned* reasons) {
if (HasChromeTicket(kUserTicket))
local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;
+ if (![[NSFileManager defaultManager] isWritableFileAtPath:@"/Applications"])
+ local_reasons |= GCCC_ERROR_ACCESSDENIED;
+
[pool drain];
// Done. Copy/return results.
@@ -123,9 +269,97 @@ int GoogleChromeCompatibilityCheck(unsigned* reasons) {
return local_reasons == 0;
}
+int InstallGoogleChrome(const char* source_path,
+ const char* brand_code,
+ const char* master_prefs_contents,
+ unsigned master_prefs_contents_size) {
+ if (!GoogleChromeCompatibilityCheck(NULL))
+ return 0;
+
+ @autoreleasepool {
+ NSString* app_path = [NSString stringWithUTF8String:source_path];
+ NSString* info_plist_path =
+ [app_path stringByAppendingPathComponent:@"Contents/Info.plist"];
+ NSDictionary* info_plist =
+ [NSDictionary dictionaryWithContentsOfFile:info_plist_path];
+
+ // Use install.sh from the Chrome app bundle to copy Chrome to its
+ // destination.
+ NSString* install_script = PathToInstallScript(app_path, info_plist);
+ if (!install_script)
+ return 0;
+
+ @try {
+ NSTask* task = [[[NSTask alloc] init] autorelease];
+ [task setLaunchPath:install_script];
+ [task setArguments:@[app_path, kChromeInstallPath]];
+ [task launch];
+ [task waitUntilExit];
+ if ([task terminationStatus] != 0)
+ return 0;
+ }
+ @catch (id exception) {
+ return 0;
+ }
+
+ // Set brand code. If Chrome's Info.plist contains a brand code, use that.
+ NSString* info_plist_brand = [info_plist objectForKey:kBrandKey];
+ if (info_plist_brand &&
+ [info_plist_brand respondsToSelector:@selector(UTF8String)])
+ brand_code = [info_plist_brand UTF8String];
+
+ BOOL valid_brand_code = strlen(brand_code) == 4 &&
+ isbrandchar(brand_code[0]) && isbrandchar(brand_code[1]) &&
+ isbrandchar(brand_code[2]) && isbrandchar(brand_code[3]);
+
+ NSString* brand_path = nil;
+ if (brand_code && valid_brand_code)
+ brand_path = WriteBrandCode(brand_code);
+
+ // Write master prefs.
+ if (master_prefs_contents)
+ WriteMasterPrefs(master_prefs_contents, master_prefs_contents_size);
+
+ // Install Keystone if necessary.
+ if (NSString* keystone = FindOrInstallKeystone(app_path, info_plist)) {
+ // Register Chrome with Keystone.
+ @try {
+ NSTask* task = [[[NSTask alloc] init] autorelease];
+ [task setLaunchPath:keystone];
+ NSString* tag = [info_plist objectForKey:kChannelKey];
+ [task setArguments:@[
+ @"--register",
+ @"--productid", [info_plist objectForKey:@"KSProductID"],
+ @"--version", [info_plist objectForKey:kVersionKey],
+ @"--xcpath", kChromeInstallPath,
+ @"--url", [info_plist objectForKey:@"KSUpdateURL"],
+
+ @"--tag", tag ? tag : @"", // Stable channel
+ @"--tag-path", info_plist_path,
+ @"--tag-key", kChannelKey,
+
+ @"--brand-path", brand_path ? brand_path : @"",
+ @"--brand-key", brand_path ? kBrandKey: @"",
+
+ @"--version-path", info_plist_path,
+ @"--version-key", kVersionKey,
+ ]];
+ [task launch];
+ [task waitUntilExit];
+ }
+ @catch (id exception) {
+ // Chrome will try to register keystone on launch.
+ }
+ }
+
+ // TODO Set default browser if requested. Will be tricky when running as
+ // root.
+ }
+ return 1;
+}
+
int LaunchGoogleChrome() {
@autoreleasepool {
- return [[NSWorkspace sharedWorkspace]
- launchApplication:@"/Applications/Google Chrome.app"];
+ return [[NSWorkspace sharedWorkspace] launchApplication:kChromeInstallPath];
}
}