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

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

Auto installation of F-Script as requested by Philippe Mougin. We'll optimize the FSA framework package later (not sure if it would be a good idea to remove headers in framework, etc.)

File size: 19.8 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>
[153]33
34NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
35NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
36
37typedef struct {
38 OSStatus err;
39 NSString * const desc;
40} errRec, errList[];
41
42// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
43static errList ERRS = { // XXX change for mach_inject/SCPatch
44 { 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" },
45 { 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" },
46 { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
47 { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application. Please try reinstalling F-Script Anywhere" },
48 { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
49 { 0, nil }
50};
51
52NSMutableDictionary *FSA_errors;
53
54NSString * FSA_descriptionForOSStatus(OSStatus err)
55{
56 NSString *desc = nil;
57
58 if (FSA_errors != nil)
59 desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
60
61 if (desc != nil)
62 return desc;
63
64 return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
65}
66
67FSAPatchController::FSAPatchController(FSAApp *fsaApp)
68{
69 mApp = fsaApp;
70}
71
72void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
73 pid_t pid;
74 OSStatus err = GetProcessPID(inPSN, &pid);
75
76 NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
77
78 FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
79 [mApp controllerIsPatchingApplicationWithProcessID: pid];
80}
81
82// XXX should remove?
83void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
84 OSErr err = noErr;
85 Size actualSize;
86 DescType actualType;
87 OSType eventClass, eventID;
88
89 if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
90 return;
91 if (eventClass != kSCMessageClass)
92 return;
93
94 if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
95 return;
[217]96
97#if 0
[153]98 if (eventID == kSCLoadResult) {
99 OSStatus
100 (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
101 (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
102 bundleID[actualSize] = 0;
103 printf("patch bundle %s loaded with error status %d\n", bundleID, error);
104 }
[217]105#endif
[153]106
107
108}
109
110
111@implementation FSAApp
112
113+ (void)initialize;
114{
115 errRec *rec;
116
117 FSA_errors = [[NSMutableDictionary alloc] init];
118 for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
119 [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
120}
121
[230]122-(void)installFScript
123{
124 OSStatus myStatus;
125
126 NSString *installPath = nil;
127 if([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/Frameworks/FScript.framework"])
128 installPath = @"/Library/Frameworks/FScript.framework";
129 if([[NSFileManager defaultManager] fileExistsAtPath:[@"~/Library/Frameworks/FScript.framework" stringByExpandingTildeInPath]])
130 installPath = [@"~/Library/Frameworks/FScript.framework" stringByExpandingTildeInPath];
131
132 if(installPath){
133 [[NSFileManager defaultManager] removeFileAtPath:installPath handler:nil];
134 }
135
136 if(!installPath){
137 switch([[NSAlert alertWithMessageText:@"Install F-Script"
138 defaultButton:@"Current User"
139 alternateButton:@"All Users"
140 otherButton:nil
141 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]){
142 case NSAlertDefaultReturn: // Current user
143 installPath = [@"~/Library/Frameworks/FScript.framework" stringByExpandingTildeInPath];
144 break;
145 case NSAlertAlternateReturn: // All users
146 installPath = @"/Library/Frameworks/FScript.framework";
147 break;
148 default:
149 break;
150 }
151 }
152
153 NSString *fromPath = [[NSBundle mainBundle] pathForResource:@"FScript" ofType:@"framework"];
154 if(![[NSFileManager defaultManager] fileExistsAtPath:[installPath stringByDeletingLastPathComponent]]){
155 if ([[NSFileManager defaultManager] isWritableFileAtPath:installPath]){
156 [[NSFileManager defaultManager] createDirectoryAtPath:[installPath stringByDeletingLastPathComponent]
157 attributes:nil];
158 } else {
159 [self createAuthorization];
160 NSString *myToolPath = @"/bin/mkdir";
161 char *myArguments[] = { (char *)[[installPath stringByDeletingLastPathComponent] UTF8String], NULL };
162 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
163 myFlags, myArguments,
164 NULL);
165 }
166 }
167 if([[NSFileManager defaultManager] isWritableFileAtPath:[installPath stringByDeletingLastPathComponent]]){
168 [[NSFileManager defaultManager] copyPath:fromPath toPath:installPath handler:nil];
169 } else {
170 [self createAuthorization];
171 NSString *myToolPath = @"/bin/cp";
172 char *myArguments[] = { "-r", (char *)[fromPath UTF8String], (char *)[installPath UTF8String], NULL };
173 myStatus = AuthorizationExecuteWithPrivileges(myAuthorizationRef, [myToolPath UTF8String],
174 myFlags, myArguments,
175 NULL);
176 }
177}
178
[153]179- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result;
180{
181 switch (result) {
182 case NSAlertOtherReturn:
183 return true;
[230]184 case NSAlertAlternateReturn: /* Install from resources */
185 [self installFScript];
186 return true;
[153]187 case NSAlertDefaultReturn:
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{
[229]251#ifdef __i386__
252 if(![[NSUserDefaults standardUserDefaults] objectForKey:@"doPathCheck"])
253 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"doPathCheck"];
254
255 if([[NSUserDefaults standardUserDefaults] boolForKey:@"doPathCheck"]){
256top:
257 PathManager *pm = [[PathManager alloc] initWithLocalNode];
258 [pm backupStack];
259 [pm cd:@"/Groups/procmod"];
260 CFDictionaryRef sessionInfoDict = CGSessionCopyCurrentDictionary();
261 if(sessionInfoDict){
262 CFStringRef shortUserName = (CFStringRef)CFDictionaryGetValue(sessionInfoDict, kCGSessionUserNameKey);
263 if(![[[pm lastObject] readArray:@"GroupMembership"] containsObject:(NSString *)shortUserName]){
264 switch([[NSAlert alertWithMessageText:[NSString stringWithFormat:@"User %@ not in the procmod group", shortUserName]
265 defaultButton:@"Add me"
266 alternateButton:@"Disable checking"
267 otherButton:@"Ignore message"
268 informativeTextWithFormat:@"F-Script Anywhere requires that you add yourself to the procmod "
269 "group in order for it to function properly. If you like, F-Script Anywhere can automatically add you "
270 "to the procmod group."] runModal]){
271 case NSAlertDefaultReturn:
272 {
273 tDirStatus status = [self authorizeAndAddToProcMod:(NSString *)shortUserName];
274 if(status != eDSNoErr){
275 [[NSAlert alertWithMessageText:@"Error adding to procmod group"
276 defaultButton:nil
277 alternateButton:nil
278 otherButton:nil
279 informativeTextWithFormat:@"There was an error (%@) adding you to the procmod group. ", [[NSClassFromString(@"DSoStatus") sharedInstance] stringForStatus:status]] runModal];
280 }
281 }
282 [pm restoreStack];
283 [pm release];
284 goto top;
285 case NSAlertAlternateReturn:
286 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"doPathCheck"];
287 break;
288 default:
289 break;
290 }
291 }
292 }
293 [pm restoreStack];
294 [pm release];
295 }
296#endif
[153]297 patchController = new FSAPatchController(self);
298 patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
299 (CFStringRef)PatchBundleName);
300
301 NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
302 pid_t pid;
303 ProcessSerialNumber psn;
304 OSStatus err;
305
306 while ( (pid = [[e nextObject] longValue]) != 0) {
307 err = GetProcessForPID(pid, &psn);
308
309 if (err != noErr) {
310 NSLog(@"Can't get PSN for PID %ld", pid);
311 [appList applicationQuitWithProcessID: pid];
312 } else if (patchController->IsProcessPatched(&psn)) {
313 [appList didPatchProcessID: pid];
314 }
315 }
316
317 // yes, someone could move the framework while the app is running, but they deserve what they get.
318 NSFileManager *fileMgr = [NSFileManager defaultManager];
319 NSArray *libraryDirectories =
320 NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
321 NSString *libPath;
322 NSString *frameworkPath;
323 CFBundleRef frameworkBundle = NULL;
324 BOOL isDirectory;
325
326 e = [libraryDirectories objectEnumerator];
327 while ( (libPath = [e nextObject]) != nil) {
328 frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
329 stringByAppendingPathComponent: @"FScript.framework"];
330 if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
331 frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
332 [(id)frameworkBundle autorelease];
333 break;
334 }
335 }
336 FSALog(@"Framework bundle");
337 FSAShow(frameworkBundle);
338 [super finishLaunching];
339 if (frameworkBundle == NULL) {
340 int result = NSRunInformationalAlertPanel(
341 NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
[230]342 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]343 "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
344 NSLocalizedString(@"Quit", "Quit button title"),
[230]345 NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
[153]346 NSLocalizedString(@"Continue", "Continue button title"),
347 FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
348 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
349 } else {
350 UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
351 FSALog(@"Framework version 0x%x", version);
352 if (version < FSA_FSCRIPT_MIN_VERSION) {
353 NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
354 if (frameworkVersion == nil)
355 frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
356 int result = NSRunInformationalAlertPanel(
357 NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
[230]358 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, 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 '%@'.",
[153]359 "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"),
360 NSLocalizedString(@"Quit", "Quit button title"),
[230]361 NSLocalizedString(@"Install F-Script", "'Get F-Script' button title"),
[153]362 NSLocalizedString(@"Continue", "Continue button title"),
363 FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
[230]364 FSA_FScriptURL, NSLocalizedString(@"Install F-Script", ""));
[153]365 if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
366 }
367 }
368
[230]369 if(myAuthorizationRef){
370 AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
371 myAuthorizationRef = nil;
372 }
373
[153]374 NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
375 [workspaceNotificationCenter
376 addObserver: self
377 selector: @selector(applicationDidLaunch:)
378 name: NSWorkspaceWillLaunchApplicationNotification
379 object: nil];
380 [workspaceNotificationCenter
381 addObserver: self
382 selector: @selector(applicationDidTerminate:)
383 name: NSWorkspaceDidTerminateApplicationNotification
384 object: nil];
385
386 [appListWindow makeKeyAndOrderFront: self];
387}
388
389- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
390{
391 NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
392 nil, nil, nil, appListWindow, self, nil, nil, nil,
393 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"),
394 pid, FSA_descriptionForOSStatus(err));
395}
396
397- (void)installBundleInAppWithPID:(pid_t)pid;
398{
399 if (pid == -1) return;
400
401 ProcessSerialNumber psn;
402 OSStatus err = GetProcessForPID(pid, &psn);
403
404 if (err == noErr)
405 err = patchController->PatchProcess(&psn);
[217]406
[153]407 if (err != noErr)
408 [self installationError: err inAppWithPID: pid];
[222]409 else
410 [appList didPatchProcessID: pid];
[153]411}
412
413- (void)loaderBundleMessage:(NSNotification *)notification;
414{
415 /*
416 if (error) {
417 [self installationError: error inAppWithPID: pid];
418 } else {
419 [appList didPatchProcessID: pid];
420 } */
421}
422
423- (IBAction)installBundleInSelectedApp:(id)sender;
424{
425 [self installBundleInAppWithPID: [appList selectedProcessID]];
426}
427
428// from dock menu only!
429- (IBAction)installBundleInFrontmostApp:(id)sender;
430{
431 NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
432 [self installBundleInAppWithPID: (pid_t)[sender tag]];
433}
434
435@end
436
437@implementation FSAApp (FSAPatchControllerDelegate)
438
439- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
440{
441 [appList isPatchingProcessID: pid];
442}
443
444@end
445
446@implementation FSAApp (NSWorkspaceNotifications)
447
448- (void)applicationDidLaunch:(NSNotification *)notification;
449{
450 [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
451}
452
453- (void)applicationDidTerminate:(NSNotification *)notification;
454{
455 [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
456}
457
458@end
Note: See TracBrowser for help on using the repository browser.