| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/app/keystone_glue.h" | 5 #import "chrome/browser/cocoa/keystone_glue.h" |
| 6 | 6 |
| 7 #include <sys/param.h> |
| 8 #include <sys/mount.h> |
| 9 |
| 10 #include <vector> |
| 11 |
| 12 #import "app/l10n_util_mac.h" |
| 7 #include "base/logging.h" | 13 #include "base/logging.h" |
| 8 #include "base/mac_util.h" | 14 #include "base/mac_util.h" |
| 9 #import "base/worker_pool_mac.h" | 15 #import "base/worker_pool_mac.h" |
| 16 #include "chrome/browser/cocoa/authorization_util.h" |
| 10 #include "chrome/common/chrome_constants.h" | 17 #include "chrome/common/chrome_constants.h" |
| 18 #include "grit/chromium_strings.h" |
| 19 #include "grit/generated_resources.h" |
| 11 | 20 |
| 12 namespace { | 21 namespace { |
| 13 | 22 |
| 14 // Provide declarations of the Keystone registration bits needed here. From | 23 // Provide declarations of the Keystone registration bits needed here. From |
| 15 // KSRegistration.h. | 24 // KSRegistration.h. |
| 16 typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType; | 25 typedef enum { |
| 26 kKSPathExistenceChecker, |
| 27 } KSExistenceCheckerType; |
| 28 |
| 29 typedef enum { |
| 30 kKSRegistrationUserTicket, |
| 31 kKSRegistrationSystemTicket, |
| 32 kKSRegistrationDontKnowWhatKindOfTicket, |
| 33 } KSRegistrationTicketType; |
| 34 |
| 35 NSString *KSRegistrationDidCompleteNotification = |
| 36 @"KSRegistrationDidCompleteNotification"; |
| 37 NSString *KSRegistrationPromotionDidCompleteNotification = |
| 38 @"KSRegistrationPromotionDidCompleteNotification"; |
| 17 | 39 |
| 18 NSString *KSRegistrationCheckForUpdateNotification = | 40 NSString *KSRegistrationCheckForUpdateNotification = |
| 19 @"KSRegistrationCheckForUpdateNotification"; | 41 @"KSRegistrationCheckForUpdateNotification"; |
| 20 NSString *KSRegistrationStatusKey = @"Status"; | 42 NSString *KSRegistrationStatusKey = @"Status"; |
| 21 NSString *KSRegistrationVersionKey = @"Version"; | 43 NSString *KSRegistrationVersionKey = @"Version"; |
| 22 NSString *KSRegistrationUpdateCheckErrorKey = @"Error"; | 44 NSString *KSRegistrationUpdateCheckErrorKey = @"Error"; |
| 23 | 45 |
| 24 NSString *KSRegistrationStartUpdateNotification = | 46 NSString *KSRegistrationStartUpdateNotification = |
| 25 @"KSRegistrationStartUpdateNotification"; | 47 @"KSRegistrationStartUpdateNotification"; |
| 26 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful"; | 48 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful"; |
| 27 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; | 49 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; |
| 28 | 50 |
| 29 NSString *KSRegistrationRemoveExistingTag = @""; | 51 NSString *KSRegistrationRemoveExistingTag = @""; |
| 30 #define KSRegistrationPreserveExistingTag nil | 52 #define KSRegistrationPreserveExistingTag nil |
| 31 | 53 |
| 32 } // namespace | 54 } // namespace |
| 33 | 55 |
| 34 @interface KSRegistration : NSObject | 56 @interface KSRegistration : NSObject |
| 35 | 57 |
| 36 + (id)registrationWithProductID:(NSString*)productID; | 58 + (id)registrationWithProductID:(NSString*)productID; |
| 37 | 59 |
| 38 - (BOOL)registerWithVersion:(NSString*)version | 60 - (BOOL)registerWithVersion:(NSString*)version |
| 39 existenceCheckerType:(KSExistenceCheckerType)xctype | 61 existenceCheckerType:(KSExistenceCheckerType)xctype |
| 40 existenceCheckerString:(NSString*)xc | 62 existenceCheckerString:(NSString*)xc |
| 41 serverURLString:(NSString*)serverURLString | 63 serverURLString:(NSString*)serverURLString |
| 42 preserveTTToken:(BOOL)preserveToken | 64 preserveTTToken:(BOOL)preserveToken |
| 43 tag:(NSString*)tag; | 65 tag:(NSString*)tag; |
| 44 | 66 |
| 67 - (BOOL)promoteWithVersion:(NSString*)version |
| 68 existenceCheckerType:(KSExistenceCheckerType)xctype |
| 69 existenceCheckerString:(NSString*)xc |
| 70 serverURLString:(NSString*)serverURLString |
| 71 preserveTTToken:(BOOL)preserveToken |
| 72 tag:(NSString*)tag |
| 73 authorization:(AuthorizationRef)authorization; |
| 74 |
| 45 - (void)setActive; | 75 - (void)setActive; |
| 46 - (void)checkForUpdate; | 76 - (void)checkForUpdate; |
| 47 - (void)startUpdate; | 77 - (void)startUpdate; |
| 78 - (KSRegistrationTicketType)ticketType; |
| 48 | 79 |
| 49 @end // @interface KSRegistration | 80 @end // @interface KSRegistration |
| 50 | 81 |
| 51 @interface KeystoneGlue(Private) | 82 @interface KeystoneGlue(Private) |
| 52 | 83 |
| 84 // Called when Keystone registration completes. |
| 85 - (void)registrationComplete:(NSNotification*)notification; |
| 86 |
| 53 // Called periodically to announce activity by pinging the Keystone server. | 87 // Called periodically to announce activity by pinging the Keystone server. |
| 54 - (void)markActive:(NSTimer*)timer; | 88 - (void)markActive:(NSTimer*)timer; |
| 55 | 89 |
| 56 // Called when an update check or update installation is complete. Posts the | 90 // Called when an update check or update installation is complete. Posts the |
| 57 // kAutoupdateStatusNotification notification to the default notification | 91 // kAutoupdateStatusNotification notification to the default notification |
| 58 // center. | 92 // center. |
| 59 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version; | 93 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version; |
| 60 | 94 |
| 61 // These three methods are used to determine the version of the application | 95 // These three methods are used to determine the version of the application |
| 62 // currently installed on disk, compare that to the currently-running version, | 96 // currently installed on disk, compare that to the currently-running version, |
| 63 // decide whether any updates have been installed, and call | 97 // decide whether any updates have been installed, and call |
| 64 // -updateStatus:version:. | 98 // -updateStatus:version:. |
| 65 // | 99 // |
| 66 // In order to check the version on disk, the installed application's | 100 // In order to check the version on disk, the installed application's |
| 67 // Info.plist dictionary must be read; in order to see changes as updates are | 101 // Info.plist dictionary must be read; in order to see changes as updates are |
| 68 // applied, the dictionary must be read each time, bypassing any caches such | 102 // applied, the dictionary must be read each time, bypassing any caches such |
| 69 // as the one that NSBundle might be maintaining. Reading files can be a | 103 // as the one that NSBundle might be maintaining. Reading files can be a |
| 70 // blocking operation, and blocking operations are to be avoided on the main | 104 // blocking operation, and blocking operations are to be avoided on the main |
| 71 // thread. I'm not quite sure what jank means, but I bet that a blocked main | 105 // thread. I'm not quite sure what jank means, but I bet that a blocked main |
| 72 // thread would cause some of it. | 106 // thread would cause some of it. |
| 73 // | 107 // |
| 74 // -determineUpdateStatusAsync is called on the main thread to initiate the | 108 // -determineUpdateStatusAsync is called on the main thread to initiate the |
| 75 // operation. It performs initial set-up work that must be done on the main | 109 // operation. It performs initial set-up work that must be done on the main |
| 76 // thread and arranges for -determineUpdateStatusAtPath: to be called on a | 110 // thread and arranges for -determineUpdateStatus to be called on a work queue |
| 77 // work queue thread managed by NSOperationQueue. | 111 // thread managed by NSOperationQueue. |
| 78 // -determineUpdateStatusAtPath: then reads the Info.plist, gets the version | 112 // -determineUpdateStatus then reads the Info.plist, gets the version from the |
| 79 // from the CFBundleShortVersionString key, and performs | 113 // CFBundleShortVersionString key, and performs |
| 80 // -determineUpdateStatusForVersion: on the main thread. | 114 // -determineUpdateStatusForVersion: on the main thread. |
| 81 // -determineUpdateStatusForVersion: does the actual comparison of the version | 115 // -determineUpdateStatusForVersion: does the actual comparison of the version |
| 82 // on disk with the running version and calls -updateStatus:version: with the | 116 // on disk with the running version and calls -updateStatus:version: with the |
| 83 // results of its analysis. | 117 // results of its analysis. |
| 84 - (void)determineUpdateStatusAsync; | 118 - (void)determineUpdateStatusAsync; |
| 85 - (void)determineUpdateStatusAtPath:(NSString*)appPath; | 119 - (void)determineUpdateStatus; |
| 86 - (void)determineUpdateStatusForVersion:(NSString*)version; | 120 - (void)determineUpdateStatusForVersion:(NSString*)version; |
| 87 | 121 |
| 122 // Returns YES if registration_ is definitely on a user ticket. If definitely |
| 123 // on a system ticket, or uncertain of ticket type (due to an older version |
| 124 // of Keystone being used), returns NO. |
| 125 - (BOOL)isUserTicket; |
| 126 |
| 127 // Called when ticket promotion completes. |
| 128 - (void)promotionComplete:(NSNotification*)notification; |
| 129 |
| 130 // Changes the application's ownership and permissions so that all files are |
| 131 // owned by root:wheel and all files and directories are writable only by |
| 132 // root, but readable and executable as needed by everyone. |
| 133 // -changePermissionsForPromotionAsync is called on the main thread by |
| 134 // -promotionComplete. That routine calls |
| 135 // -changePermissionsForPromotionWithTool: on a work queue thread. When done, |
| 136 // -changePermissionsForPromotionComplete is called on the main thread. |
| 137 - (void)changePermissionsForPromotionAsync; |
| 138 - (void)changePermissionsForPromotionWithTool:(NSString*)toolPath; |
| 139 - (void)changePermissionsForPromotionComplete; |
| 140 |
| 88 @end // @interface KeystoneGlue(Private) | 141 @end // @interface KeystoneGlue(Private) |
| 89 | 142 |
| 90 const NSString* const kAutoupdateStatusNotification = | 143 const NSString* const kAutoupdateStatusNotification = |
| 91 @"AutoupdateStatusNotification"; | 144 @"AutoupdateStatusNotification"; |
| 92 const NSString* const kAutoupdateStatusStatus = @"status"; | 145 const NSString* const kAutoupdateStatusStatus = @"status"; |
| 93 const NSString* const kAutoupdateStatusVersion = @"version"; | 146 const NSString* const kAutoupdateStatusVersion = @"version"; |
| 94 | 147 |
| 95 @implementation KeystoneGlue | 148 @implementation KeystoneGlue |
| 96 | 149 |
| 97 // TODO(jrg): use base::SingletonObjC<KeystoneGlue> | 150 + (id)defaultKeystoneGlue { |
| 98 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked | 151 static bool sTriedCreatingDefaultKeystoneGlue = false; |
| 152 // TODO(jrg): use base::SingletonObjC<KeystoneGlue> |
| 153 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked |
| 99 | 154 |
| 100 + (id)defaultKeystoneGlue { | 155 if (!sTriedCreatingDefaultKeystoneGlue) { |
| 101 if (!sDefaultKeystoneGlue) { | 156 sTriedCreatingDefaultKeystoneGlue = true; |
| 157 |
| 102 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init]; | 158 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init]; |
| 103 [sDefaultKeystoneGlue loadParameters]; | 159 [sDefaultKeystoneGlue loadParameters]; |
| 104 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) { | 160 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) { |
| 105 [sDefaultKeystoneGlue release]; | 161 [sDefaultKeystoneGlue release]; |
| 106 sDefaultKeystoneGlue = nil; | 162 sDefaultKeystoneGlue = nil; |
| 107 } | 163 } |
| 108 } | 164 } |
| 109 return sDefaultKeystoneGlue; | 165 return sDefaultKeystoneGlue; |
| 110 } | 166 } |
| 111 | 167 |
| 112 - (id)init { | 168 - (id)init { |
| 113 if ((self = [super init])) { | 169 if ((self = [super init])) { |
| 114 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | 170 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| 115 | 171 |
| 116 [center addObserver:self | 172 [center addObserver:self |
| 173 selector:@selector(registrationComplete:) |
| 174 name:KSRegistrationDidCompleteNotification |
| 175 object:nil]; |
| 176 |
| 177 [center addObserver:self |
| 178 selector:@selector(promotionComplete:) |
| 179 name:KSRegistrationPromotionDidCompleteNotification |
| 180 object:nil]; |
| 181 |
| 182 [center addObserver:self |
| 117 selector:@selector(checkForUpdateComplete:) | 183 selector:@selector(checkForUpdateComplete:) |
| 118 name:KSRegistrationCheckForUpdateNotification | 184 name:KSRegistrationCheckForUpdateNotification |
| 119 object:nil]; | 185 object:nil]; |
| 120 | 186 |
| 121 [center addObserver:self | 187 [center addObserver:self |
| 122 selector:@selector(installUpdateComplete:) | 188 selector:@selector(installUpdateComplete:) |
| 123 name:KSRegistrationStartUpdateNotification | 189 name:KSRegistrationStartUpdateNotification |
| 124 object:nil]; | 190 object:nil]; |
| 125 } | 191 } |
| 126 | 192 |
| 127 return self; | 193 return self; |
| 128 } | 194 } |
| 129 | 195 |
| 130 - (void)dealloc { | 196 - (void)dealloc { |
| 197 [productID_ release]; |
| 198 [appPath_ release]; |
| 131 [url_ release]; | 199 [url_ release]; |
| 132 [productID_ release]; | |
| 133 [version_ release]; | 200 [version_ release]; |
| 134 [channel_ release]; | 201 [channel_ release]; |
| 135 [registration_ release]; | 202 [registration_ release]; |
| 136 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 203 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| 137 [super dealloc]; | 204 [super dealloc]; |
| 138 } | 205 } |
| 139 | 206 |
| 140 - (NSDictionary*)infoDictionary { | 207 - (NSDictionary*)infoDictionary { |
| 141 // Use mac_util::MainAppBundle() to get the app framework's dictionary. | 208 // Use mac_util::MainAppBundle() to get the app framework's dictionary. |
| 142 return [mac_util::MainAppBundle() infoDictionary]; | 209 return [mac_util::MainAppBundle() infoDictionary]; |
| 143 } | 210 } |
| 144 | 211 |
| 145 - (void)loadParameters { | 212 - (void)loadParameters { |
| 146 NSDictionary* infoDictionary = [self infoDictionary]; | 213 NSDictionary* infoDictionary = [self infoDictionary]; |
| 214 |
| 215 // Use [NSBundle mainBundle] to get the application's own bundle identifier |
| 216 // and path, not the framework's. For auto-update, the application is |
| 217 // what's significant here: it's used to locate the outermost part of the |
| 218 // application for the existence checker and other operations that need to |
| 219 // see the entire application bundle. |
| 220 NSBundle* appBundle = [NSBundle mainBundle]; |
| 221 |
| 222 NSString* productID = [infoDictionary objectForKey:@"KSProductID"]; |
| 223 if (productID == nil) { |
| 224 productID = [appBundle bundleIdentifier]; |
| 225 } |
| 226 |
| 227 NSString* appPath = [appBundle bundlePath]; |
| 147 NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"]; | 228 NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"]; |
| 148 NSString* product = [infoDictionary objectForKey:@"KSProductID"]; | |
| 149 if (product == nil) { | |
| 150 // Use [NSBundle mainBundle] to fall back to the app's own bundle | |
| 151 // identifier, not the app framework's. | |
| 152 product = [[NSBundle mainBundle] bundleIdentifier]; | |
| 153 } | |
| 154 NSString* version = [infoDictionary objectForKey:@"KSVersion"]; | 229 NSString* version = [infoDictionary objectForKey:@"KSVersion"]; |
| 155 if (!product || !url || !version) { | 230 |
| 231 if (!productID || !appPath || !url || !version) { |
| 156 // If parameters required for Keystone are missing, don't use it. | 232 // If parameters required for Keystone are missing, don't use it. |
| 157 return; | 233 return; |
| 158 } | 234 } |
| 159 | 235 |
| 160 NSString* channel = [infoDictionary objectForKey:@"KSChannelID"]; | 236 NSString* channel = [infoDictionary objectForKey:@"KSChannelID"]; |
| 161 // The stable channel has no tag. If updating to stable, remove the | 237 // The stable channel has no tag. If updating to stable, remove the |
| 162 // dev and beta tags since we've been "promoted". | 238 // dev and beta tags since we've been "promoted". |
| 163 if (channel == nil) | 239 if (channel == nil) |
| 164 channel = KSRegistrationRemoveExistingTag; | 240 channel = KSRegistrationRemoveExistingTag; |
| 165 | 241 |
| 242 productID_ = [productID retain]; |
| 243 appPath_ = [appPath retain]; |
| 166 url_ = [url retain]; | 244 url_ = [url retain]; |
| 167 productID_ = [product retain]; | |
| 168 version_ = [version retain]; | 245 version_ = [version retain]; |
| 169 channel_ = [channel retain]; | 246 channel_ = [channel retain]; |
| 170 } | 247 } |
| 171 | 248 |
| 172 - (BOOL)loadKeystoneRegistration { | 249 - (BOOL)loadKeystoneRegistration { |
| 173 if (!productID_ || !url_ || !version_) | 250 if (!productID_ || !appPath_ || !url_ || !version_) |
| 174 return NO; | 251 return NO; |
| 175 | 252 |
| 176 // Load the KeystoneRegistration framework bundle if present. It lives | 253 // Load the KeystoneRegistration framework bundle if present. It lives |
| 177 // inside the framework, so use mac_util::MainAppBundle(); | 254 // inside the framework, so use mac_util::MainAppBundle(); |
| 178 NSString* ksrPath = | 255 NSString* ksrPath = |
| 179 [[mac_util::MainAppBundle() privateFrameworksPath] | 256 [[mac_util::MainAppBundle() privateFrameworksPath] |
| 180 stringByAppendingPathComponent:@"KeystoneRegistration.framework"]; | 257 stringByAppendingPathComponent:@"KeystoneRegistration.framework"]; |
| 181 NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath]; | 258 NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath]; |
| 182 [ksrBundle load]; | 259 [ksrBundle load]; |
| 183 | 260 |
| 184 // Harness the KSRegistration class. | 261 // Harness the KSRegistration class. |
| 185 Class ksrClass = [ksrBundle classNamed:@"KSRegistration"]; | 262 Class ksrClass = [ksrBundle classNamed:@"KSRegistration"]; |
| 186 KSRegistration* ksr = [ksrClass registrationWithProductID:productID_]; | 263 KSRegistration* ksr = [ksrClass registrationWithProductID:productID_]; |
| 187 if (!ksr) | 264 if (!ksr) |
| 188 return NO; | 265 return NO; |
| 189 | 266 |
| 190 registration_ = [ksr retain]; | 267 registration_ = [ksr retain]; |
| 191 return YES; | 268 return YES; |
| 192 } | 269 } |
| 193 | 270 |
| 194 - (void)registerWithKeystone { | 271 - (void)registerWithKeystone { |
| 195 // The existence checks should use the path to the app bundle, not the | 272 [self updateStatus:kAutoupdateRegistering version:nil]; |
| 196 // app framework bundle, so use [NSBundle mainBundle] instead of | 273 |
| 197 // mac_util::MainBundle(). | 274 if (![registration_ registerWithVersion:version_ |
| 198 [registration_ registerWithVersion:version_ | 275 existenceCheckerType:kKSPathExistenceChecker |
| 199 existenceCheckerType:kKSPathExistenceChecker | 276 existenceCheckerString:appPath_ |
| 200 existenceCheckerString:[[NSBundle mainBundle] bundlePath] | 277 serverURLString:url_ |
| 201 serverURLString:url_ | 278 preserveTTToken:YES |
| 202 preserveTTToken:YES | 279 tag:channel_]) { |
| 203 tag:channel_]; | 280 [self updateStatus:kAutoupdateRegisterFailed version:nil]; |
| 281 return; |
| 282 } |
| 283 |
| 284 // Upon completion, KSRegistrationDidCompleteNotification will be posted, |
| 285 // and -registrationComplete: will be called. |
| 204 | 286 |
| 205 // Mark an active RIGHT NOW; don't wait an hour for the first one. | 287 // Mark an active RIGHT NOW; don't wait an hour for the first one. |
| 206 [registration_ setActive]; | 288 [registration_ setActive]; |
| 207 | 289 |
| 208 // Set up hourly activity pings. | 290 // Set up hourly activity pings. |
| 209 timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour | 291 timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour |
| 210 target:self | 292 target:self |
| 211 selector:@selector(markActive:) | 293 selector:@selector(markActive:) |
| 212 userInfo:registration_ | 294 userInfo:registration_ |
| 213 repeats:YES]; | 295 repeats:YES]; |
| 214 } | 296 } |
| 215 | 297 |
| 298 - (void)registrationComplete:(NSNotification*)notification { |
| 299 NSDictionary* userInfo = [notification userInfo]; |
| 300 if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) { |
| 301 [self updateStatus:kAutoupdateRegistered version:nil]; |
| 302 } else { |
| 303 // Dump registration_? |
| 304 [self updateStatus:kAutoupdateRegisterFailed version:nil]; |
| 305 } |
| 306 } |
| 307 |
| 216 - (void)stopTimer { | 308 - (void)stopTimer { |
| 217 [timer_ invalidate]; | 309 [timer_ invalidate]; |
| 218 } | 310 } |
| 219 | 311 |
| 220 - (void)markActive:(NSTimer*)timer { | 312 - (void)markActive:(NSTimer*)timer { |
| 221 KSRegistration* ksr = [timer userInfo]; | 313 KSRegistration* ksr = [timer userInfo]; |
| 222 [ksr setActive]; | 314 [ksr setActive]; |
| 223 } | 315 } |
| 224 | 316 |
| 225 - (void)checkForUpdate { | 317 - (void)checkForUpdate { |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 283 updateSuccessfullyInstalled_ = YES; | 375 updateSuccessfullyInstalled_ = YES; |
| 284 | 376 |
| 285 // Nothing in the notification dictionary reports the version that was | 377 // Nothing in the notification dictionary reports the version that was |
| 286 // installed. Figure it out based on what's on disk. | 378 // installed. Figure it out based on what's on disk. |
| 287 [self determineUpdateStatusAsync]; | 379 [self determineUpdateStatusAsync]; |
| 288 } | 380 } |
| 289 } | 381 } |
| 290 | 382 |
| 291 // Runs on the main thread. | 383 // Runs on the main thread. |
| 292 - (void)determineUpdateStatusAsync { | 384 - (void)determineUpdateStatusAsync { |
| 293 // NSBundle is not documented as being thread-safe. Do NSBundle operations | |
| 294 // on the main thread before jumping over to a NSOperationQueue-managed | |
| 295 // thread to do blocking file input. | |
| 296 DCHECK([NSThread isMainThread]); | 385 DCHECK([NSThread isMainThread]); |
| 297 | 386 |
| 298 SEL selector = @selector(determineUpdateStatusAtPath:); | 387 SEL selector = @selector(determineUpdateStatus); |
| 299 NSString* appPath = [[NSBundle mainBundle] bundlePath]; | |
| 300 NSInvocationOperation* operation = | 388 NSInvocationOperation* operation = |
| 301 [[[NSInvocationOperation alloc] initWithTarget:self | 389 [[[NSInvocationOperation alloc] initWithTarget:self |
| 302 selector:selector | 390 selector:selector |
| 303 object:appPath] autorelease]; | 391 object:nil] autorelease]; |
| 304 | 392 |
| 305 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue]; | 393 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue]; |
| 306 [operationQueue addOperation:operation]; | 394 [operationQueue addOperation:operation]; |
| 307 } | 395 } |
| 308 | 396 |
| 309 // Runs on a thread managed by NSOperationQueue. | 397 // Runs on a thread managed by NSOperationQueue. |
| 310 - (void)determineUpdateStatusAtPath:(NSString*)appPath { | 398 - (void)determineUpdateStatus { |
| 311 DCHECK(![NSThread isMainThread]); | 399 DCHECK(![NSThread isMainThread]); |
| 312 | 400 |
| 313 NSString* appInfoPlistPath = | 401 NSString* appInfoPlistPath = |
| 314 [[appPath stringByAppendingPathComponent:@"Contents"] | 402 [[appPath_ stringByAppendingPathComponent:@"Contents"] |
| 315 stringByAppendingPathComponent:@"Info.plist"]; | 403 stringByAppendingPathComponent:@"Info.plist"]; |
| 316 NSDictionary* infoPlist = | 404 NSDictionary* infoPlist = |
| 317 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath]; | 405 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath]; |
| 318 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"]; | 406 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"]; |
| 319 | 407 |
| 320 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:) | 408 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:) |
| 321 withObject:version | 409 withObject:version |
| 322 waitUntilDone:NO]; | 410 waitUntilDone:NO]; |
| 323 } | 411 } |
| 324 | 412 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 376 } | 464 } |
| 377 | 465 |
| 378 - (AutoupdateStatus)recentStatus { | 466 - (AutoupdateStatus)recentStatus { |
| 379 NSDictionary* dictionary = [recentNotification_ userInfo]; | 467 NSDictionary* dictionary = [recentNotification_ userInfo]; |
| 380 return static_cast<AutoupdateStatus>( | 468 return static_cast<AutoupdateStatus>( |
| 381 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); | 469 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]); |
| 382 } | 470 } |
| 383 | 471 |
| 384 - (BOOL)asyncOperationPending { | 472 - (BOOL)asyncOperationPending { |
| 385 AutoupdateStatus status = [self recentStatus]; | 473 AutoupdateStatus status = [self recentStatus]; |
| 386 return status == kAutoupdateChecking || status == kAutoupdateInstalling; | 474 return status == kAutoupdateRegistering || |
| 475 status == kAutoupdateChecking || |
| 476 status == kAutoupdateInstalling || |
| 477 status == kAutoupdatePromoting; |
| 478 } |
| 479 |
| 480 - (BOOL)isUserTicket { |
| 481 return [registration_ ticketType] == kKSRegistrationUserTicket; |
| 482 } |
| 483 |
| 484 - (BOOL)isOnReadOnlyFilesystem { |
| 485 const char* appPathC = [appPath_ fileSystemRepresentation]; |
| 486 struct statfs statfsBuf; |
| 487 |
| 488 if (statfs(appPathC, &statfsBuf) != 0) { |
| 489 PLOG(ERROR) << "statfs"; |
| 490 // Be optimistic about the filesystem's writability. |
| 491 return NO; |
| 492 } |
| 493 |
| 494 return (statfsBuf.f_flags & MNT_RDONLY) != 0; |
| 495 } |
| 496 |
| 497 - (BOOL)needsPromotion { |
| 498 if (![self isUserTicket] || [self isOnReadOnlyFilesystem]) { |
| 499 return NO; |
| 500 } |
| 501 |
| 502 // Check the outermost bundle directory, the main executable path, and the |
| 503 // framework directory. It may be enough to just look at the outermost |
| 504 // bundle directory, but checking an interior file and directory can be |
| 505 // helpful in case permissions are set differently only on the outermost |
| 506 // directory. An interior file and directory are both checked because some |
| 507 // file operations, such as Snow Leopard's Finder's copy operation when |
| 508 // authenticating, may actually result in different ownership being applied |
| 509 // to files and directories. |
| 510 NSFileManager* fileManager = [NSFileManager defaultManager]; |
| 511 NSString* executablePath = [[NSBundle mainBundle] executablePath]; |
| 512 NSString* frameworkPath = [mac_util::MainAppBundle() bundlePath]; |
| 513 return ![fileManager isWritableFileAtPath:appPath_] || |
| 514 ![fileManager isWritableFileAtPath:executablePath] || |
| 515 ![fileManager isWritableFileAtPath:frameworkPath]; |
| 516 } |
| 517 |
| 518 - (BOOL)wantsPromotion { |
| 519 // -needsPromotion checks these too, but this method doesn't necessarily |
| 520 // return NO just becuase -needsPromotion returns NO, so another check is |
| 521 // needed here. |
| 522 if (![self isUserTicket] || [self isOnReadOnlyFilesystem]) { |
| 523 return NO; |
| 524 } |
| 525 |
| 526 if ([self needsPromotion]) { |
| 527 return YES; |
| 528 } |
| 529 |
| 530 return [appPath_ hasPrefix:@"/Applications/"]; |
| 531 } |
| 532 |
| 533 - (void)promoteTicket { |
| 534 if ([self asyncOperationPending] || ![self wantsPromotion]) { |
| 535 // Because there are multiple ways of reaching promoteTicket that might |
| 536 // not lock each other out, it may be possible to arrive here while an |
| 537 // asynchronous operation is pending, or even after promotion has already |
| 538 // occurred. Just quietly return without doing anything. |
| 539 return; |
| 540 } |
| 541 |
| 542 // Create an empty AuthorizationRef. |
| 543 scoped_AuthorizationRef authorization; |
| 544 OSStatus status = AuthorizationCreate(NULL, |
| 545 kAuthorizationEmptyEnvironment, |
| 546 kAuthorizationFlagDefaults, |
| 547 &authorization); |
| 548 if (status != errAuthorizationSuccess) { |
| 549 LOG(ERROR) << "AuthorizationCreate: " << status; |
| 550 return; |
| 551 } |
| 552 |
| 553 // Specify the "system.privilege.admin" right, which allows |
| 554 // AuthorizationExecuteWithPrivileges to run commands as root. |
| 555 AuthorizationItem rightItems[] = { |
| 556 {kAuthorizationRightExecute, 0, NULL, 0} |
| 557 }; |
| 558 AuthorizationRights rights = {arraysize(rightItems), rightItems}; |
| 559 |
| 560 // product_logo_32.png is used instead of app.icns because Authorization |
| 561 // Services requires an image that NSImage can read. |
| 562 NSString* iconPath = |
| 563 [mac_util::MainAppBundle() pathForResource:@"product_logo_32" |
| 564 ofType:@"png"]; |
| 565 const char* iconPathC = [iconPath fileSystemRepresentation]; |
| 566 size_t iconPathLength = iconPathC ? strlen(iconPathC) : 0; |
| 567 |
| 568 // The OS will append " Type an administrator's name and password to allow |
| 569 // <CFBundleDisplayName> to make changes." |
| 570 NSString* prompt = l10n_util::GetNSStringFWithFixup( |
| 571 IDS_PROMOTE_AUTHENTICATION_PROMPT, |
| 572 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); |
| 573 const char* promptC = [prompt UTF8String]; |
| 574 size_t promptLength = promptC ? strlen(promptC) : 0; |
| 575 |
| 576 AuthorizationItem environmentItems[] = { |
| 577 {kAuthorizationEnvironmentIcon, iconPathLength, (void*)iconPathC, 0}, |
| 578 {kAuthorizationEnvironmentPrompt, promptLength, (void*)promptC, 0} |
| 579 }; |
| 580 |
| 581 AuthorizationEnvironment environment = {arraysize(environmentItems), |
| 582 environmentItems}; |
| 583 |
| 584 AuthorizationFlags flags = kAuthorizationFlagDefaults | |
| 585 kAuthorizationFlagInteractionAllowed | |
| 586 kAuthorizationFlagExtendRights | |
| 587 kAuthorizationFlagPreAuthorize; |
| 588 |
| 589 status = AuthorizationCopyRights(authorization, |
| 590 &rights, |
| 591 &environment, |
| 592 flags, |
| 593 NULL); |
| 594 if (status != errAuthorizationSuccess) { |
| 595 if (status != errAuthorizationCanceled) { |
| 596 LOG(ERROR) << "AuthorizationCopyRights: " << status; |
| 597 } |
| 598 return; |
| 599 } |
| 600 |
| 601 [self updateStatus:kAutoupdatePromoting version:nil]; |
| 602 |
| 603 // TODO(mark): Remove when able! |
| 604 // |
| 605 // keystone_promote_preflight is hopefully temporary. It's here to ensure |
| 606 // that the Keystone system ticket store is in a usable state for all users |
| 607 // on the system. Ideally, Keystone's installer or another part of Keystone |
| 608 // would handle this. The underlying problem is http://b/2285921, and it |
| 609 // causes http://b/2289908, which this workaround addresses. |
| 610 // |
| 611 // This is run synchronously, which isn't optimal, but |
| 612 // -[KSRegistration promoteWithVersion:...] is currently synchronous too, |
| 613 // and this operation needs to happen before that one. |
| 614 // |
| 615 // TODO(mark): Make asynchronous. That only makes sense if the promotion |
| 616 // operation itself is asynchronous too. http://b/2290009. Hopefully, |
| 617 // the Keystone promotion code will just be changed to do what preflight |
| 618 // now does, and then the preflight script can be removed instead. |
| 619 NSString* preflightPath = |
| 620 [mac_util::MainAppBundle() pathForResource:@"keystone_promote_preflight" |
| 621 ofType:@"sh"]; |
| 622 const char* preflightPathC = [preflightPath fileSystemRepresentation]; |
| 623 const char* arguments[] = {NULL}; |
| 624 |
| 625 int exit_status; |
| 626 status = authorization_util::ExecuteWithPrivilegesAndWait( |
| 627 authorization, |
| 628 preflightPathC, |
| 629 kAuthorizationFlagDefaults, |
| 630 arguments, |
| 631 NULL, // pipe |
| 632 &exit_status); |
| 633 if (status != errAuthorizationSuccess) { |
| 634 LOG(ERROR) << "AuthorizationExecuteWithPrivileges preflight: " << status; |
| 635 [self updateStatus:kAutoupdatePromoteFailed version:nil]; |
| 636 return; |
| 637 } |
| 638 if (exit_status != 0) { |
| 639 LOG(ERROR) << "keystone_promote_preflight status " << exit_status; |
| 640 [self updateStatus:kAutoupdatePromoteFailed version:nil]; |
| 641 return; |
| 642 } |
| 643 |
| 644 // Hang on to the AuthorizationRef so that it can be used once promotion is |
| 645 // complete. Do this before asking Keystone to promote the ticket, because |
| 646 // -promotionComplete: may be called from inside the Keystone promotion |
| 647 // call. |
| 648 authorization_.swap(authorization); |
| 649 |
| 650 if (![registration_ promoteWithVersion:version_ |
| 651 existenceCheckerType:kKSPathExistenceChecker |
| 652 existenceCheckerString:appPath_ |
| 653 serverURLString:url_ |
| 654 preserveTTToken:YES |
| 655 tag:channel_ |
| 656 authorization:authorization_]) { |
| 657 [self updateStatus:kAutoupdatePromoteFailed version:nil]; |
| 658 authorization_.reset(); |
| 659 return; |
| 660 } |
| 661 |
| 662 // Upon completion, KSRegistrationPromotionDidCompleteNotification will be |
| 663 // posted, and -promotionComplete: will be called. |
| 664 } |
| 665 |
| 666 - (void)promotionComplete:(NSNotification*)notification { |
| 667 NSDictionary* userInfo = [notification userInfo]; |
| 668 if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) { |
| 669 [self changePermissionsForPromotionAsync]; |
| 670 } else { |
| 671 authorization_.reset(); |
| 672 [self updateStatus:kAutoupdatePromoteFailed version:nil]; |
| 673 } |
| 674 } |
| 675 |
| 676 - (void)changePermissionsForPromotionAsync { |
| 677 // NSBundle is not documented as being thread-safe. Do NSBundle operations |
| 678 // on the main thread before jumping over to a NSOperationQueue-managed |
| 679 // thread to run the tool. |
| 680 DCHECK([NSThread isMainThread]); |
| 681 |
| 682 SEL selector = @selector(changePermissionsForPromotionWithTool:); |
| 683 NSString* toolPath = |
| 684 [mac_util::MainAppBundle() pathForResource:@"keystone_promote_postflight" |
| 685 ofType:@"sh"]; |
| 686 |
| 687 NSInvocationOperation* operation = |
| 688 [[[NSInvocationOperation alloc] initWithTarget:self |
| 689 selector:selector |
| 690 object:toolPath] autorelease]; |
| 691 |
| 692 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue]; |
| 693 [operationQueue addOperation:operation]; |
| 694 } |
| 695 |
| 696 - (void)changePermissionsForPromotionWithTool:(NSString*)toolPath { |
| 697 const char* toolPathC = [toolPath fileSystemRepresentation]; |
| 698 |
| 699 const char* appPathC = [appPath_ fileSystemRepresentation]; |
| 700 const char* arguments[] = {appPathC, NULL}; |
| 701 |
| 702 int exit_status; |
| 703 OSStatus status = authorization_util::ExecuteWithPrivilegesAndWait( |
| 704 authorization_, |
| 705 toolPathC, |
| 706 kAuthorizationFlagDefaults, |
| 707 arguments, |
| 708 NULL, // pipe |
| 709 &exit_status); |
| 710 if (status != errAuthorizationSuccess) { |
| 711 LOG(ERROR) << "AuthorizationExecuteWithPrivileges postflight: " << status; |
| 712 } else if (exit_status != 0) { |
| 713 LOG(ERROR) << "keystone_promote_postflight status " << exit_status; |
| 714 } |
| 715 |
| 716 SEL selector = @selector(changePermissionsForPromotionComplete); |
| 717 [self performSelectorOnMainThread:selector |
| 718 withObject:nil |
| 719 waitUntilDone:NO]; |
| 720 } |
| 721 |
| 722 - (void)changePermissionsForPromotionComplete { |
| 723 authorization_.reset(); |
| 724 |
| 725 [self updateStatus:kAutoupdatePromoted version:nil]; |
| 387 } | 726 } |
| 388 | 727 |
| 389 @end // @implementation KeystoneGlue | 728 @end // @implementation KeystoneGlue |
| OLD | NEW |