简体   繁体   English

OSX,USB 大容量存储发现

[英]OSX, usb mass storage discovery

I'm trying to discover USB mass storage devices under MAC OSX.我正在尝试在 MAC OSX 下发现 USB 大容量存储设备。 I hoped to get the device class, and, on base of that, decide if the device is a mass storage or not.我希望获得设备类,并在此基础上确定设备是否为大容量存储设备。 But, for all the usb flash drives I have, I'm getting the device class == 0, which seems to be a composite device.但是,对于我拥有的所有 USB 闪存驱动器,我得到的设备类 == 0,这似乎是一个复合设备。 Please, help me to figure out, what am I doing wrong, or, maybe, what is other reliable way to discover USB Mass Storage devices (I need to get PID, VID and mount point).请帮我弄清楚,我做错了什么,或者,也许还有其他可靠的方法来发现 USB 大容量存储设备(我需要获取 PID、VID 和安装点)。 Here is my code:这是我的代码:

#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;

} }

For me, the following way of iterating through USB worked on OSX:对我来说,以下通过 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 make a query for *any* subclass. 
 //BUT: all the devices I've checked 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
    }
 }

Getting mount path from BSD name, though, was unexpectedly tricky for me, and if someone knows a better way, please, share it.不过,从 BSD 名称获取挂载路径对我来说出乎意料地棘手,如果有人知道更好的方法,请分享。 Here is my method.这是我的方法。 There is a lot of code, but at least this works in several different OSX versions:有很多代码,但至少这适用于几个不同的 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;
}

and finally - getChildBsdNames function:最后 - 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);
}
}

PS There is an event-based mechanism for that, but for some reasons for me it wasn't a solution. PS 有一个基于事件的机制,但出于某些原因,这不是解决方案。 I've choose polling, but it is not that hard to modify this code to make it event based.我选择了轮询,但修改此代码使其基于事件并不难。 Beware, though, that the event could come earlier than the device would be mounted.但请注意,该事件可能早于安装设备。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM