1 | //␊ |
2 | // PartitionInfoElement.mm␊ |
3 | // ChameleonPrefPane␊ |
4 | //␊ |
5 | // Powerful Partition Info class utility␊ |
6 | //␊ |
7 | // Created by Rekursor on 11-11-12.␊ |
8 | //␊ |
9 | ␊ |
10 | #import "PartitionInfoElement.h"␊ |
11 | #import "ShellProcess.h"␊ |
12 | #include "string_util.h"␊ |
13 | ␊ |
14 | static NSUInteger sHdRedirTable[MAX_HD];␊ |
15 | ␊ |
16 | @implementation PartitionInfoElement␊ |
17 | ␊ |
18 | @synthesize descDict, bsdName, vUUID, vKind, vName, vPath, mediaPath, mediaRemovable, devInternal, devProtocol;␊ |
19 | @synthesize vAliasName, hidden, bootInfo;␊ |
20 | ␊ |
21 | /// Create a list of all bsd partitions␊ |
22 | +(NSArray*) createBSDPartitionList␊ |
23 | {␊ |
24 | ␉NSMutableArray* arr = [[[NSMutableArray alloc] init] autorelease];␊ |
25 | ␉␊ |
26 | ␉char line[256];␊ |
27 | ␉ShellProcess p;␊ |
28 | ␉␊ |
29 | ␉p.open("ls -1 /dev/disk*");␊ |
30 | ␉while( p.get_line(line, sizeof(line)-1))␊ |
31 | ␉{␊ |
32 | ␉␉size_t l = strlen(line);␊ |
33 | ␉␉if (l==0 || strstr(line, "ls:")!=NULL) continue;␊ |
34 | ␉␉if (line[l-1]) line[l-1]='\0';␊ |
35 | ␉␉const char * p = strstr(line, "disk");␊ |
36 | ␉␉NSString* s = [NSString stringWithUTF8String: p] ;␊ |
37 | ␉␉[arr addObject: s];␉␊ |
38 | ␉␉␊ |
39 | ␉}␊ |
40 | ␉p.close();␊ |
41 | ␉␊ |
42 | ␉return arr;␊ |
43 | }␊ |
44 | /// extract version info from boot files when possible␊ |
45 | - (NSString*) cmdInfoForFile:(NSString*) file ␊ |
46 | withFilter:(NSString*) filter␊ |
47 | withPath:(NSString*) path␊ |
48 | {␊ |
49 | std::string p = [path UTF8String];␊ |
50 | p += [file UTF8String];␊ |
51 | ␊ |
52 | if (!fileExists(p)) return @"";␊ |
53 | ␊ |
54 | NSString *cmd = [NSString stringWithFormat:@"strings \"%@%@\" | sed -nE '%@'",␊ |
55 | path, file, filter];␊ |
56 | // NSLog(@"cmd to run is %@", cmd);␊ |
57 | return [NSString stringWithUTF8String: ShellProcess([cmd UTF8String]).get_line().c_str()];␊ |
58 | }␊ |
59 | ␊ |
60 | /// Get the boot information string, for chameleon only for now ...␊ |
61 | -(NSString*) bootDescription␊ |
62 | {␊ |
63 | if (bootInfo != nil) return bootInfo;␊ |
64 | ␊ |
65 | NSString* path = [[self.vPath ␊ |
66 | stringByReplacingOccurrencesOfString: @"file://localhost"␊ |
67 | withString:@""] ␊ |
68 | stringByReplacingOccurrencesOfString:@"%20" withString:@" "];␊ |
69 | NSString *cmdCham = [self cmdInfoForFile: @"boot" ␊ |
70 | withFilter: @"s!^Darwin/x86 boot v.+\\ -\\ (.+)!\\1!p"␊ |
71 | withPath: path];␊ |
72 | NSString *cmdDarwin = [self cmdInfoForFile: @"mach_kernel" ␊ |
73 | withFilter: @"s!^(Darwin Kernel Version.+)(\\: .*$)!\\1!p"␊ |
74 | withPath: path];␊ |
75 | NSString *cmdWindows = (cmdDarwin==nil) ? @"" : ␊ |
76 | [self cmdInfoForFile: @"boot.ini" ␊ |
77 | withFilter: @"s!^multi.+WIN.+=\\\"(.+)(\\\".+$)!\\1!p"␊ |
78 | withPath: path];␊ |
79 | ␊ |
80 | NSString * result = [[cmdCham stringByAppendingString: cmdDarwin] ␊ |
81 | stringByAppendingString:cmdWindows];␊ |
82 | result = [result stringByTrimmingCharactersInSet: ␊ |
83 | [NSCharacterSet whitespaceAndNewlineCharacterSet] ];␊ |
84 | ␊ |
85 | return (bootInfo = [result length] > 0 ? result : nil);␊ |
86 | }␊ |
87 | ␊ |
88 | ␊ |
89 | /// redirection table for disk swapping␊ |
90 | +(NSUInteger*) hdRedirTable␊ |
91 | {␊ |
92 | ␉return sHdRedirTable;␊ |
93 | }␊ |
94 | ␊ |
95 | /// redirection table for disk swapping␊ |
96 | /// extract disk number from bsdname spec␊ |
97 | -(int) diskNumber␊ |
98 | {␊ |
99 | ␉int newDiskNum = (diskNum>=0 && diskNum <MAX_HD) ? sHdRedirTable[diskNum] : diskNum;␊ |
100 | ␉return newDiskNum;␊ |
101 | }␊ |
102 | ␊ |
103 | /// extract partition number from bsdname spec␊ |
104 | -(int) partitionNumber␊ |
105 | {␊ |
106 | ␉return partNum; // todo extract d num from bsdname␊ |
107 | }␊ |
108 | ␊ |
109 | -(NSString*) hdString␊ |
110 | {␊ |
111 | ␉return [NSString stringWithFormat:@"hd(%d,%d)", [self diskNumber], [self partitionNumber]];␊ |
112 | }␊ |
113 | ␊ |
114 | -(void) setDiskNumber: (int) d␊ |
115 | {␊ |
116 | ␉diskNum = d;␊ |
117 | }␊ |
118 | ␊ |
119 | -(void) setPartitionNumber:(int) p␊ |
120 | {␊ |
121 | ␉partNum = p;␊ |
122 | }␊ |
123 | /// return true if partition extraction successfully executed, false otherwise␊ |
124 | -(bool) isValid␊ |
125 | {␊ |
126 | ␉␊ |
127 | ␉return (␊ |
128 | ␉␉␉err==0␊ |
129 | ␉␉␉&& [self isBootable] ␊ |
130 | ␉␉␉// with chameleon as much as possible␊ |
131 | ␉␉␉&& ![[self devProtocol] isEqual:@"Virtual Interface"]␊ |
132 | ␉␉␉);␊ |
133 | }␊ |
134 | ␊ |
135 | /// Extract a particular information from the disk partition dictionary from a key, generate a string value output␊ |
136 | -(NSString*) stringValueWithKey: (NSString*) key␊ |
137 | {␊ |
138 | ␉if (err) return @"";␊ |
139 | ␉CFTypeRef o = [descDict objectForKey: key];␊ |
140 | ␉if (o==nil) return @"";␊ |
141 | ␉CFStringRef sref = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), o);␊ |
142 | ␉NSString* s = [[((NSString*) sref) copy] autorelease];␊ |
143 | ␉CFRelease(sref);␊ |
144 | ␉return s;␊ |
145 | }␊ |
146 | ␊ |
147 | /// main extraction method create a dictionary containing all needed information from disk arbitration API␊ |
148 | - (int) extractInfoWithBSDName: (NSString*) name withinSession:(DASessionRef) session␊ |
149 | {␊ |
150 | ␉err = 0;␊ |
151 | ␉␊ |
152 | ␉NSRange rDisk = [name rangeOfString:@"disk"];␊ |
153 | ␉NSRange rS = [name rangeOfString:@"s" options:NSBackwardsSearch] ;␊ |
154 | ␉␊ |
155 | ␉if (name == nil ␊ |
156 | ␉␉|| [name length] < 7 ␊ |
157 | ␉␉|| rDisk.location == NSNotFound ␊ |
158 | ␉␉|| rS.location <5) ␊ |
159 | ␉{␊ |
160 | ␉␉err = EINVAL;␊ |
161 | ␉␉return err;␊ |
162 | ␉}␊ |
163 | ␉␉␉␉␉␉␉␉␉␉␉ ␊ |
164 | ␉// extract disk and partition number␊ |
165 | ␉int pos = rDisk.location+rDisk.length;␊ |
166 | ␉int pos2 = rS.location+rS.length;␊ |
167 | ␉NSString* sDisk = [name substringWithRange:␊ |
168 | ␉␉␉␉␉ NSMakeRange(pos, rS.location - pos) ];␊ |
169 | ␉NSString* sPart = [name substringFromIndex: pos2 ];␊ |
170 | ␉␊ |
171 | ␉bool mustRelease = (session == nil) ? true : false;␊ |
172 | ␉␊ |
173 | ␉if (session == nil)␊ |
174 | ␉{␊ |
175 | ␉␉session = DASessionCreate(NULL);␊ |
176 | ␉}␊ |
177 | ␉␊ |
178 | ␉if (session == NULL)␉err = EINVAL;␊ |
179 | ␉␊ |
180 | ␉if (err == 0) {␊ |
181 | ␉␉DADiskRef disk = DADiskCreateFromBSDName(NULL, session, [name UTF8String] );␊ |
182 | ␉␉if (disk == NULL) err = EINVAL;␊ |
183 | else␊ |
184 | {␊ |
185 | [self cleanup];␊ |
186 | CFDictionaryRef␉dr = DADiskCopyDescription(disk);␊ |
187 | if (dr!=NULL)␊ |
188 | {␊ |
189 | descDict = (NSDictionary*) dr;␊ |
190 | CFRelease(dr);␊ |
191 | }␊ |
192 | }␊ |
193 | if (disk) CFRelease(disk);␊ |
194 | ␉}␊ |
195 | ␊ |
196 | ␉if (err == 0) {␊ |
197 | ␉␉diskNum = [sDisk␉intValue];␊ |
198 | ␉␉partNum = [sPart␉intValue];␊ |
199 | ␉␉␊ |
200 | ␉␉self.vUUID = [self stringValueWithKey: @"DAVolumeUUID"];␊ |
201 | ␉␉self.vName = [self stringValueWithKey: @"DAVolumeName"];␊ |
202 | ␉␉self.vPath = [self stringValueWithKey: @"DAVolumePath"];␊ |
203 | ␉␉if (self.vName == nil || [vName length]==0)␊ |
204 | ␉␉␉self.vName = [[self.vPath lastPathComponent ] stringByReplacingOccurrencesOfString:@"%20" withString:@" "];␊ |
205 | ␉␉self.vAliasName = self.vName; // by default renamed = original part name ␊ |
206 | ␉␉self.vKind = [self stringValueWithKey: @"DAVolumeKind"];␊ |
207 | ␉␉self.mediaPath = [self stringValueWithKey: @"DAMediaPath"];␊ |
208 | ␉␉self.devProtocol = [self stringValueWithKey: @"DADeviceProtocol"];␊ |
209 | ␉␉self.bsdName = [NSString stringWithString:name];␊ |
210 | ␉␉␊ |
211 | ␉␉self.devInternal = (bool) [[descDict objectForKey: @"DADeviceInternal"] boolValue];␊ |
212 | ␉␉self.mediaRemovable = (bool) [[descDict objectForKey: @"DAMediaRemovable"] boolValue];␊ |
213 | ␉␉␊ |
214 | ␉}␊ |
215 | ␉␊ |
216 | ␉// Clean up.␊ |
217 | ␉␊ |
218 | ␉if (session != NULL && mustRelease) CFRelease(session);␊ |
219 | ␉␊ |
220 | ␉return err;␊ |
221 | }␊ |
222 | ␊ |
223 | +(NSMutableArray*)␉extractInfoWithBSDNames: (NSArray*) bsdNames␊ |
224 | {␊ |
225 | ␉return [PartitionInfoElement extractInfoWithBSDNames:bsdNames withArray: nil];␊ |
226 | }␊ |
227 | ␊ |
228 | /// main extraction method create a dictionary of PartitionInfoElement objects containing all needed information from disk arbitration API␊ |
229 | + (NSMutableArray*) extractInfoWithBSDNames: (NSArray*) names withArray:(NSMutableArray*) arr␊ |
230 | {␉␉␊ |
231 | ␉NSArray* partArr = [PartitionInfoElement createBSDPartitionList];␊ |
232 | ␉if (arr == nil)␊ |
233 | ␉␉arr = [[[NSMutableArray alloc ] init] autorelease];␊ |
234 | ␉␊ |
235 | ␉if (partArr!=nil && [partArr count]>0)␊ |
236 | ␉{␊ |
237 | ␉␉␉DASessionRef session = DASessionCreate(NULL);␊ |
238 | ␉␉␉for (NSString* part in partArr)␊ |
239 | ␉␉␉{␊ |
240 | ␉␉␉␉PartitionInfoElement* elt = ␊ |
241 | ␉␉␉␉[[[PartitionInfoElement alloc] initWithBSDName: part withinSession: session] autorelease];␊ |
242 | ␉␉␉␉if (elt!=nil && [[elt vName] length] >0 && [elt isValid] ) ␊ |
243 | ␉␉␉␉␉[arr addObject: elt];␊ |
244 | ␉␉␉}␊ |
245 | ␉␉␉if (session!=nil) CFRelease(session);␊ |
246 | ␉}␊ |
247 | ␉return arr;␊ |
248 | }␊ |
249 | ␊ |
250 | - (int) extractInfoWithBSDName: (NSString*) name ␊ |
251 | {␊ |
252 | ␉return [self extractInfoWithBSDName:name withinSession: nil]; ␊ |
253 | }␊ |
254 | ␊ |
255 | -(bool) isBootable␊ |
256 | {␊ |
257 | ␉bool bootable = false;␊ |
258 | ␉NSFileManager* mgr = [NSFileManager defaultManager];␊ |
259 | ␉NSString *fmt = @"/Volumes/%@%s";␊ |
260 | ␉NSRange r = [vPath rangeOfString:@"file://localhost/Volumes/"];␊ |
261 | ␉NSString * vol = [NSString stringWithFormat: @"%@/",vName];␊ |
262 | ␉␊ |
263 | ␉// ␊ |
264 | ␉if (r.location != NSNotFound) {␊ |
265 | ␉␉vol = [[vPath substringFromIndex:r.location+r.length] stringByReplacingOccurrencesOfString:@"%20" withString:@" "];␊ |
266 | ␉}␊ |
267 | ␉␊ |
268 | ␉// that Windows is the name of WIN32 bootable disk dir ...␊ |
269 | ␉if(␊ |
270 | ␉␉[mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "System/Library/Extensions" ]] ||␊ |
271 | ␉ [mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "ntldr" ]] ||␊ |
272 | ␉␉[mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "bootmgr" ]] ||␊ |
273 | ␉␉[mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "boot/bcd" ]] ||␊ |
274 | ␉␉[mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "pagefile.sys" ]] ||␊ |
275 | ␉␉[mgr fileExistsAtPath: [NSString stringWithFormat: fmt, vol, "hiberfil.sys" ]] ␊ |
276 | ␉ )␊ |
277 | ␉␉bootable=true;␊ |
278 | ␉else if ([vName rangeOfString:@"ext"].location != NSNotFound) // linux ?␊ |
279 | ␉␉bootable = true;␊ |
280 | ␉return bootable;␊ |
281 | }␊ |
282 | ␊ |
283 | /// Return the partition image index according to its file system␊ |
284 | -(int) imageIndexFromFs␊ |
285 | {␊ |
286 | ␉␊ |
287 | ␉if ( [[self vKind] rangeOfString:@"hfs"].location != NSNotFound )␊ |
288 | ␉␉return 0; // Mac␊ |
289 | ␉if ( [[self vKind] rangeOfString:@"fat"].location != NSNotFound ||␊ |
290 | ␉␉ [[self vKind] rangeOfString:@"ntfs"].location != NSNotFound ||␊ |
291 | ␉␉ [[self vKind] rangeOfString:@"dos"].location != NSNotFound )␊ |
292 | ␉␉return 1; // Windows␊ |
293 | ␉if ( [[self vKind] rangeOfString:@"ext"].location != NSNotFound )␊ |
294 | ␉␉return 2; // Linux␊ |
295 | ␉␊ |
296 | ␉return 10; // unknown␊ |
297 | }␊ |
298 | ␊ |
299 | /// Volume label trimming utility function␊ |
300 | +(NSString*) removesSpacesFromLabel:(NSString*) label␊ |
301 | {␊ |
302 | ␉if (label == nil || [label length]==0) return label;␊ |
303 | ␉return [label stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];␊ |
304 | }␊ |
305 | ␊ |
306 | /// make the object description available for NSLog() debug purpose␊ |
307 | -(NSString*) description␊ |
308 | {␊ |
309 | ␉NSString* format =␊ |
310 | ␉@"(\n"␊ |
311 | ␉ " bsdName %@\n deviceProtocol %@\n deviceInternal %i\n"␊ |
312 | ␉ " volumeName %@\n volumeKind %@\n volumeUUID %@\n"␊ |
313 | ␉ " volumePath %@\n mediaPath %@\n mediaRemovable %i\n"␉␊ |
314 | ␉ ")";␊ |
315 | ␉NSString* value = [NSString stringWithFormat: format,␊ |
316 | ␉␉␉␉␉ self.bsdName, self.devProtocol, self.devInternal, ␊ |
317 | ␉␉␉␉␉ self.vName, self.vKind, self.vUUID, ␊ |
318 | ␉␉␉␉␉ self.vPath, self.mediaPath, self.mediaRemovable ];␊ |
319 | ␉return value;␊ |
320 | }␊ |
321 | ␊ |
322 | ␊ |
323 | /// initialize a partition element with DA partition info␊ |
324 | -(id) initWithBSDName:(NSString*) name ␊ |
325 | {␊ |
326 | ␉self = [super init];␊ |
327 | ␉diskNum = partNum = -1;␊ |
328 | ␉err = [self extractInfoWithBSDName: name withinSession: nil];␊ |
329 | ␉return self;␊ |
330 | }␊ |
331 | ␊ |
332 | /// initialize a partition element with DA partition info and an optional opened session␊ |
333 | -(id) initWithBSDName:(NSString*) name withinSession:(DASessionRef) session␊ |
334 | {␊ |
335 | ␉self = [super init];␊ |
336 | ␉diskNum = partNum = -1;␊ |
337 | ␉err = [self extractInfoWithBSDName: name withinSession: session];␊ |
338 | ␉␊ |
339 | ␉return self;␊ |
340 | }␊ |
341 | ␊ |
342 | -(void) cleanup␊ |
343 | {␊ |
344 | ␊ |
345 | ␉if (descDict != nil) CFRelease(descDict);␊ |
346 | ␉if (bsdName != nil) [bsdName release];␊ |
347 | ␉if (vUUID != nil) [vUUID release];␊ |
348 | ␉if (vKind != nil) [vKind release];␊ |
349 | ␉if (vName != nil) [vName release];␊ |
350 | ␉if (mediaPath != nil) [mediaPath release];␊ |
351 | ␉if (devProtocol != nil) [devProtocol release];␊ |
352 | ␉if (vAliasName != nil) [vAliasName release];␊ |
353 | ␉if (bootInfo != nil) [bootInfo release];␊ |
354 | ␉bsdName = vUUID = vKind = vName = mediaPath = devProtocol = vAliasName = bootInfo = nil;␊ |
355 | ␉descDict = nil;␊ |
356 | }␊ |
357 | ␊ |
358 | /// release created objects␊ |
359 | -(void) dealloc␊ |
360 | {␊ |
361 | ␉[self cleanup];␊ |
362 | ␉[super dealloc];␊ |
363 | }␊ |
364 | ␊ |
365 | @end␊ |
366 | |