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