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

Last change on this file since 222 was 222, checked in by rchin, 14 years ago

Fixed problem with F-Script leaking file handles (caused F-Script to not be able to inject into applications after a certain number of running apps had been launched). The problem was in appIsPEF calling open(2) but not close(2) before returning.

File size: 11.7 KB
Line 
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"
29
30NSString * const PatchBundleIdentifier = @"net.sabi.FScriptAnywhere";
31NSString * const PatchBundleName = @"F-Script Anywhere.bundle";
32
33typedef struct {
34    OSStatus err;
35    NSString * const desc;
36} errRec, errList[];
37
38// These are just educated guesses based on the errors I've seen; I haven't seen these documented anywhere!
39static errList ERRS = { // XXX change for mach_inject/SCPatch
40    { 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" },
41    { 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" },
42    { smUnExBusErr, @"a bus error occurred.\n\nTry switching to the application first then using F-Script AnywhereÕs dock menu to install" },
43    { fnfErr, @"F-Script Anywhere was unable to locate its component to install in the application.  Please try reinstalling F-Script Anywhere" },
44    { cfragDupRegistrationErr, @"another running copy of F-Script Anywhere is already installed in the application"},
45    { 0, nil }
46};
47
48NSMutableDictionary *FSA_errors;
49
50NSString * FSA_descriptionForOSStatus(OSStatus err)
51{
52    NSString *desc = nil;
53   
54    if (FSA_errors != nil)
55        desc = [FSA_errors objectForKey: [NSNumber numberWithLong: err]];
56   
57    if (desc != nil)
58        return desc;
59   
60    return [NSString stringWithFormat: NSLocalizedString(@"an error of type %ld occurred", "Unknown error message used after 'XXX happened because'"), err];
61}
62
63FSAPatchController::FSAPatchController(FSAApp *fsaApp)
64{
65    mApp = fsaApp;
66}
67
68void FSAPatchController::PatchNotification(ProcessSerialNumber *inPSN, OSType inCreator, OSType type, CFStringRef name, UInt32 flags) {
69    pid_t pid;
70    OSStatus err = GetProcessPID(inPSN, &pid);
71   
72    NSCAssert3(err == noErr, @"PatchNotification can't get PID for PSN %ld.%ld: error %ld", inPSN->highLongOfPSN, inPSN->lowLongOfPSN, err);
73   
74    FSALog(@"Got patch notification for %@ (pid %d)", (NSString *)name, pid);
75    [mApp controllerIsPatchingApplicationWithProcessID: pid];
76}
77
78// XXX should remove?
79void FSAPatchController::ReceiveMessage(const AppleEvent *theAE) {
80    OSErr err = noErr;
81    Size actualSize;
82    DescType actualType;
83    OSType eventClass, eventID;
84   
85    if ((err = AEGetAttributePtr(theAE, keyEventClassAttr, typeType, &actualType, &eventClass, sizeof(OSType), &actualSize)) != noErr)
86        return;
87    if (eventClass != kSCMessageClass)
88        return;
89
90    if ((err = AEGetAttributePtr(theAE, keyEventIDAttr, typeType, &actualType, &eventID, sizeof(OSType), &actualSize)) != noErr)
91        return;
92 
93#if 0
94    if (eventID == kSCLoadResult) {
95        OSStatus
96        (err = AEGetParamPtr(theAE, keyError, typeInteger, &actualType, &error, sizeof(long), &actualSize)) == noErr &&
97        (err = AEGetParamPtr(theAE, keyBundleID, typeText, &actualType, bundleID, sizeof(bundleID), &actualSize)) == noErr)
98        bundleID[actualSize] = 0;
99        printf("patch bundle %s loaded with error status %d\n", bundleID, error);
100    }
101#endif
102   
103
104}
105
106
107@implementation FSAApp
108
109+ (void)initialize;
110{
111    errRec *rec;
112
113    FSA_errors = [[NSMutableDictionary alloc] init];
114    for (rec = &(ERRS[0]) ; rec->err != 0 ; rec++)
115        [FSA_errors setObject: rec->desc forKey: [NSNumber numberWithLong: rec->err]];
116}
117
118- (BOOL)_shouldContinueAfterFrameworkErrorPanelReturnedWithResult:(int)result;
119{
120    switch (result) {
121        case NSAlertOtherReturn:
122            return true;
123        case NSAlertAlternateReturn:
124            [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FSA_FScriptURL]];
125        case NSAlertDefaultReturn:
126        default:
127            [self terminate: self];
128            return false;
129    }
130}
131
132- (void)finishLaunching
133{
134    patchController = new FSAPatchController(self);
135    patchController->AddPatch((CFStringRef)PatchBundleIdentifier, CFSTR("Contents/Resources/"),
136                              (CFStringRef)PatchBundleName);
137   
138    NSEnumerator *e = [[appList cocoaAppProcessIDs] objectEnumerator];
139    pid_t pid;
140    ProcessSerialNumber psn;
141    OSStatus err;
142   
143    while ( (pid = [[e nextObject] longValue]) != 0) {
144        err = GetProcessForPID(pid, &psn);
145       
146        if (err != noErr) {
147            NSLog(@"Can't get PSN for PID %ld", pid);
148            [appList applicationQuitWithProcessID: pid];
149        } else if (patchController->IsProcessPatched(&psn)) {
150            [appList didPatchProcessID: pid];
151        }
152    }
153
154    // yes, someone could move the framework while the app is running, but they deserve what they get.
155    NSFileManager *fileMgr = [NSFileManager defaultManager];
156    NSArray *libraryDirectories =
157        NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, TRUE);
158    NSString *libPath;
159    NSString *frameworkPath;
160    CFBundleRef frameworkBundle = NULL;
161    BOOL isDirectory;
162
163    e = [libraryDirectories objectEnumerator];
164    while ( (libPath = [e nextObject]) != nil) {
165        frameworkPath = [[libPath stringByAppendingPathComponent: @"Frameworks"]
166            stringByAppendingPathComponent: @"FScript.framework"];
167        if ([fileMgr fileExistsAtPath: frameworkPath isDirectory: &isDirectory] && isDirectory) {
168            frameworkBundle = CFBundleCreate(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath: frameworkPath]);
169            [(id)frameworkBundle autorelease];
170            break;
171        }
172    }
173    FSALog(@"Framework bundle");
174    FSAShow(frameworkBundle);
175    [super finishLaunching];
176    if (frameworkBundle == NULL) {
177        int result = NSRunInformationalAlertPanel(
178            NSLocalizedString(@"F-Script Framework Not Found", "Framework not found alert title"),
179            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\nIf you believe this message is in error, click '%@'.",
180                              "Framework not found alert message, F-Script Web site URL parameter, 'Continue' button parameter"),
181            NSLocalizedString(@"Quit", "Quit button title"),
182            NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
183            NSLocalizedString(@"Continue", "Continue button title"),
184            FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
185        if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
186    } else {
187        UInt32 version = CFBundleGetVersionNumber(frameworkBundle);
188        FSALog(@"Framework version 0x%x", version);
189        if (version < FSA_FSCRIPT_MIN_VERSION) {
190            NSString *frameworkVersion = (NSString *)CFBundleGetValueForInfoDictionaryKey(frameworkBundle, kCFBundleVersionKey);
191            if (frameworkVersion == nil)
192                frameworkVersion = NSLocalizedString(@"unavailable", "Framework too old 'unavailable' version");
193            int result = NSRunInformationalAlertPanel(
194                NSLocalizedString(@"F-Script Framework Too Old", "Framework too old alert title"),
195                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\nTo use this version of F-Script regardless of the problems you may experience, click '%@'.",
196                                  "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"),
197                NSLocalizedString(@"Quit", "Quit button title"),
198                NSLocalizedString(@"Get F-Script", "'Get F-Script' button title"),
199                NSLocalizedString(@"Continue", "Continue button title"),
200                FSA_VERSION, FSA_FScriptMinimumVersion, frameworkVersion,
201                FSA_FScriptURL, NSLocalizedString(@"Continue", ""));
202            if (![self _shouldContinueAfterFrameworkErrorPanelReturnedWithResult: result]) return;
203        }
204    }
205
206    NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
207    [workspaceNotificationCenter
208        addObserver: self
209           selector: @selector(applicationDidLaunch:)
210               name: NSWorkspaceWillLaunchApplicationNotification
211             object: nil];
212    [workspaceNotificationCenter
213        addObserver: self
214           selector: @selector(applicationDidTerminate:)
215               name: NSWorkspaceDidTerminateApplicationNotification
216             object: nil];
217   
218    [appListWindow makeKeyAndOrderFront: self];
219}
220
221- (void)installationError:(OSStatus)err inAppWithPID:(pid_t)pid;
222{
223    NSBeginAlertSheet(NSLocalizedString(@"Installation failed", "Unable to install alert title"),
224                      nil, nil, nil, appListWindow, self, nil, nil, nil,
225                      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"),
226                      pid, FSA_descriptionForOSStatus(err));
227}
228
229- (void)installBundleInAppWithPID:(pid_t)pid;
230{
231    if (pid == -1) return;
232       
233    ProcessSerialNumber psn;
234    OSStatus err = GetProcessForPID(pid, &psn);
235   
236    if (err == noErr)
237        err = patchController->PatchProcess(&psn);
238
239    if (err != noErr)
240        [self installationError: err inAppWithPID: pid];
241    else
242        [appList didPatchProcessID: pid];
243}
244
245- (void)loaderBundleMessage:(NSNotification *)notification;
246{
247    /*
248    if (error) {
249        [self installationError: error inAppWithPID: pid];
250    } else {
251        [appList didPatchProcessID: pid];
252    } */
253}
254
255- (IBAction)installBundleInSelectedApp:(id)sender;
256{
257    [self installBundleInAppWithPID: [appList selectedProcessID]];
258}
259
260// from dock menu only!
261- (IBAction)installBundleInFrontmostApp:(id)sender;
262{
263    NSAssert1([sender tag] > 0, @"Unable to determine frontmost application from %@", sender);
264    [self installBundleInAppWithPID: (pid_t)[sender tag]];
265}
266
267@end
268
269@implementation FSAApp (FSAPatchControllerDelegate)
270
271- (void)controllerIsPatchingApplicationWithProcessID:(pid_t)pid;
272{
273    [appList isPatchingProcessID: pid];
274}
275
276@end
277
278@implementation FSAApp (NSWorkspaceNotifications)
279
280- (void)applicationDidLaunch:(NSNotification *)notification;
281{
282    [appList applicationLaunchedWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
283}
284
285- (void)applicationDidTerminate:(NSNotification *)notification;
286{
287    [appList applicationQuitWithProcessID: [[[notification userInfo] objectForKey: @"NSApplicationProcessIdentifier"] intValue]];
288}
289
290@end
Note: See TracBrowser for help on using the repository browser.