PageRenderTime 65ms CodeModel.GetById 11ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 1ms

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