/core/externals/update-engine/Common/KSDiskImage.m

http://macfuse.googlecode.com/ · Objective C · 302 lines · 204 code · 58 blank · 40 comment · 52 complexity · 4cf06aea1bb78aa71458dae0f0178c9d MD5 · raw file

  1. // Copyright 2008 Google Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "KSDiskImage.h"
  15. #import "GTMDefines.h"
  16. #import "GTMLogger.h"
  17. @implementation KSDiskImage
  18. + (id)diskImageWithPath:(NSString *)path {
  19. return [[[self alloc] initWithPath:path] autorelease];
  20. }
  21. - (id)init {
  22. return [self initWithPath:nil];
  23. }
  24. - (id)initWithPath:(NSString *)path {
  25. if ((self = [super init])) {
  26. path_ = [path copy];
  27. BOOL isDir = NO;
  28. NSFileManager *fm = [NSFileManager defaultManager];
  29. if ([path_ length] == 0 ||
  30. ![fm fileExistsAtPath:path_ isDirectory:&isDir] || isDir) {
  31. [self release];
  32. return nil;
  33. }
  34. }
  35. return self;
  36. }
  37. - (void)dealloc {
  38. [self unmount];
  39. [path_ release];
  40. [mountPoint_ release];
  41. [super dealloc];
  42. }
  43. - (NSString *)path {
  44. return [[path_ copy] autorelease];
  45. }
  46. - (NSString *)mountPoint {
  47. return [[mountPoint_ copy] autorelease];
  48. }
  49. - (BOOL)isEncrypted {
  50. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  51. NSArray *args = [NSArray arrayWithObjects:
  52. @"imageinfo", @"-plist", @"-stdinpass", path_, nil];
  53. // I know this looks weird, so let me explain... The "isencrypted" verb to
  54. // hdiutil is not available on Tiger, so we need to use some other method for
  55. // finding out if a disk image is encrypted. The "imageinfo" verb provides
  56. // this information on both Tiger and Leopard, however it requires an
  57. // encrypted DMG's password to tell if its encrypted (yes, you read that
  58. // correctly). So, our approach is to try to get the imageinfo from hdiutil
  59. // by providing it a bogus password, and if it fails, then we assume the DMG
  60. // was encrypted. If it actually succeeds, then we'll parse the imageinfo
  61. // plist to to check the Properties/Encrypted bool.
  62. NSString *plist = nil;
  63. [[KSHDIUtilTask hdiutil] runWithArgs:args
  64. inputString:@"t0p_z3cr3t"
  65. outputString:&plist];
  66. if (plist == nil) return YES;
  67. NSDictionary *resultDict = [plist propertyList];
  68. NSNumber *isEncrypted = [resultDict valueForKeyPath:@"Properties.Encrypted"];
  69. return isEncrypted && [isEncrypted boolValue];
  70. }
  71. - (BOOL)hasLicense {
  72. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  73. NSString *plist = nil;
  74. NSArray *args = [NSArray arrayWithObjects:
  75. @"imageinfo", @"-plist", @"-stdinpass", path_, nil];
  76. [[KSHDIUtilTask hdiutil] runWithArgs:args
  77. inputString:@"t0p_z3cr3t"
  78. outputString:&plist];
  79. if (plist == nil) return YES;
  80. NSDictionary *resultDict = [plist propertyList];
  81. NSNumber *hasSLA = [resultDict valueForKeyPath:
  82. @"Properties.Software License Agreement"];
  83. return hasSLA && [hasSLA boolValue];
  84. }
  85. - (void)removeLicense {
  86. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  87. if (![self hasLicense]) return;
  88. NSArray* args = [NSArray arrayWithObjects:
  89. @"unflatten", path_, @"-quiet", nil];
  90. int status = [[KSHDIUtilTask hdiutil] runWithArgs:args
  91. inputString:nil
  92. outputString:nil];
  93. if (status != 0) return;
  94. // now remove the LPic 5000 resource to neuter the SLA
  95. FSRef fsRef;
  96. const UInt8 *fsPath = (const UInt8 *)[path_ fileSystemRepresentation];
  97. OSStatus err = FSPathMakeRef(fsPath, &fsRef, NULL);
  98. if (err == noErr) {
  99. short saveResRefNum = CurResFile();
  100. short resRefNum = FSOpenResFile(&fsRef, fsRdWrPerm);
  101. if (resRefNum != kResFileNotOpened) {
  102. Handle lpicHandle = Get1Resource('LPic', 5000);
  103. if (lpicHandle) {
  104. RemoveResource(lpicHandle);
  105. DisposeHandle(lpicHandle);
  106. }
  107. CloseResFile(resRefNum);
  108. UseResFile(saveResRefNum);
  109. }
  110. }
  111. }
  112. // Common mount method, used by all other mount: methods.
  113. - (NSString *)mountCommon:(NSString *)mountPoint browsable:(BOOL)browsable {
  114. _GTMDevAssert(path_ != nil, @"path_ must not be nil");
  115. if (mountPoint_ != nil) return mountPoint_;
  116. if ([self isEncrypted]) return nil;
  117. // If a SLA is attached to the disk image, then the mount will hang, so we
  118. // always want to remove the SLA in case one exists. There is no way to tell
  119. // if this succeeds (it returns void). We just have to assume it worked.
  120. [self removeLicense];
  121. NSString *plist = nil;
  122. NSMutableArray *args = [NSMutableArray arrayWithObjects:
  123. @"attach", path_, @"-plist",
  124. @"-readonly", @"-noverify", nil];
  125. // If not told to be browsable, specify so on the command line.
  126. if (browsable == NO)
  127. [args addObject:@"-nobrowse"];
  128. // If a mountpoint was specified, then request it. Otherwise, hdiutil will
  129. // give us a default one in /Volumes/
  130. if (mountPoint != nil) {
  131. [args addObject:@"-mountPoint"];
  132. [args addObject:mountPoint];
  133. }
  134. int status = [[KSHDIUtilTask hdiutil] runWithArgs:args
  135. inputString:nil
  136. outputString:&plist];
  137. if (status != 0) {
  138. // COV_NF_START
  139. GTMLoggerError(@"Failed to mount %@, status = %d, output: %@",
  140. path_, status, plist);
  141. return nil;
  142. // COV_NF_END
  143. }
  144. NSDictionary *dict = [plist propertyList];
  145. NSArray *systemEntities = [dict objectForKey:@"system-entities"];
  146. unsigned int numSystemEntities = [systemEntities count];
  147. NSEnumerator *entityEnum = [systemEntities objectEnumerator];
  148. NSDictionary *entityDict = nil;
  149. while ((entityDict = [entityEnum nextObject])) {
  150. NSString *contentHint = [entityDict objectForKey:@"content-hint"];
  151. if ([contentHint isEqualToString:@"Apple_HFS"] || numSystemEntities == 1) {
  152. mountPoint_ = [[[entityDict objectForKey:@"mount-point"]
  153. stringByStandardizingPath] retain];
  154. break;
  155. }
  156. }
  157. return mountPoint_;
  158. }
  159. - (NSString *)mount:(NSString *)mountPoint {
  160. return [self mountCommon:mountPoint browsable:NO];
  161. }
  162. - (NSString *)mountBrowsable:(NSString *)mountPoint {
  163. return [self mountCommon:mountPoint browsable:YES];
  164. }
  165. - (BOOL)isMounted {
  166. return mountPoint_ != nil;
  167. }
  168. - (BOOL)unmount {
  169. if (mountPoint_ == nil) return NO;
  170. NSArray* args = [NSArray arrayWithObjects:
  171. @"detach", mountPoint_, @"-quiet", nil];
  172. int status = [[KSHDIUtilTask hdiutil] runWithArgs:args
  173. inputString:nil
  174. outputString:nil];
  175. if (status == 0) {
  176. [mountPoint_ release];
  177. mountPoint_ = nil;
  178. return YES;
  179. }
  180. return NO; // COV_NF_LINE
  181. }
  182. @end
  183. @implementation KSHDIUtilTask
  184. + (id)hdiutil {
  185. return [[[self alloc] init] autorelease];
  186. }
  187. - (int)runWithArgs:(NSArray *)args
  188. inputString:(NSString *)input
  189. outputString:(NSString **)output {
  190. NSTask *task = [[[NSTask alloc] init] autorelease];
  191. [task setLaunchPath:@"/usr/bin/hdiutil"];
  192. if (args)
  193. [task setArguments:args];
  194. if (output != nil) {
  195. NSPipe *outPipe = [NSPipe pipe];
  196. [task setStandardOutput:outPipe];
  197. }
  198. if (input != nil) {
  199. NSPipe *inPipe = [NSPipe pipe];
  200. [task setStandardInput:inPipe];
  201. }
  202. @try {
  203. // NSTask has been known to throw
  204. [task launch];
  205. // COV_NF_START
  206. } @catch (id ex) {
  207. GTMLoggerError(@"KSHDIUtilTask: Caught %@ when launching %@ with args %@",
  208. ex, task, [args componentsJoinedByString:@" "]);
  209. return -1;
  210. }
  211. // COV_NF_END
  212. // Now that the task is running, send over the stdin if we have any
  213. if (input != nil) {
  214. NSFileHandle *fh = [[task standardInput] fileHandleForWriting];
  215. [fh writeData:[input dataUsingEncoding:NSUTF8StringEncoding]];
  216. [fh closeFile];
  217. }
  218. // If the caller wanted the stdout, get it.
  219. if (output != nil) {
  220. NSFileHandle *fh = [[task standardOutput] fileHandleForReading];
  221. @try {
  222. NSData *data = [fh readDataToEndOfFile]; // blocks until EOF is delivered
  223. if ([data length] > 0) {
  224. *output = [[[NSString alloc] initWithData:data
  225. encoding:NSUTF8StringEncoding]
  226. autorelease];
  227. }
  228. // COV_NF_START
  229. } @catch (id ex) {
  230. // running in gdb we get exception: Interrupted system call
  231. GTMLoggerDebug(@"KSHDIUtilTask diskImageInfo: gdb issue -- "
  232. @"getting file data causes exception: %@", ex);
  233. }
  234. // COV_NF_END
  235. }
  236. [task waitUntilExit];
  237. int status = [task terminationStatus];
  238. if (status != 0) {
  239. GTMLoggerError(@"KSHDIUtilTask: hdiutil %@, returned %d",
  240. [args componentsJoinedByString:@" "], status);
  241. }
  242. return status;
  243. }
  244. @end // KSHDIUtilTask