|
1 // |
|
2 // NSWorkspace_RBAdditions.m |
|
3 // PathProps |
|
4 // |
|
5 // Created by Rainer Brockerhoff on 10/04/2007. |
|
6 // Copyright 2007 Rainer Brockerhoff. All rights reserved. |
|
7 // |
|
8 |
|
9 #import "NSWorkspace_RBAdditions.h" |
|
10 #include <IOKit/IOKitLib.h> |
|
11 #include <sys/mount.h> |
|
12 |
|
13 NSString* NSWorkspace_RBfstypename = @"NSWorkspace_RBfstypename"; |
|
14 NSString* NSWorkspace_RBmntonname = @"NSWorkspace_RBmntonname"; |
|
15 NSString* NSWorkspace_RBmntfromname = @"NSWorkspace_RBmntfromname"; |
|
16 NSString* NSWorkspace_RBdeviceinfo = @"NSWorkspace_RBdeviceinfo"; |
|
17 NSString* NSWorkspace_RBimagefilepath = @"NSWorkspace_RBimagefilepath"; |
|
18 NSString* NSWorkspace_RBconnectiontype = @"NSWorkspace_RBconnectiontype"; |
|
19 NSString* NSWorkspace_RBpartitionscheme = @"NSWorkspace_RBpartitionscheme"; |
|
20 NSString* NSWorkspace_RBserverURL = @"NSWorkspace_RBserverURL"; |
|
21 |
|
22 // This static funtion concatenates two strings, but first checks several possibilities... |
|
23 // like one or the other nil, or one containing the other already. |
|
24 |
|
25 static NSString* AddPart(NSString* first,NSString* second) { |
|
26 if (!second) { |
|
27 return first; |
|
28 } |
|
29 second = [second stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
|
30 if (first) { |
|
31 if ([first rangeOfString:second options:NSCaseInsensitiveSearch].location==NSNotFound) { |
|
32 if ([second rangeOfString:first options:NSCaseInsensitiveSearch].location==NSNotFound) { |
|
33 return [NSString stringWithFormat:@"%@; %@",first,second]; |
|
34 } |
|
35 return second; |
|
36 } |
|
37 return first; |
|
38 } |
|
39 return second; |
|
40 } |
|
41 |
|
42 // This static functions recurses "upwards" over the IO registry. Returns strings that are concatenated |
|
43 // and ultimately end up under the NSWorkspace_RBdeviceinfo key. |
|
44 // This isn't too robust in that it assumes that objects returned by the objectForKey methods are |
|
45 // either strings or dictionaries. A "standard" implementations would use either only CoreFoundation and |
|
46 // IOKit calls for this, or do more robust type checking on the returned objects. |
|
47 // |
|
48 // Also notice that this works as determined experimentally in 10.4.9, there's no official docs I could find. |
|
49 // YMMV, and it may stop working in any new version of Mac OS X. |
|
50 |
|
51 static NSString* CheckParents(io_object_t thing,NSString* part,NSMutableDictionary* dict) { |
|
52 NSString* result = part; |
|
53 io_iterator_t parentsIterator = 0; |
|
54 kern_return_t kernResult = IORegistryEntryGetParentIterator(thing,kIOServicePlane,&parentsIterator); |
|
55 if ((kernResult==KERN_SUCCESS)&&parentsIterator) { |
|
56 io_object_t nextParent = 0; |
|
57 while ((nextParent = IOIteratorNext(parentsIterator))) { |
|
58 NSDictionary* props = nil; |
|
59 NSString* image = nil; |
|
60 NSString* partition = nil; |
|
61 NSString* connection = nil; |
|
62 kernResult = IORegistryEntryCreateCFProperties(nextParent,(CFMutableDictionaryRef*)&props,kCFAllocatorDefault,0); |
|
63 if (IOObjectConformsTo(nextParent,"IOApplePartitionScheme")) { |
|
64 partition = [props objectForKey:@"Content Mask"]; |
|
65 } else if (IOObjectConformsTo(nextParent,"IOMedia")) { |
|
66 partition = [props objectForKey:@"Content"]; |
|
67 } else if (IOObjectConformsTo(nextParent,"IODiskImageBlockStorageDeviceOutKernel")) { |
|
68 NSData* data = nil; |
|
69 if (data = [[props objectForKey:@"Protocol Characteristics"] objectForKey:@"Virtual Interface Location Path"]) { |
|
70 image = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease]; |
|
71 } |
|
72 } else if (IOObjectConformsTo(nextParent,"IOHDIXHDDriveInKernel")) { |
|
73 image = [props objectForKey:@"KDIURLPath"]; |
|
74 } |
|
75 NSDictionary* subdict; |
|
76 if (subdict = [props objectForKey:@"Protocol Characteristics"]) { |
|
77 connection = [subdict objectForKey:@"Physical Interconnect"]; |
|
78 } else { |
|
79 connection = [props objectForKey:@"Physical Interconnect"]; |
|
80 } |
|
81 if (connection) { |
|
82 [dict setObject:AddPart([dict objectForKey:NSWorkspace_RBconnectiontype],connection) forKey:NSWorkspace_RBconnectiontype]; |
|
83 } |
|
84 if (partition) { |
|
85 [dict setObject:partition forKey:NSWorkspace_RBpartitionscheme]; |
|
86 } |
|
87 if (image) { |
|
88 [dict setObject:image forKey:NSWorkspace_RBimagefilepath]; |
|
89 } |
|
90 NSString* value; |
|
91 if (subdict = [props objectForKey:@"Device Characteristics"]) { |
|
92 if (value = [subdict objectForKey:@"Product Name"]) { |
|
93 result = AddPart(result,value); |
|
94 } |
|
95 if (value = [subdict objectForKey:@"Product Revision Level"]) { |
|
96 result = AddPart(result,value); |
|
97 } |
|
98 if (value = [subdict objectForKey:@"Vendor Name"]) { |
|
99 result = AddPart(result,value); |
|
100 } |
|
101 } |
|
102 if (value = [props objectForKey:@"USB Serial Number"]) { |
|
103 result = AddPart(result,value); |
|
104 } |
|
105 if (value = [props objectForKey:@"USB Vendor Name"]) { |
|
106 result = AddPart(result,value); |
|
107 } |
|
108 NSString* cls = [(NSString*)IOObjectCopyClass(nextParent) autorelease]; |
|
109 if (![cls isEqualToString:@"IOPCIDevice"]) { |
|
110 |
|
111 // Uncomment the following line to have the device tree dumped to the console. |
|
112 // NSLog(@"=================================> %@:%@\n",cls,props); |
|
113 |
|
114 result = CheckParents(nextParent,result,dict); |
|
115 } |
|
116 IOObjectRelease(nextParent); |
|
117 } |
|
118 } |
|
119 if (parentsIterator) { |
|
120 IOObjectRelease(parentsIterator); |
|
121 } |
|
122 return result; |
|
123 } |
|
124 |
|
125 // This formats the (partially undocumented) AFPXMountInfo info into a string. |
|
126 |
|
127 static NSString* FormatAFPURL(AFPXVolMountInfoPtr mountInfo,NSString** devdesc) { |
|
128 UInt8* work = ((UInt8*)mountInfo)+mountInfo->serverNameOffset; |
|
129 if (devdesc) { |
|
130 *devdesc = [[[NSString alloc] initWithBytes:&work[1] length:work[0] encoding:NSUTF8StringEncoding] autorelease]; |
|
131 } |
|
132 work = ((UInt8*)mountInfo)+mountInfo->volNameOffset; |
|
133 NSString* volname = [[[NSString alloc] initWithBytes:&work[1] length:work[0] encoding:NSUTF8StringEncoding] autorelease]; |
|
134 work = ((UInt8*)mountInfo)+mountInfo->alternateAddressOffset; |
|
135 AFPAlternateAddress* afpa = (AFPAlternateAddress*)work; |
|
136 AFPTagData* afpta = (AFPTagData*)(&afpa->fAddressList); |
|
137 NSString* ip = nil; |
|
138 NSString* dns = nil; |
|
139 int i = afpa->fAddressCount; |
|
140 while ((i-->0)) { |
|
141 switch (afpta->fType) { |
|
142 case kAFPTagTypeIP: |
|
143 if (!ip) { |
|
144 ip = [[[NSString alloc] initWithBytes:&afpta->fData[0] length:afpta->fLength-2 encoding:NSUTF8StringEncoding] autorelease]; |
|
145 } |
|
146 break; |
|
147 case kAFPTagTypeIPPort: |
|
148 ip = [NSString stringWithFormat:@"%u.%u.%u.%u:%u",afpta->fData[0],afpta->fData[1],afpta->fData[2],afpta->fData[3],OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[4])]; |
|
149 break; |
|
150 case kAFPTagTypeDNS: |
|
151 dns = [[[NSString alloc] initWithBytes:&afpta->fData[0] length:afpta->fLength-2 encoding:NSUTF8StringEncoding] autorelease]; |
|
152 break; |
|
153 case 0x07: |
|
154 ip = [NSString stringWithFormat:@"[%x:%x:%x:%x:%x:%x:%x:%x]",OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[0]), |
|
155 OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[2]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[4]), |
|
156 OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[6]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[8]), |
|
157 OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[10]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[12]), |
|
158 OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[14])]; |
|
159 break; |
|
160 } |
|
161 afpta = (AFPTagData*)((char*)afpta+afpta->fLength); |
|
162 } |
|
163 return [NSString stringWithFormat:@"afp://%@/%@",dns?:(ip?:@""),volname]; |
|
164 } |
|
165 |
|
166 @implementation NSWorkspace (NSWorkspace_RBAdditions) |
|
167 |
|
168 // Returns a NSDictionary with properties for the path. See details in the .h file. |
|
169 // This assumes that the length of path is less than PATH_MAX (currently 1024 characters). |
|
170 |
|
171 - (NSDictionary*)propertiesForPath:(NSString*)path { |
|
172 const char* ccpath = (const char*)[path fileSystemRepresentation]; |
|
173 NSMutableDictionary* result = nil; |
|
174 struct statfs fs; |
|
175 if (!statfs(ccpath,&fs)) { |
|
176 NSString* from = [NSString stringWithUTF8String:fs.f_mntfromname]; |
|
177 result = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
|
178 [NSString stringWithUTF8String:fs.f_fstypename],NSWorkspace_RBfstypename, |
|
179 [NSString stringWithUTF8String:fs.f_mntonname],NSWorkspace_RBmntonname, |
|
180 nil]; |
|
181 if (strncmp(fs.f_mntfromname,"/dev/",5)==0) { |
|
182 // For a local volume,get the IO registry tree and search it for further info. |
|
183 mach_port_t masterPort = 0; |
|
184 io_iterator_t mediaIterator = 0; |
|
185 kern_return_t kernResult = IOMasterPort(bootstrap_port,&masterPort); |
|
186 if (kernResult==KERN_SUCCESS) { |
|
187 CFMutableDictionaryRef classesToMatch = IOBSDNameMatching(masterPort,0,&fs.f_mntfromname[5]); |
|
188 if (classesToMatch) { |
|
189 kernResult = IOServiceGetMatchingServices(masterPort,classesToMatch,&mediaIterator); |
|
190 if ((kernResult==KERN_SUCCESS)&&mediaIterator) { |
|
191 io_object_t firstMedia = 0; |
|
192 while ((firstMedia = IOIteratorNext(mediaIterator))) { |
|
193 NSString* stuff = CheckParents(firstMedia,nil,result); |
|
194 if (stuff) { |
|
195 [result setObject:stuff forKey:NSWorkspace_RBdeviceinfo]; |
|
196 } |
|
197 IOObjectRelease(firstMedia); |
|
198 } |
|
199 } |
|
200 } |
|
201 } |
|
202 if (mediaIterator) { |
|
203 IOObjectRelease(mediaIterator); |
|
204 } |
|
205 if (masterPort) { |
|
206 mach_port_deallocate(mach_task_self(),masterPort); |
|
207 } |
|
208 } |
|
209 //Don't need this for disk images, gets around warnings for some deprecated functions |
|
210 |
|
211 /* else { |
|
212 // For a network volume, get the volume reference number and use to get the server URL. |
|
213 FSRef ref; |
|
214 if (FSPathMakeRef((const UInt8*)ccpath,&ref,NULL)==noErr) { |
|
215 FSCatalogInfo info; |
|
216 if (FSGetCatalogInfo(&ref,kFSCatInfoVolume,&info,NULL,NULL,NULL)==noErr) { |
|
217 ParamBlockRec pb; |
|
218 UInt16 vmisize = 0; |
|
219 VolumeMountInfoHeaderPtr mountInfo = NULL; |
|
220 pb.ioParam.ioCompletion = NULL; |
|
221 pb.ioParam.ioNamePtr = NULL; |
|
222 pb.ioParam.ioVRefNum = info.volume; |
|
223 pb.ioParam.ioBuffer = (Ptr)&vmisize; |
|
224 pb.ioParam.ioReqCount = sizeof(vmisize); |
|
225 if ((PBGetVolMountInfoSize(&pb)==noErr)&&vmisize) { |
|
226 mountInfo = (VolumeMountInfoHeaderPtr)malloc(vmisize); |
|
227 if (mountInfo) { |
|
228 pb.ioParam.ioBuffer = (Ptr)mountInfo; |
|
229 pb.ioParam.ioReqCount = vmisize; |
|
230 if (PBGetVolMountInfo(&pb)==noErr) { |
|
231 NSString* url = nil; |
|
232 switch (mountInfo->media) { |
|
233 case AppleShareMediaType: |
|
234 url = FormatAFPURL((AFPXVolMountInfoPtr)mountInfo,&from); |
|
235 break; |
|
236 case 'http': |
|
237 url = from; |
|
238 break; |
|
239 case 'crbm': |
|
240 case 'nfs_': |
|
241 case 'cifs': |
|
242 url = [NSString stringWithUTF8String:(char*)mountInfo+sizeof(VolumeMountInfoHeader)+sizeof(OSType)]; |
|
243 break; |
|
244 } |
|
245 if (url) { |
|
246 [result setObject:url forKey:NSWorkspace_RBserverURL]; |
|
247 } |
|
248 } |
|
249 } |
|
250 free(mountInfo); |
|
251 } |
|
252 } |
|
253 } |
|
254 }*/ |
|
255 [result setObject:from forKey:NSWorkspace_RBmntfromname]; |
|
256 } |
|
257 return result; |
|
258 } |
|
259 |
|
260 @end |