Я пытаюсь обнаружить запоминающие устройства USB под MAC OSX. Я надеялся получить класс устройства и на основании этого решить, является ли устройство запоминающим устройством или нет. Но для всех имеющихся у меня флешек я получаю класс устройства == 0, который выглядит как составное устройство. Пожалуйста, помогите мне выяснить, что я делаю не так, или, может быть, какой-то другой надежный способ обнаружить устройства USB Mass Storage (мне нужно получить PID, VID и точку монтирования). Вот мой код:
#import <iostream>
#import <IOKit/IOkitLib.h>
#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/usb/USBSpec.h>
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
CFMutableDictionaryRef matchingDictionary = NULL;
io_iterator_t foundIterator = 0;
io_service_t usbDevice;
matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);
IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &foundIterator);
for(usbDevice = IOIteratorNext(foundIterator); usbDevice; usbDevice = IOIteratorNext(foundIterator))
{
IOCFPlugInInterface** plugin = NULL;
SInt32 theScore=0;
IOReturn err;
err = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &theScore);
if (err!= 0){
std::cout<<"error, error code: "<<err_get_code(err) <<std::endl;
}
else if (plugin && *plugin)
{
IOUSBDeviceInterface182** usbInterface = NULL;
(*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182),(LPVOID*)&usbInterface);
(*plugin)->Release(plugin);
if (usbInterface && *usbInterface)
{
UInt8 devClass;
UInt8 devSubClass;
UInt16 productId;
UInt16 vendorID;
//here I'm getting 0 for all my USB flash cards
(*usbInterface)->GetDeviceClass(usbInterface,&devClass);
(*usbInterface)->GetDeviceVendor(usbInterface, &vendorID);
(*usbInterface)->GetDeviceProduct(usbInterface, &productId);
(*usbInterface)->GetDeviceSubClass(usbInterface, &devSubClass);
std::cout<<"device class: "<<+devClass<<std::endl;
std::cout<<"device sub class: "<<+devSubClass<<std::endl;
std::cout<<"vendor ID: "<<vendorID<<std::endl;
std::cout<<"product ID: "<<productId<<std::endl;
}
}
IOObjectRelease(usbDevice);
}
IOObjectRelease(foundIterator);
return 0;
}
Для меня следующий способ итерации по USB работал на OSX:
void scanUsbMassStorage()
{
CFMutableDictionaryRef matchingDictionary = IOServiceMatching(kIOUSBInterfaceClassName);
//now specify class and subclass to iterate only through USB mass storage devices:
CFNumberRef cfValue;
SInt32 deviceClassNum = kUSBMassStorageInterfaceClass;
cfValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &deviceClassNum);
CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBInterfaceClass), cfValue);
CFRelease(cfValue);
SInt32 deviceSubClassNum = kUSBMassStorageSCSISubClass;
cfValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &deviceSubClassNum);
CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBInterfaceSubClass), cfValue);
CFRelease(cfValue);
//NOTE: if you will specify only device class and will not specify subclass, it will return an empty iterator,
//and I don't know how to say that we need any subclass.
//BUT: all the devices I've check had kUSBMassStorageSCSISubClass
io_iterator_t foundIterator = 0;
io_service_t usbInterface;
IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &foundIterator);
//iterate through USB mass storage devices
for(usbInterface = IOIteratorNext(foundIterator); usbInterface; usbInterface = IOIteratorNext(foundIterator))
{
CFTypeRef bsdName = IORegistryEntrySearchCFProperty(usbInterface,
kIOServicePlane,
CFSTR(kIOBSDNameKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively);
CFTypeRef serial = IORegistryEntrySearchCFProperty(usbInterface,
kIOServicePlane,
CFSTR(kUsbSerialPropertyName),
kCFAllocatorDefault,
kIORegistryIterateRecursively|kIORegistryIterateParents);
CFTypeRef pid = IORegistryEntrySearchCFProperty(usbInterface,
kIOServicePlane,
CFSTR(kUsbPidPropertyName),
kCFAllocatorDefault,
kIORegistryIterateRecursively|kIORegistryIterateParents);
CFTypeRef vid = IORegistryEntrySearchCFProperty(usbInterface,
kIOServicePlane,
CFSTR(kUsbVidPropertyName),
kCFAllocatorDefault,
kIORegistryIterateRecursively|kIORegistryIterateParents);
//now we can perform checks and casts from CFTypeRef like this:
std::string filePathStr;
std::string serialStr;
uint16_t pidInt;
uint16_t vidInt;
//getMountPathByBSDName - see below
bool stillOk = getMountPathByBSDName(bsdName, filePath);
if (stillOk)
{
stillOk = CFTypeRef2AsciiString(serial, serialStr);
}
if (stillOk)
{
stillOk = CFTypeRef2uint16(pid, pidInt);
}
if (stillOk)
{
stillOk = CFTypeRef2uint16(vid, vidInt);
}
if (stillOK)
{
//can do something with the values here
}
}
Однако получение пути монтирования из имени BSD было неожиданно сложным для меня, и, если кто-то знает лучший способ, поделитесь им. Вот мой метод. Там много кода, но по крайней мере это работает в нескольких разных версиях OSX:
bool getMountPathByBSDName(CFTypeRef bsdName, std::string& dest)
{
std::list<std::string> bsdNames;
//for getChildBsdNames - see below =)
getChildBsdNames(bsdName, bsdNames);
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (!session)
{
return false;
}
for (const auto& bsdNameStr : bsdNames)
{
DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsdNameStr.c_str());
if (disk)
{
CFDictionaryRef diskInfo = DADiskCopyDescription(disk);
if (diskInfo)
{
char buf[1024];
CFURLRef fspath = (CFURLRef)CFDictionaryGetValue(diskInfo, kDADiskDescriptionVolumePathKey);
if (CFURLGetFileSystemRepresentation(fspath, false, (UInt8 *)buf, 1024))
{
//for now, return the first found partition
dest = std::string(buf);
return true;
}
}
}
}
return false;
}
и наконец — функция getChildBsdNames:
void getChildBsdNames(CFTypeRef bsdName, std::list<std::string>& tgtList)
{
std::string bsdNameStr;
if(!CFTypeRef2AsciiString(bsdName, bsdNameStr))
{
return;
}
CFDictionaryRef matchingDictionary = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdNameStr.c_str());
io_iterator_t it;
IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &it);
io_object_t service;
while ((service = IOIteratorNext(it)))
{
io_iterator_t children;
io_registry_entry_t child;
IORegistryEntryGetChildIterator(service, kIOServicePlane, &children);
while((child = IOIteratorNext(children)))
{
CFTypeRef bsdNameChild = IORegistryEntrySearchCFProperty(child,
kIOServicePlane,
CFSTR (kIOBSDNameKey),
kCFAllocatorDefault,
kIORegistryIterateRecursively);
std::string bsdNameChildStr;
if (CFTypeRef2AsciiString(bsdNameChild, bsdNameChildStr))
{
tgtList.push_back(bsdNameChildStr);
}
}
}
/**
* The device could get name 'disk1s1, or just 'disk1'. In first case, the original bsd name would be
* 'disk1', and the child bsd name would be 'disk1s1'. In second case, there would be no child bsd names,
* but the original one is valid for further work (obtaining various properties).
*/
if (tgtList.empty())
{
tgtList.push_back(bsdNameStr);
}
}
Постскриптум Для этого есть механизм, основанный на событиях, но по некоторым причинам для меня это не было решением. Я выбрал опрос, но это не так сложно изменить этот код, чтобы сделать его основанным на событиях. Остерегайтесь, однако, что даже может прийти раньше, чем устройство будет установлено.
Других решений пока нет …