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