/* iPodSCSICodePages This project shows how to access the hidden XML data from the iPod's SCSI CodePages This XML contains, amongst other things, the iPod's serial number and audio / video capabilities version 1.0 30 Jan 2007 Dave Addey http://www.thismuchiknow.co.uk/ Adapted from Apple's SCSIOldAndNew sample code v1.1, found at: http://developer.apple.com/samplecode/SCSIOldAndNew/ */ #include #include #include #include #include #include // IO_OBJECT_NULL was added in Mac OS X 10.4. If it's not defined in the headers, define it ourselves. // This allows the same code to build using the 10.4u SDK for Intel-based systems and earlier SDKs for backwards compatibility // with PowerPC-based systems. #ifndef IO_OBJECT_NULL #define IO_OBJECT_NULL ((io_object_t) 0) #endif // define a struct to contain the CodePage data we will be retrieving typedef struct SCSICmd_INQUIRY_CodePageData { UInt8 Padding1; UInt8 CodePage; UInt8 Padding2; UInt8 DataSize; char Data[251]; } SCSICmd_INQUIRY_CodePageData; bool ExecuteVPDInquiryUsingSTUC(SCSITaskDeviceInterface **interface, UInt32 inCodePage, SCSICmd_INQUIRY_CodePageData *inqBuffer) { SCSITaskStatus taskStatus; SCSI_Sense_Data senseData; SCSICommandDescriptorBlock cdb; SCSITaskInterface **task = NULL; IOReturn kr = kIOReturnSuccess; IOVirtualRange *range = NULL; UInt64 transferCount = 0; UInt32 transferCountHi = 0; UInt32 transferCountLo = 0; assert(interface != NULL); // Create a task now that we have exclusive access. task = (*interface)->CreateSCSITask(interface); if (task == NULL) { fprintf(stderr, "xxxxxxxx CreateSCSITask returned NULL.\n"); return false; } else { // Zero the buffer. We set it to be the max data size, even though we may not receive this much back in inqBuffer->Data. bzero(inqBuffer, sizeof(SCSICmd_INQUIRY_CodePageData)); // Allocate a virtual range for the buffer. If we had more than 1 scatter-gather entry, // we would allocate more than 1 IOVirtualRange. range = (IOVirtualRange *) malloc(sizeof(IOVirtualRange)); if (range == NULL) { fprintf(stderr, "xxxxxxxx Could not malloc an IOVirtualRange\n" ); } // Zero the senseData and CDB. bzero(&senseData, sizeof(senseData)); bzero(cdb, sizeof(cdb)); // Set up the range. The address is just the buffer's address. The length is our request size. range->address = (IOVirtualAddress) inqBuffer; range->length = sizeof(SCSICmd_INQUIRY_CodePageData); // We're going to execute an INQUIRY to the device. // see http://en.wikipedia.org/wiki/SCSI_Inquiry_Command for more info on the format of the SCSI INQUIRY command cdb[0] = kSCSICmd_INQUIRY; cdb[1] = 1; // set the EVPD flag to 1 so that the target will return Vital Product Data cdb[2] = inCodePage; // set byte 2 to be the codepage we want to retrieve cdb[4] = sizeof(SCSICmd_INQUIRY_CodePageData); // the size of the data we want to retrieve // Set the actual cdb in the task. kr = (*task)->SetCommandDescriptorBlock(task, cdb, kSCSICDBSize_6Byte); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx Error setting CDB. (0x%08x)\n", kr); } // Set the scatter-gather entry in the task. kr = (*task)->SetScatterGatherEntries(task, range, 1, sizeof(SCSICmd_INQUIRY_CodePageData), kSCSIDataTransfer_FromTargetToInitiator); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx Error setting scatter-gather entries. (0x%08x)\n", kr); } // Set the timeout in the task kr = (*task)->SetTimeoutDuration(task, 10000); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx Error setting timeout. (0x%08x)\n", kr); } // Send it! kr = (*task)->ExecuteTaskSync(task, &senseData, &taskStatus, &transferCount); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx Error executing task. (0x%08x)\n", kr); } // Get the transfer counts transferCountHi = ((transferCount >> 32) & 0xFFFFFFFF); transferCountLo = (transferCount & 0xFFFFFFFF); // Be a good citizen and clean up. free(range); // Release the task interface. (*task)->Release(task); // return a boolean to indicate if we got the data or not return (taskStatus == kSCSITaskStatus_GOOD); } } void CreateDeviceInterfaceUsingSTUC(io_object_t scsiDevice, IOCFPlugInInterface ***thePlugInInterface, SCSITaskDeviceInterface ***theInterface) { kern_return_t kr = kIOReturnSuccess; IOCFPlugInInterface **plugInInterface = NULL; SCSITaskDeviceInterface **interface = NULL; HRESULT plugInResult = S_OK; SInt32 score = 0; assert(scsiDevice != IO_OBJECT_NULL); // Create the base interface of type IOCFPlugInInterface. // This object will be used to create the SCSI device interface object. kr = IOCreatePlugInInterfaceForService(scsiDevice, kIOSCSITaskDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx Couldn't create a plugin interface for the io_service_t. (0x%08x)\n", kr); } else { // Query the base plugin interface for an instance of the specific SCSI device interface // object. plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOSCSITaskDeviceInterfaceID), (LPVOID *) &interface); if (plugInResult != S_OK) { fprintf(stderr, "xxxxxxxx Couldn't create SCSI device interface. (%ld)\n", plugInResult); } } // Set the return values. *thePlugInInterface = plugInInterface; *theInterface = interface; } void GetiPodXML(io_service_t scsiDevice) { IOCFPlugInInterface **plugInInterface = NULL; SCSITaskDeviceInterface **interface = NULL; // Create an interface to this iPod, so that we can send it a SCSI Inquiry command CreateDeviceInterfaceUsingSTUC(scsiDevice, &plugInInterface, &interface); // if we got an interface, then try and get the xml if (interface != NULL) { // get the XML kern_return_t kr = kIOReturnSuccess; assert(interface != NULL); // Get exclusive access for the device if we can. This must be done // before any SCSITasks can be created and sent to the device. kr = (*interface)->ObtainExclusiveAccess(interface); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx ObtainExclusiveAccess failed. (0x%08x)\n", kr); } else { // Execute a VPD INQUIRY command on the device to get the codepages of the hidden XML document // allocate a buffer for the codepage list output SCSICmd_INQUIRY_CodePageData CodePagesList; bool GotCodePagesList = false; // request the data on the codepages of the XML // this list of codepages is stored in codepage 0xC0 GotCodePagesList = ExecuteVPDInquiryUsingSTUC(interface, 0xC0, &CodePagesList); // check we got the codepage and that we got the same codepage number back - this is a sign that we have some data to retrieve if (GotCodePagesList && CodePagesList.CodePage == 0xC0) { // allocate a codepage data structure for retrieving each page (will be re-used) SCSICmd_INQUIRY_CodePageData ThisCodePage; // a tracker bool for each attempt bool GotThisCodePage = false; // get each data codepage UInt32 i; for (i = 0; i < CodePagesList.DataSize; i++) { // request the XML string data for this codepage (the number of which is in CodePagesList.Data[i]) GotThisCodePage = ExecuteVPDInquiryUsingSTUC(interface, CodePagesList.Data[i], &ThisCodePage); if (GotThisCodePage) { // print this chunk of XML to the standard output // we will have received ThisCodePage.DataSize chars in ThisCodePage.Data, // although we don't use ThisCodePage.DataSize for this simple call to fprintf fprintf(stderr, "%s", ThisCodePage.Data); } else { // end the loop i = CodePagesList.DataSize; } } } // done with the interface now, so release our exclusive access to it kr = (*interface)->ReleaseExclusiveAccess(interface); if (kr != kIOReturnSuccess) { fprintf(stderr, "xxxxxxxx ReleaseExclusiveAccess failed. (0x%08x)\n", kr); } } (*interface)->Release(interface); } // if we have a plugin interface, then release it if (plugInInterface != NULL) { IODestroyPlugInInterface(plugInInterface); } } void TestiPodsUsingSTUC(io_iterator_t iterator) { io_service_t scsiDevice = IO_OBJECT_NULL; while ((scsiDevice = IOIteratorNext(iterator))) { GetiPodXML(scsiDevice); } } void CreateMatchingDictionaryForSTUC(CFMutableDictionaryRef *matchingDict) { CFMutableDictionaryRef subDict; assert(matchingDict != NULL); // Create the dictionaries *matchingDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (*matchingDict != NULL) { subDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (subDict != NULL) { // Create a dictionary with the "SCSITaskDeviceCategory" key with the appropriate value // for the device type we're interested in. // for iPods, this key is "iPodUserClientDevice" CFDictionarySetValue(subDict, CFSTR(kIOPropertySCSITaskDeviceCategory), CFSTR("iPodUserClientDevice")); } // Add the dictionary to the main dictionary with the key "IOPropertyMatch" to // narrow the search to the above dictionary. CFDictionarySetValue(*matchingDict, CFSTR(kIOPropertyMatchKey), subDict); CFRelease(subDict); } } boolean_t FindiPodsUsingSTUC(mach_port_t masterPort, io_iterator_t *iterator) { CFMutableDictionaryRef matchingDict = NULL; boolean_t result = false; // Set up a matching dictionary to search the I/O Registry for SCSI devices // we are interested in CreateMatchingDictionaryForSTUC(&matchingDict); if (matchingDict == NULL) { fprintf(stderr, "xxxxxxxx Couldn't create a matching dictionary.\n"); } else { kern_return_t kr; // Now search I/O Registry for matching devices. kr = IOServiceGetMatchingServices(masterPort, matchingDict, iterator); if (*iterator && kr == kIOReturnSuccess) { result = true; } } // IOServiceGetMatchingServices consumes a reference to the // matching dictionary, so we don't need to release the dictionary ref. return result; } int main(int argc, char* argv[]) { CFMutableDictionaryRef matchingDict; io_service_t service; matchingDict = IOBSDNameMatching(kIOMasterPortDefault, 0, "disk2"); if (NULL == matchingDict) { fprintf(stderr, "IOBSDNameMatching returned a NULL dictionary.\n"); return 1; } // Fetch the object with the matching BSD node name. // Note that there should only be one match, so IOServiceGetMatchingService is used instead of // IOServiceGetMatchingServices to simplify the code. service = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); if (IO_OBJECT_NULL == service) { fprintf(stderr, "IOServiceGetMatchingService returned IO_OBJECT_NULL.\n"); return 1; } GetiPodXML(service); return 0; }