source: trunk/Cocoa/F-Script Anywhere/Source/FSAApp.mm@ 332

Last change on this file since 332 was 231, checked in by rchin, 18 years ago

Updated FScript framework zip to current working version (pre-release). Also added code to better do upgrading of existing outdated fscript framework versions. Also better checking for procmod existence.

File size: 23.2 KB
RevLine 
[153]1//
2// FSAApp.m
3// F-Script Anywhere
4//
5// Created by Nicholas Riley on Fri Feb 01 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9/*
10
11 F-Script Anywhere is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 F-Script Anywhere is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with F-Script Anywhere; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
25*/
26
27#import "FSAApp.h"
28#import "FSAnywhere.h"
[229]29#import <DSCL/PathManager.h>
30#import <CoreFoundation/CoreFoundation.h>
31#import <ApplicationServices/ApplicationServices.h>
32#import <Security/AuthorizationTags.h>
[231]33#include <mach/mach.h>
[153]34
35NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
36NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
37
38typedef struct {
39 OSStatus err;
40 NSString * const desc;
41} errRec, errList[];
42
43// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
44static errList ERRS = { // XXX change for mach_inject/SCPatch
45 { 5, @"F-Script Anywhere must be installed in a Cocoa application running as the current user.\n\nYou may be attempting to install in a setuid application, which is not supported" },
46 { 11, @"F-Script Anywhere cannot be installed in itself.\n\nIf you wish to install F-Script Anywhere in itself, make a copy of the F-Script Anywhere application and install one copy into the other" },
47 { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
48 { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application. Please try reinstalling F-Script Anywhere" },
49 { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
50 { 0, nil }
51};
52
53NSMutableDictionary *FSA_errors;
54
55NSString * FSA_descriptionForOSStatus(OSStatus err)
56{
57 NSString *desc = nil;
58
59 if (FSA_errors != nil)
60 desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
61
62 if (desc != nil)
63 return desc;
64
65 return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
66}
67
68FSAPatchController::FSAPatchController(FSAApp *fsaApp)
69{
70 mApp = fsaApp;
71}
72
73void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
74 pid_t pid;
75 OSStatus err = GetProcessPID(inPSN, &pid);
76
77 NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
78
79 FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
80 [mApp controllerIsPatchingApplicationWithProcessID: pid];
81}
82
83// XXX should remove?
84void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
85 OSErr err = noErr;
86 Size actualSize;
87 DescType actualType;
88 OSType eventClass, eventID;
89
90 if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
91 return;
92 if (eventClass != kSCMessageClass)
93 return;
94
95 if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
96 return;
[217]97
98#if 0
[153]99 if (eventID == kSCLoadResult) {
100 OSStatus
101 (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
102 (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
103 bundleID[actualSize] = 0;
104 printf("patch bundle %s loaded with error status %d\n", bundleID, error);
105 }
[217]106#endif
[153]107
108
109}
110
111
112@implementation FSAApp
113
114+ (void)initialize;
115{
116 errRec *rec;
117
118 FSA_errors = [[NSMutableDictionary alloc] init];
119 for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
120 [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
121}
122
[231]123-(void)installFScriptAtPath:(NSString *)installPath
[230]124{
125 OSStatus myStatus;
126
127 if(installPath){
[231]128 if([installPath hasSuffix:@"FScript.framework"]){
129 installPath = [installPath stringByDeletingLastPathComponent];
130 NSLog(@"new ip %@", installPath);
131 }
[230]132 }
133
134 if(!installPath){
135 switch([[NSAlert alertWithMessageText:@"Install F-Script"
136 defaultButton:@"Current User"
137 alternateButton:@"All Users"
138 otherButton:nil
139 informativeTextWithFormat:@"No previous version of F-Script found. You can choose to install for just your current user (~/Library/Frameworks) or for all users (/Library/Frameworks)."] runModal]){
140 case NSAlertDefaultReturn: // Current user
[231]141 installPath = [@"~/Library/Frameworks" stringByExpandingTildeInPath];
[230]142 break;
143 case NSAlertAlternateReturn: // All users
[231]144 installPath = @"/Library/Frameworks";
[230]145 break;
146 default:
147 break;
148 }
149 }
150
151 NSString *fromPath = [[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"];
[231]152 if(![[NSFileManager defaultManager] fileExistsAtPath:installPath]){
[230]153 if ([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
[231]154 [[NSFileManager defaultManager] createDirectoryAtPath:installPath
[230]155 attributes:nil];
156 } else {
157 [self createAuthorization];
158 NSString *myToolPath = @"/bin/mkdir";
[231]159 char *myArguments[] = { (char *)[installPath UTF8String], NULL };
[230]160 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
161 myFlags, myArguments,
162 NULL);
163 }
164 }
[231]165 if([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
166 [[NSFileManager defaultManager] copyPath:fromPath toPath:[installPath stringByAppendingString:@"/FScript.framework"] handler:nil];
[230]167 } else {
168 [self createAuthorization];
169 NSString *myToolPath = @"/bin/cp";
[231]170 char *myArguments[] = { "-R", "-f", (char *)[fromPath UTF8String], (char *)[installPath UTF8String], NULL };
[230]171 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
172 myFlags, myArguments,
173 NULL);
174 }
175}
176
[231]177- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result withFrameworkPath:(NSString *)frameworkPath
[153]178{
179 switch (result) {
180 case NSAlertOtherReturn:
[231]181 [self terminate: self];
182 return false;
183 case NSAlertAlternateReturn:
[153]184 return true;
[231]185 case NSAlertDefaultReturn: /* Install from resources */
186 [self installFScriptAtPath:frameworkPath];
[230]187 return true;
[153]188 default:
189 [self terminate: self];
190 return false;
191 }
192}
193
[230]194-(BOOL)createAuthorization
[229]195{
[230]196 if(myAuthorizationRef)
197 return YES;
198
[229]199 OSStatus myStatus;
[230]200 myFlags = kAuthorizationFlagDefaults;
[229]201
202 myStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
203 myFlags, &myAuthorizationRef);
204 if(myStatus != errAuthorizationSuccess)
[230]205 return NO;
[229]206
207 AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
208 AuthorizationRights myRights = {1, &myItems};
209
210 myFlags = kAuthorizationFlagDefaults
211 | kAuthorizationFlagInteractionAllowed
212 | kAuthorizationFlagPreAuthorize
213 | kAuthorizationFlagExtendRights;
214 myStatus = AuthorizationCopyRights(myAuthorizationRef, &myRights,
215 NULL, myFlags, NULL);
216
217 if(myStatus != errAuthorizationSuccess)
[230]218 return NO;
[229]219
220 myFlags = kAuthorizationFlagDefaults;
[230]221 return YES;
222}
223
224-(tDirStatus)authorizeAndAddToProcMod:(NSString *)username
225{
226 OSStatus myStatus;
[229]227
[230]228 if(![self createAuthorization])
229 return eDSAuthFailed;
230
[229]231 NSString *myToolPath = [[NSBundle mainBundle] pathForResource:@"AddToProcMod" ofType:@""];
232 char *myArguments[] = { (char *)[username UTF8String], NULL };
233 FILE *myCommunicationsPipe = NULL;
234 char myReadBuffer[128];
235 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
236 myFlags, myArguments,
237 &myCommunicationsPipe);
238
239 int didRead = 0;
240 int lastRead;
241 while((lastRead = read(fileno(myCommunicationsPipe), myReadBuffer, sizeof(myReadBuffer) - didRead - 1)) && (lastRead > 0))
242 didRead += lastRead;
243
244 myReadBuffer[didRead - 1] = 0;
245
246 return (tDirStatus)strtol(myReadBuffer, NULL, 10);
247}
248
[153]249- (void)finishLaunching
250{
[231]251 mach_port_t taskOfOurProcess = mach_task_self();
252 mach_port_t machPortForProcess;
[229]253
[231]254 if(task_for_pid(taskOfOurProcess, 1, &machPortForProcess) != KERN_SUCCESS){ // launchd should always be pid 1
255 if(![[NSUserDefaults standardUserDefaults] objectForKey:@"doPathCheck"])
256 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"doPathCheck"];
257
258 if([[NSUserDefaults standardUserDefaults] boolForKey:@"doPathCheck"]){
[229]259top:
[231]260 PathManager *pm = [[PathManager alloc] initWithLocalNode];
261 [pm backupStack];
262 @try {
263 [pm cd:@"/Groups/procmod"];
264 } @catch ( NSException *exception ) {
265 goto next; // this means we must be on ppc 10.4 or less.
266 }
267 CFDictionaryRef sessionInfoDict = CGSessionCopyCurrentDictionary();
268 if(sessionInfoDict){
269 CFStringRef shortUserName = (CFStringRef)CFDictionaryGetValue(sessionInfoDict, kCGSessionUserNameKey);
270 if(![[[pm lastObject] readArray:@"GroupMembership"] containsObject:(NSString *)shortUserName]){
271 switch([[NSAlert alertWithMessageText:[NSString stringWithFormat:@"User %@ not in the procmod group", shortUserName]
272 defaultButton:@"Add me"
273 alternateButton:@"Disable checking"
274 otherButton:@"Ignore message"
275 informativeTextWithFormat:@"F-Script Anywhere requires that you add yourself to the procmod "
276 "group in order for it to function properly. If you like, F-Script Anywhere can automatically add you "
277 "to the procmod group."] runModal]){
278 case NSAlertDefaultReturn:
279 {
280 tDirStatus status = [self authorizeAndAddToProcMod:(NSString *)shortUserName];
281 if(status != eDSNoErr){
282 [[NSAlert alertWithMessageText:@"Error adding to procmod group"
283 defaultButton:nil
284 alternateButton:nil
285 otherButton:nil
286 informativeTextWithFormat:@"There was an error (%@) adding you to the procmod group. ", [[NSClassFromString(@"DSoStatus") sharedInstance] stringForStatus:status]] runModal];
287 } else {
288 [[NSAlert alertWithMessageText:@"Adding user procmod group succeeded"
289 defaultButton:nil
290 alternateButton:nil
291 otherButton:nil
292 informativeTextWithFormat:@"You may have to wait a few minutes until the system updates its caches (or alternatively, reboot your machine) before things will work properly."] runModal];
293 }
[229]294 }
[231]295 [pm restoreStack];
296 [pm release];
297 goto top;
298 case NSAlertAlternateReturn:
299 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"doPathCheck"];
300 break;
301 default:
302 break;
303 }
304 }
[229]305 }
[231]306 [pm restoreStack];
307 [pm release];
[229]308 }
[231]309 } else {
310 mach_port_deallocate(taskOfOurProcess, machPortForProcess);
[229]311 }
[231]312next:
[153]313 patchController = new FSAPatchController(self);
314 patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
315 (CFStringRef)PatchBundleName);
316
317 NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
318 pid_t pid;
319 ProcessSerialNumber psn;
320 OSStatus err;
321
322 while ( (pid = [[e nextObject] longValue]) != 0) {
323 err = GetProcessForPID(pid, &psn);
324
325 if (err != noErr) {
326 NSLog(@"Can't get PSN for PID %ld", pid);
327 [appList applicationQuitWithProcessID: pid];
328 } else if (patchController->IsProcessPatched(&psn)) {
329 [appList didPatchProcessID: pid];
330 }
331 }
332
333 // yes, someone could move the framework while the app is running, but they deserve what they get.
334 NSFileManager *fileMgr = [NSFileManager defaultManager];
335 NSArray *libraryDirectories =
336 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
337 NSString *libPath;
[231]338 NSString *frameworkPath = NULL;
[153]339 CFBundleRef frameworkBundle = NULL;
340 BOOL isDirectory;
341
342 e = [libraryDirectories objectEnumerator];
343 while ( (libPath = [e nextObject]) != nil) {
344 frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
345 stringByAppendingPathComponent: @"FScript.framework"];
346 if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
347 frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
348 [(id)frameworkBundle autorelease];
349 break;
350 }
351 }
352 FSALog(@"Framework bundle");
353 FSAShow(frameworkBundle);
354 [super finishLaunching];
355 if (frameworkBundle == NULL) {
356 int result = NSRunInformationalAlertPanel(
357 NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
[230]358 NSLocalizedString(@"F-Script Anywhere requires the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks.\n\nTo download F-Script, please visit its Web site\n%@.\n\nThis package also includes a copy of F-Script for your convenience that can be installed easily by clicking below. Note that there may be a more recent version of F-Script available from the F-Script web site.\n\nIf you believe this message is in error, click '%@'.",
[153]359 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
[230]360 NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
[153]361 NSLocalizedString(@"Continue", "Continue button title"),
[231]362 NSLocalizedString(@"Quit", "Quit button title"),
[153]363 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
[231]364 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:nil]) return;
[153]365 } else {
[231]366 CFBundleRef includedBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"]]);
367 [(id)includedBundle autorelease];
368 UInt32 includedVersion = CFBundleGetVersionNumber(includedBundle);
[153]369 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
370 FSALog(@"Framework version 0x%x", version);
[231]371 if(version < includedVersion){
372 if (version < FSA_FSCRIPT_MIN_VERSION){ // must force upgrade
373 NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
374 if (frameworkVersion == nil)
375 frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
376 int result = NSRunInformationalAlertPanel(
377 NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
378 NSLocalizedString(@"F-Script Anywhere %s requires version %@ or later of the F-Script framework be installed in a Frameworks directory, such as ~/Library/Frameworks or /Library/Frameworks. The installed version is %@.\n\nTo upgrade F-Script to the latest version, please visit its Web site\n%@.\n\nThis package also includes a copy of F-Script for your convenience that can be installed easily by clicking below. Note that there may be a more recent version of F-Script available from the F-Script web site.\n\nTo use this version of F-Script regardless of the problems you may experience, click '%@'.",
379 "Framework too old alert message, FSA version parameter, minimum F-Script version parameter, current F-Script version parameter, F-Script Web site URL parameter, 'Continue' button parameter"),
380 NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
381 NSLocalizedString(@"Continue", "Continue button title"),
382 NSLocalizedString(@"Quit", "Quit button title"),
383 FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
384 FSA_FScriptURL, NSLocalizedString(@"Install F-Script", ""));
385 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;
386 } else {
387 NSNumber *lastChecked = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastCheckedVersion"];
388 if((version < includedVersion) && (!lastChecked || ([lastChecked unsignedIntValue] != version))){
389 FSALog(@"Included framework in resources is newer than installed version.");
390 int result = NSRunInformationalAlertPanel(
391 NSLocalizedString(@"Newer Version of F-Script Framework Available", "Framework newer found alert title"),
392 NSLocalizedString(@"F-Script Anywhere is bundled with a newer version of the F-Script framework. If you wish, F-Script Anywhere can upgrade your framework version.",
393 "Framework newer found alert message, 'Continue' button parameter"),
394 NSLocalizedString(@"Upgrade F-Script Framework", "Upgrade button title"),
395 NSLocalizedString(@"Continue", "Continue button title"),
396 nil,
397 NSLocalizedString(@"Upgrade", ""), NSLocalizedString(@"Continue", ""));
398 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result withFrameworkPath:frameworkPath]) return;
399 }
400 }
[153]401 }
[231]402 [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInt:version] forKey:@"LastCheckedVersion"];
[153]403 }
404
[230]405 if(myAuthorizationRef){
406 AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
407 myAuthorizationRef = nil;
408 }
409
[153]410 NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
411 [workspaceNotificationCenter
412 addObserver: self
413 selector: @selector(applicationDidLaunch:)
414 name: NSWorkspaceWillLaunchApplicationNotification
415 object: nil];
416 [workspaceNotificationCenter
417 addObserver: self
418 selector: @selector(applicationDidTerminate:)
419 name: NSWorkspaceDidTerminateApplicationNotification
420 object: nil];
421
422 [appListWindow makeKeyAndOrderFront: self];
423}
424
425- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
426{
427 NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
428 nil, nil, nil, appListWindow, self, nil, nil, nil,
429 NSLocalizedString(@"F-Script Anywhere was unable to install itself in the selected application (process ID %d), because %@.\n\nThe application may have crashed; restart it if needed.", "Unable to install alert message, PID parameter, 'XXX happened because' explanation parameter"),
430 pid, FSA_descriptionForOSStatus(err));
431}
432
433- (void)installBundleInAppWithPID:(pid_t)pid;
434{
435 if (pid == -1) return;
436
437 ProcessSerialNumber psn;
438 OSStatus err = GetProcessForPID(pid, &psn);
439
440 if (err == noErr)
441 err = patchController->PatchProcess(&psn);
[217]442
[153]443 if (err != noErr)
444 [self installationError: err inAppWithPID: pid];
[222]445 else
446 [appList didPatchProcessID: pid];
[153]447}
448
449- (void)loaderBundleMessage:(NSNotification *)notification;
450{
451 /*
452 if (error) {
453 [self installationError: error inAppWithPID: pid];
454 } else {
455 [appList didPatchProcessID: pid];
456 } */
457}
458
459- (IBAction)installBundleInSelectedApp:(id)sender;
460{
461 [self installBundleInAppWithPID: [appList selectedProcessID]];
462}
463
464// from dock menu only!
465- (IBAction)installBundleInFrontmostApp:(id)sender;
466{
467 NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
468 [self installBundleInAppWithPID: (pid_t)[sender tag]];
469}
470
471@end
472
473@implementation FSAApp (FSAPatchControllerDelegate)
474
475- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
476{
477 [appList isPatchingProcessID: pid];
478}
479
480@end
481
482@implementation FSAApp (NSWorkspaceNotifications)
483
484- (void)applicationDidLaunch:(NSNotification *)notification;
485{
486 [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
487}
488
489- (void)applicationDidTerminate:(NSNotification *)notification;
490{
491 [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
492}
493
494@end
Note: See TracBrowser for help on using the repository browser.