PageRenderTime 90ms CodeModel.GetById 13ms app.highlight 72ms RepoModel.GetById 2ms app.codeStats 0ms

/.attic/sshfs-gui/DiskImageUtilities.m

http://macfuse.googlecode.com/
Objective C | 341 lines | 218 code | 60 blank | 63 comment | 53 complexity | eff1ef530efaa965704536db72bc0962 MD5 | raw file
  1// sshfs.app
  2// Copyright 2007, Google Inc.
  3//
  4// Redistribution and use in source and binary forms, with or without 
  5// modification, are permitted provided that the following conditions are met:
  6//
  7//  1. Redistributions of source code must retain the above copyright notice, 
  8//     this list of conditions and the following disclaimer.
  9//  2. Redistributions in binary form must reproduce the above copyright notice,
 10//     this list of conditions and the following disclaimer in the documentation
 11//     and/or other materials provided with the distribution.
 12//  3. The name of the author may not be used to endorse or promote products 
 13//     derived from this software without specific prior written permission.
 14//
 15// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 16// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 17// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 18// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 19// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 20// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 21// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 22// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 23// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 24// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 25
 26#include <unistd.h>
 27#include <Security/Security.h>
 28#include <SecurityFoundation/SFAuthorization.h>
 29
 30#import "DiskImageUtilities.h"
 31
 32static NSString* const kHDIUtilPath = @"/usr/bin/hdiutil";
 33
 34@interface DiskImageUtilities (PrivateMethods_handleApplicationLaunchFromReadOnlyDiskImage)
 35+ (BOOL)canWriteToPath:(NSString *)path;
 36+ (void)copyAndLaunchAppAtPath:(NSString *)oldPath
 37               toDirectoryPath:(NSString *)newDirectory;
 38+ (void)killAppAtPath:(NSString *)appPath;
 39+ (BOOL)doAuthorizedCopyFromPath:(NSString *)src toPath:(NSString *)dest;
 40@end
 41
 42@implementation DiskImageUtilities
 43
 44// get the mounted disk image info as a dictionary
 45+ (NSDictionary *)diskImageInfo {
 46  
 47  NSDictionary *resultDict = nil;
 48  
 49  NSArray* args = [NSArray arrayWithObjects:@"info", @"-plist", nil];
 50  // -plist means the results will be written as a property list to stdout
 51  
 52  NSPipe* outputPipe = [NSPipe pipe];
 53  NSTask* theTask = [[[NSTask alloc] init] autorelease];
 54  [theTask setLaunchPath:kHDIUtilPath];
 55  [theTask setArguments:args];
 56  [theTask setStandardOutput:outputPipe];
 57  
 58  [theTask launch];
 59  
 60  NSFileHandle *outputFile = [outputPipe fileHandleForReading];
 61  NSData *plistData = nil;
 62  @try {
 63    plistData = [outputFile readDataToEndOfFile]; // blocks until EOF delivered
 64  }
 65  @catch(id obj) {
 66    // running in gdb we get exception: Interrupted system call
 67    NSLog(@"DiskImageUtilities diskImageInfo: gdb issue -- "
 68                "getting file data causes exception: %@", obj);
 69  }
 70  [theTask waitUntilExit];
 71  int status = [theTask terminationStatus];
 72  
 73  if (status != 0 || [plistData length] == 0) {
 74    
 75    NSLog(@"DiskImageUtilities diskImageInfo: hdiutil failed, result %d", status); 
 76    
 77  } else {
 78    NSString *plist = [[[NSString alloc] initWithData:plistData 
 79                                             encoding:NSUTF8StringEncoding] autorelease];
 80    resultDict = [plist propertyList];
 81  }    
 82  return resultDict;
 83}
 84
 85+ (NSArray *)readOnlyDiskImagePaths {
 86  
 87  NSMutableArray *paths = [NSMutableArray array];
 88  NSDictionary *dict = [self diskImageInfo];
 89  if (dict) {
 90    
 91    NSArray *imagesArray = [dict objectForKey:@"images"];
 92    
 93    // we have an array of dicts for the known images
 94    //
 95    // we want to find non-writable images, and get the mount
 96    // points from their system entities
 97    
 98    if (imagesArray) {
 99      int idx;
100      unsigned int numberOfImages = [imagesArray count];
101      for (idx = 0; idx < numberOfImages; idx++) {
102        
103        NSDictionary *imageDict = [imagesArray objectAtIndex:idx];
104        NSNumber *isWriteable = [imageDict objectForKey:@"writeable"];
105        if (isWriteable && ![isWriteable boolValue]) {
106          
107          NSArray *systemEntitiesArray = [imageDict objectForKey:@"system-entities"];
108          if (systemEntitiesArray) {
109            int idx;
110            unsigned int numberOfSystemEntities = [systemEntitiesArray count];
111            for (idx = 0; idx < numberOfSystemEntities; idx++) {
112              
113              NSDictionary *entityDict = [systemEntitiesArray objectAtIndex:idx];
114              NSString *mountPoint = [entityDict objectForKey:@"mount-point"];
115              if ([mountPoint length] > 0) {
116                
117                // found a read-only image mount point; add it to our list
118                // and move to the next image
119                [paths addObject:mountPoint];
120                break;
121              }
122            }
123          }
124        }
125      }
126    }
127  }
128  return paths;
129}
130
131// checks if the current app is running from a disk image,
132// displays a dialog offering to copy to /Applications, and
133// does the copying
134
135+ (void)handleApplicationLaunchFromReadOnlyDiskImage {
136  
137  NSString * const kLastLaunchedPathKey = @"LastLaunchedPath";
138  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
139  
140  NSString *lastLaunchedPath = [defaults objectForKey:kLastLaunchedPathKey];
141  NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
142  
143  BOOL isRunningFromDiskImage = NO;
144  
145  if (lastLaunchedPath == nil 
146      || ![lastLaunchedPath isEqualToString:mainBundlePath]) {
147    
148    // we haven't tested this launch path; check it now
149    NSArray *imagePaths = [self readOnlyDiskImagePaths];
150    
151    int idx;
152    for (idx = 0; idx < [imagePaths count]; idx++) {
153      
154      NSString *imagePath = [imagePaths objectAtIndex:idx];
155      if (![imagePath hasSuffix:@"/"])
156        imagePath = [NSString stringWithFormat:@"%@/", imagePath];
157      if ([mainBundlePath hasPrefix:imagePath]) {
158        
159        isRunningFromDiskImage = YES;
160        break;
161        
162      }
163    }
164    
165    // ? should we ask every time the user runs from a read-only disk image
166    if (!isRunningFromDiskImage) {
167      
168      // we don't need to check this bundle path again
169      [defaults setObject:mainBundlePath forKey:kLastLaunchedPathKey];
170      
171    } else {
172      // we're running from a disk image
173      
174      [NSApp activateIgnoringOtherApps:YES];
175      
176      NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:mainBundlePath];
177      NSString *msg1template = NSLocalizedString(@"Would you like to copy %@ to your "
178                                                 @"computer's Applications folder and "
179                                                 @"run it from there?",
180                                                 @"Copy app from disk image: title");
181      NSString *msg1 = [NSString stringWithFormat:msg1template, displayName];
182      NSString *msg2 = NSLocalizedString(@"%@ is currently running from the Disk Image, "
183                                         @"and must be copied for full functionality. "
184                                         @"Copying may replace an older version in the "
185                                         @"Applications directory.",
186                                         @"Copy app from disk image: message");
187      NSString *btnOK = NSLocalizedString(@"Copy",
188                                          @"Copy app from disk image: ok button");
189      NSString *btnCancel = NSLocalizedString(@"Don't Copy",
190                                              @"Copy app from disk image: cancel button");
191      
192      int result = NSRunAlertPanel(msg1, msg2, btnOK, btnCancel, NULL, displayName);
193      if (result == NSAlertDefaultReturn) {
194        // copy to /Applications and launch from there
195        
196        NSArray *appsPaths = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
197                                                                 NSLocalDomainMask,
198                                                                 YES);
199        if ([appsPaths count] > 0) {
200          
201          [self copyAndLaunchAppAtPath:mainBundlePath
202                       toDirectoryPath:[appsPaths objectAtIndex:0]];  
203          // calls exit(0) on successful copy/launch
204        } else {
205          NSLog(@"DiskImageUtilities: Cannot make applications folder path");
206        }
207      }
208    }
209  }
210}
211
212// copies an application from the given path to a new directory, if necessary
213// authenticating as admin or killing a running process at that location
214+ (void)copyAndLaunchAppAtPath:(NSString *)oldPath
215               toDirectoryPath:(NSString *)newDirectory {
216  
217  NSFileManager *fileManager = [NSFileManager defaultManager];
218  NSString *pathInApps = [newDirectory stringByAppendingPathComponent:[oldPath lastPathComponent]];
219  BOOL isDir;
220  BOOL dirPathExists = [fileManager fileExistsAtPath:pathInApps isDirectory:&isDir] && isDir;
221  
222  // We must authenticate as admin if we don't have write permission
223  // in the /Apps directory, or if there's already an app there
224  // with the same name and we don't have permission to write to it
225  BOOL mustAuth = (![self canWriteToPath:newDirectory] 
226                   || (dirPathExists && ![self canWriteToPath:pathInApps]));
227  
228  [self killAppAtPath:pathInApps];
229  
230  BOOL didCopy;
231  if (mustAuth) {
232    didCopy = [self doAuthorizedCopyFromPath:oldPath toPath:pathInApps];
233  } else {
234    didCopy = [fileManager copyPath:oldPath toPath:pathInApps handler:nil];
235  }
236
237  if (didCopy) {
238    // launch the new copy and bail
239    LSLaunchURLSpec spec;
240    spec.appURL = (CFURLRef) [NSURL fileURLWithPath:pathInApps];
241    spec.launchFlags = kLSLaunchNewInstance;
242    spec.itemURLs = NULL;
243    spec.passThruParams = NULL;
244    spec.asyncRefCon = NULL;
245    
246    OSStatus err = LSOpenFromURLSpec(&spec, NULL); // NULL -> don't care about the launched URL
247    if (err == noErr) {
248      exit(0);
249    } else {
250      NSLog(@"DiskImageUtilities: Error %d launching \"%@\"", err, pathInApps);
251    }
252  } else {
253    // copying to /Applications failed 
254    NSLog(@"DiskImageUtilities: Error copying to \"%@\"", pathInApps);     
255  }
256}
257
258// looks for an app running from the specified path, and calls KillProcess on it
259+ (void)killAppAtPath:(NSString *)appPath {
260  
261  // get the FSRef for bundle of the the target app to kill
262  FSRef targetFSRef;
263  OSStatus err = FSPathMakeRef((const UInt8 *)[appPath fileSystemRepresentation],
264                               &targetFSRef, nil);
265  if (err == noErr) {
266    
267    // search for a PSN of a process with the bundle at that location, if any
268    ProcessSerialNumber psn = { 0, kNoProcess };
269    while (GetNextProcess(&psn) == noErr) {
270      
271      FSRef compareFSRef;
272      if (GetProcessBundleLocation(&psn, &compareFSRef) == noErr
273          && FSCompareFSRefs(&compareFSRef, &targetFSRef) == noErr) {
274        
275        // we found an app running from that path; kill it
276        err = KillProcess(&psn);
277        if (err != noErr) {
278          NSLog(@"DiskImageUtilities: Could not kill process at %@, error %d",
279                appPath, err);
280        } 
281      }
282    }
283  }
284}
285
286// canWriteToPath checks for permissions to write into the directory |path|
287+ (BOOL)canWriteToPath:(NSString *)path {
288  int stat = access([path fileSystemRepresentation], (W_OK | R_OK));
289  return (stat == 0);
290}
291
292// doAuthorizedCopyFromPath does an authorized copy, getting admin rights
293//
294// NOTE: when running the task with admin privileges, this waits on any child 
295// process, since AEWP doesn't tell us the child's pid.  This could be fooled 
296// by any other child process that quits in the window between launch and 
297// completion of our actual tool.
298+ (BOOL)doAuthorizedCopyFromPath:(NSString *)src toPath:(NSString *)dest {
299  // authorize
300  AuthorizationFlags authFlags =  kAuthorizationFlagPreAuthorize 
301                                | kAuthorizationFlagExtendRights
302                                | kAuthorizationFlagInteractionAllowed;
303  AuthorizationItem authItem = {kAuthorizationRightExecute, 0, nil, 0}; 
304  AuthorizationRights authRights = {1, &authItem}; 
305  SFAuthorization *authorization = [SFAuthorization authorizationWithFlags:authFlags
306                                                                    rights:&authRights
307                                                               environment:kAuthorizationEmptyEnvironment];
308  
309  // execute the copy
310  const char taskPath[] = "/usr/bin/ditto";
311  const char* arguments[] = { 
312    "-rsrcFork",  // 0: copy resource forks; --rsrc requires 10.3
313    NULL,  // 1: src path
314    NULL,  // 2: dest path
315    NULL 
316  };
317  arguments[1] = [src fileSystemRepresentation];
318  arguments[2] = [dest fileSystemRepresentation];
319  
320  FILE **kNoPipe = nil;
321  OSStatus status = AuthorizationExecuteWithPrivileges([authorization authorizationRef],
322                                                       taskPath,
323                                                       kAuthorizationFlagDefaults,
324                                                       (char *const *)arguments,
325                                                       kNoPipe);
326  if (status == errAuthorizationSuccess) {
327    int wait_status;
328    int pid = wait(&wait_status);
329    if (pid == -1 || !WIFEXITED(wait_status))	{
330      status = -1;
331    } else {
332      status = WEXITSTATUS(wait_status);
333    }
334  }
335  
336  // deauthorize
337  [authorization invalidateCredentials];
338  
339  return (status == 0);
340}
341@end