/.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. #include <unistd.h>
  26. #include <Security/Security.h>
  27. #include <SecurityFoundation/SFAuthorization.h>
  28. #import "DiskImageUtilities.h"
  29. static NSString* const kHDIUtilPath = @"/usr/bin/hdiutil";
  30. @interface DiskImageUtilities (PrivateMethods_handleApplicationLaunchFromReadOnlyDiskImage)
  31. + (BOOL)canWriteToPath:(NSString *)path;
  32. + (void)copyAndLaunchAppAtPath:(NSString *)oldPath
  33. toDirectoryPath:(NSString *)newDirectory;
  34. + (void)killAppAtPath:(NSString *)appPath;
  35. + (BOOL)doAuthorizedCopyFromPath:(NSString *)src toPath:(NSString *)dest;
  36. @end
  37. @implementation DiskImageUtilities
  38. // get the mounted disk image info as a dictionary
  39. + (NSDictionary *)diskImageInfo {
  40. NSDictionary *resultDict = nil;
  41. NSArray* args = [NSArray arrayWithObjects:@"info", @"-plist", nil];
  42. // -plist means the results will be written as a property list to stdout
  43. NSPipe* outputPipe = [NSPipe pipe];
  44. NSTask* theTask = [[[NSTask alloc] init] autorelease];
  45. [theTask setLaunchPath:kHDIUtilPath];
  46. [theTask setArguments:args];
  47. [theTask setStandardOutput:outputPipe];
  48. [theTask launch];
  49. NSFileHandle *outputFile = [outputPipe fileHandleForReading];
  50. NSData *plistData = nil;
  51. @try {
  52. plistData = [outputFile readDataToEndOfFile]; // blocks until EOF delivered
  53. }
  54. @catch(id obj) {
  55. // running in gdb we get exception: Interrupted system call
  56. NSLog(@"DiskImageUtilities diskImageInfo: gdb issue -- "
  57. "getting file data causes exception: %@", obj);
  58. }
  59. [theTask waitUntilExit];
  60. int status = [theTask terminationStatus];
  61. if (status != 0 || [plistData length] == 0) {
  62. NSLog(@"DiskImageUtilities diskImageInfo: hdiutil failed, result %d", status);
  63. } else {
  64. NSString *plist = [[[NSString alloc] initWithData:plistData
  65. encoding:NSUTF8StringEncoding] autorelease];
  66. resultDict = [plist propertyList];
  67. }
  68. return resultDict;
  69. }
  70. + (NSArray *)readOnlyDiskImagePaths {
  71. NSMutableArray *paths = [NSMutableArray array];
  72. NSDictionary *dict = [self diskImageInfo];
  73. if (dict) {
  74. NSArray *imagesArray = [dict objectForKey:@"images"];
  75. // we have an array of dicts for the known images
  76. //
  77. // we want to find non-writable images, and get the mount
  78. // points from their system entities
  79. if (imagesArray) {
  80. int idx;
  81. unsigned int numberOfImages = [imagesArray count];
  82. for (idx = 0; idx < numberOfImages; idx++) {
  83. NSDictionary *imageDict = [imagesArray objectAtIndex:idx];
  84. NSNumber *isWriteable = [imageDict objectForKey:@"writeable"];
  85. if (isWriteable && ![isWriteable boolValue]) {
  86. NSArray *systemEntitiesArray = [imageDict objectForKey:@"system-entities"];
  87. if (systemEntitiesArray) {
  88. int idx;
  89. unsigned int numberOfSystemEntities = [systemEntitiesArray count];
  90. for (idx = 0; idx < numberOfSystemEntities; idx++) {
  91. NSDictionary *entityDict = [systemEntitiesArray objectAtIndex:idx];
  92. NSString *mountPoint = [entityDict objectForKey:@"mount-point"];
  93. if ([mountPoint length] > 0) {
  94. // found a read-only image mount point; add it to our list
  95. // and move to the next image
  96. [paths addObject:mountPoint];
  97. break;
  98. }
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. return paths;
  106. }
  107. // checks if the current app is running from a disk image,
  108. // displays a dialog offering to copy to /Applications, and
  109. // does the copying
  110. + (void)handleApplicationLaunchFromReadOnlyDiskImage {
  111. NSString * const kLastLaunchedPathKey = @"LastLaunchedPath";
  112. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  113. NSString *lastLaunchedPath = [defaults objectForKey:kLastLaunchedPathKey];
  114. NSString *mainBundlePath = [[NSBundle mainBundle] bundlePath];
  115. BOOL isRunningFromDiskImage = NO;
  116. if (lastLaunchedPath == nil
  117. || ![lastLaunchedPath isEqualToString:mainBundlePath]) {
  118. // we haven't tested this launch path; check it now
  119. NSArray *imagePaths = [self readOnlyDiskImagePaths];
  120. int idx;
  121. for (idx = 0; idx < [imagePaths count]; idx++) {
  122. NSString *imagePath = [imagePaths objectAtIndex:idx];
  123. if (![imagePath hasSuffix:@"/"])
  124. imagePath = [NSString stringWithFormat:@"%@/", imagePath];
  125. if ([mainBundlePath hasPrefix:imagePath]) {
  126. isRunningFromDiskImage = YES;
  127. break;
  128. }
  129. }
  130. // ? should we ask every time the user runs from a read-only disk image
  131. if (!isRunningFromDiskImage) {
  132. // we don't need to check this bundle path again
  133. [defaults setObject:mainBundlePath forKey:kLastLaunchedPathKey];
  134. } else {
  135. // we're running from a disk image
  136. [NSApp activateIgnoringOtherApps:YES];
  137. NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:mainBundlePath];
  138. NSString *msg1template = NSLocalizedString(@"Would you like to copy %@ to your "
  139. @"computer's Applications folder and "
  140. @"run it from there?",
  141. @"Copy app from disk image: title");
  142. NSString *msg1 = [NSString stringWithFormat:msg1template, displayName];
  143. NSString *msg2 = NSLocalizedString(@"%@ is currently running from the Disk Image, "
  144. @"and must be copied for full functionality. "
  145. @"Copying may replace an older version in the "
  146. @"Applications directory.",
  147. @"Copy app from disk image: message");
  148. NSString *btnOK = NSLocalizedString(@"Copy",
  149. @"Copy app from disk image: ok button");
  150. NSString *btnCancel = NSLocalizedString(@"Don't Copy",
  151. @"Copy app from disk image: cancel button");
  152. int result = NSRunAlertPanel(msg1, msg2, btnOK, btnCancel, NULL, displayName);
  153. if (result == NSAlertDefaultReturn) {
  154. // copy to /Applications and launch from there
  155. NSArray *appsPaths = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
  156. NSLocalDomainMask,
  157. YES);
  158. if ([appsPaths count] > 0) {
  159. [self copyAndLaunchAppAtPath:mainBundlePath
  160. toDirectoryPath:[appsPaths objectAtIndex:0]];
  161. // calls exit(0) on successful copy/launch
  162. } else {
  163. NSLog(@"DiskImageUtilities: Cannot make applications folder path");
  164. }
  165. }
  166. }
  167. }
  168. }
  169. // copies an application from the given path to a new directory, if necessary
  170. // authenticating as admin or killing a running process at that location
  171. + (void)copyAndLaunchAppAtPath:(NSString *)oldPath
  172. toDirectoryPath:(NSString *)newDirectory {
  173. NSFileManager *fileManager = [NSFileManager defaultManager];
  174. NSString *pathInApps = [newDirectory stringByAppendingPathComponent:[oldPath lastPathComponent]];
  175. BOOL isDir;
  176. BOOL dirPathExists = [fileManager fileExistsAtPath:pathInApps isDirectory:&isDir] && isDir;
  177. // We must authenticate as admin if we don't have write permission
  178. // in the /Apps directory, or if there's already an app there
  179. // with the same name and we don't have permission to write to it
  180. BOOL mustAuth = (![self canWriteToPath:newDirectory]
  181. || (dirPathExists && ![self canWriteToPath:pathInApps]));
  182. [self killAppAtPath:pathInApps];
  183. BOOL didCopy;
  184. if (mustAuth) {
  185. didCopy = [self doAuthorizedCopyFromPath:oldPath toPath:pathInApps];
  186. } else {
  187. didCopy = [fileManager copyPath:oldPath toPath:pathInApps handler:nil];
  188. }
  189. if (didCopy) {
  190. // launch the new copy and bail
  191. LSLaunchURLSpec spec;
  192. spec.appURL = (CFURLRef) [NSURL fileURLWithPath:pathInApps];
  193. spec.launchFlags = kLSLaunchNewInstance;
  194. spec.itemURLs = NULL;
  195. spec.passThruParams = NULL;
  196. spec.asyncRefCon = NULL;
  197. OSStatus err = LSOpenFromURLSpec(&spec, NULL); // NULL -> don't care about the launched URL
  198. if (err == noErr) {
  199. exit(0);
  200. } else {
  201. NSLog(@"DiskImageUtilities: Error %d launching \"%@\"", err, pathInApps);
  202. }
  203. } else {
  204. // copying to /Applications failed
  205. NSLog(@"DiskImageUtilities: Error copying to \"%@\"", pathInApps);
  206. }
  207. }
  208. // looks for an app running from the specified path, and calls KillProcess on it
  209. + (void)killAppAtPath:(NSString *)appPath {
  210. // get the FSRef for bundle of the the target app to kill
  211. FSRef targetFSRef;
  212. OSStatus err = FSPathMakeRef((const UInt8 *)[appPath fileSystemRepresentation],
  213. &targetFSRef, nil);
  214. if (err == noErr) {
  215. // search for a PSN of a process with the bundle at that location, if any
  216. ProcessSerialNumber psn = { 0, kNoProcess };
  217. while (GetNextProcess(&psn) == noErr) {
  218. FSRef compareFSRef;
  219. if (GetProcessBundleLocation(&psn, &compareFSRef) == noErr
  220. && FSCompareFSRefs(&compareFSRef, &targetFSRef) == noErr) {
  221. // we found an app running from that path; kill it
  222. err = KillProcess(&psn);
  223. if (err != noErr) {
  224. NSLog(@"DiskImageUtilities: Could not kill process at %@, error %d",
  225. appPath, err);
  226. }
  227. }
  228. }
  229. }
  230. }
  231. // canWriteToPath checks for permissions to write into the directory |path|
  232. + (BOOL)canWriteToPath:(NSString *)path {
  233. int stat = access([path fileSystemRepresentation], (W_OK | R_OK));
  234. return (stat == 0);
  235. }
  236. // doAuthorizedCopyFromPath does an authorized copy, getting admin rights
  237. //
  238. // NOTE: when running the task with admin privileges, this waits on any child
  239. // process, since AEWP doesn't tell us the child's pid. This could be fooled
  240. // by any other child process that quits in the window between launch and
  241. // completion of our actual tool.
  242. + (BOOL)doAuthorizedCopyFromPath:(NSString *)src toPath:(NSString *)dest {
  243. // authorize
  244. AuthorizationFlags authFlags = kAuthorizationFlagPreAuthorize
  245. | kAuthorizationFlagExtendRights
  246. | kAuthorizationFlagInteractionAllowed;
  247. AuthorizationItem authItem = {kAuthorizationRightExecute, 0, nil, 0};
  248. AuthorizationRights authRights = {1, &authItem};
  249. SFAuthorization *authorization = [SFAuthorization authorizationWithFlags:authFlags
  250. rights:&authRights
  251. environment:kAuthorizationEmptyEnvironment];
  252. // execute the copy
  253. const char taskPath[] = "/usr/bin/ditto";
  254. const char* arguments[] = {
  255. "-rsrcFork", // 0: copy resource forks; --rsrc requires 10.3
  256. NULL, // 1: src path
  257. NULL, // 2: dest path
  258. NULL
  259. };
  260. arguments[1] = [src fileSystemRepresentation];
  261. arguments[2] = [dest fileSystemRepresentation];
  262. FILE **kNoPipe = nil;
  263. OSStatus status = AuthorizationExecuteWithPrivileges([authorization authorizationRef],
  264. taskPath,
  265. kAuthorizationFlagDefaults,
  266. (char *const *)arguments,
  267. kNoPipe);
  268. if (status == errAuthorizationSuccess) {
  269. int wait_status;
  270. int pid = wait(&wait_status);
  271. if (pid == -1 || !WIFEXITED(wait_status)) {
  272. status = -1;
  273. } else {
  274. status = WEXITSTATUS(wait_status);
  275. }
  276. }
  277. // deauthorize
  278. [authorization invalidateCredentials];
  279. return (status == 0);
  280. }
  281. @end