/* Copyright (c) 2004 Peter O'Gorman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #define PROGNAME "uncle" typedef struct ATASMARTAttributeStruct { UInt8 attributeID; UInt8 statusFlags[2]; UInt8 attributeValue; UInt8 reserved1; UInt8 reserved2[6]; UInt8 reserved3; } ATASMARTAttributeStruct; typedef struct ATASMARTThresholdsStruct { UInt8 attributeID; UInt8 thresholdVal;; UInt8 reserved[10]; } ATASMARTThresholdsStruct; typedef struct ATASMARTDataStruct { UInt16 revNumber; ATASMARTAttributeStruct attributes[30]; UInt8 offLineDataCollectionStatus; UInt8 selfTestExecutionStatus; UInt8 secondsToCompleteOffLineActivity[2]; UInt8 vendorSpecific2; UInt8 offLineDataCollectionCapability; UInt8 SMARTCapability[2]; UInt8 errorLoggingCapability; UInt8 vendorSpecific3; UInt8 shortTestPollingInterval; /* expressed in minutes */ // My ATA drive spec sheet lists this UInt8 extendedTestPollingInterval; /* expressed in minutes */ // as the test completion time... UInt8 reserved[12]; UInt8 vendorSpecific4[125]; UInt8 checksum; } ATASMARTDataStruct; typedef struct ATASMARTThreshStruct // good naming dude ... NOT! { UInt16 revNumber; ATASMARTThresholdsStruct thresholds[30]; UInt8 reserved[18]; UInt8 vendorSpecific[131]; UInt8 checksum; } ATASMARTThreshStruct; void print_usage() { fprintf(stderr, "Usage: %s -hsewnd:",PROGNAME); } IOReturn enable_smart(IOATASMARTInterface ** SMARTInterface) { IOReturn ioresult = 0; ioresult = (*SMARTInterface)->SMARTEnableDisableOperations(SMARTInterface,1); return ioresult; } IOReturn disable_smart(IOATASMARTInterface ** SMARTInterface) { IOReturn ioresult = 0; ioresult = (*SMARTInterface)->SMARTEnableDisableOperations(SMARTInterface,0); return ioresult; } kern_return_t MyGetDeviceFilePath( io_registry_entry_t nextMedia, char *deviceFilePath, CFIndex maxPathSize ) { kern_return_t kernResult = KERN_FAILURE; CFTypeRef deviceFilePathAsCFString; *deviceFilePath = '\0'; deviceFilePathAsCFString = IORegistryEntryCreateCFProperty( nextMedia, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); if ( deviceFilePathAsCFString ) { size_t devPathLength = strlen( _PATH_DEV ); strcpy( deviceFilePath, _PATH_DEV ); if ( CFStringGetCString( deviceFilePathAsCFString, deviceFilePath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII ) ) { kernResult = KERN_SUCCESS; } CFRelease( deviceFilePathAsCFString ); } return kernResult; } IOReturn await_test_completion(IOATASMARTInterface ** SMARTInterface, int flags) { IOReturn ioresult = 0; ATASMARTDataStruct smartData; ioresult = (*SMARTInterface)->SMARTReadData(SMARTInterface,(ATASMARTData*)&smartData); if (ioresult) return ioresult; if (flags & 0x0010) fprintf(stderr,".\n"); while (0xF0 == (smartData.selfTestExecutionStatus & 0xF0)) { UInt8 progress = 0; sleep(60 * smartData.shortTestPollingInterval); // sleep time ioresult = (*SMARTInterface)->SMARTReadData(SMARTInterface,(ATASMARTData*)&smartData); if (ioresult) return ioresult; // FIXME if (flags & 0x0010) { progress = 10 - ( smartData.selfTestExecutionStatus & 0x0F); while (progress) { fprintf(stderr,"."); progress--; } fprintf(stderr,"\n"); } } return 0; } const char * testname(UInt8 value) { char * retVal = "Unknown"; switch(value) { case 0: retVal = "Invaid 0"; break; case 1: retVal = "Raw Read Error Rate"; break; case 2: retVal = "Throughput Performance (*)"; break; case 3: retVal = "Spin Up Time"; break; case 4: retVal = "Start/Stop Count"; break; case 5: retVal = "Reallocated Sector Count"; break; case 7: retVal = "Seek Error Rate"; break; case 8: retVal = "Seek Time Performance (*)"; break; case 9: retVal = "Power-On Hours Count"; break; case 10: retVal = "Spin Retry Count"; break; case 11: retVal = "Calibration Retry Count"; break; case 12: retVal = "Device Power Cycle Count"; break; case 191: retVal = "Gsense Error Rate"; break; case 192: retVal = "Power Off Retract Count"; break; case 193: retVal = "Load/Unload Cycle Count"; break; case 194: retVal = "Device Temperature"; break; case 195: retVal = "On The Fly Error Rate"; break; case 196: retVal = "Reallocation Event Count"; break; case 197: retVal = "Current Pending Sector Count"; break; case 198: retVal = "Off-Line Scan Uncorrectable Sector Count"; break; case 199: retVal = "Ultra DMA CRC Error Count" ; break; case 200: retVal = "Write Preamp Errors"; break; case 201: retVal = "Off Track Errors"; break; case 202: retVal = "DAM Error Rate"; break; case 203: retVal = "ECC Errors"; break; case 204: retVal = "Raw Read Error Rate"; break; case 205: retVal = "Thermal Asperity Rate"; break; case 206: retVal = "Unknown 206"; break ; case 207: retVal = "Spin High Current"; break; case 208: retVal = "Spin Buzz"; break; case 209: retVal = "Off Line Seek Performance"; break; } return retVal; } void doRealStuff(IOATASMARTInterface ** SMARTInterface, int flags) { UInt16 id[256]; ATASMARTDataStruct smartData; ATASMARTThreshStruct threshData; char firmwareRev[9]; char serialNumber[21]; char modelNumber[41]; IOReturn ioresult = 0; Boolean failing = 0; int thirtyCount; // get the device identity data ioresult = (*SMARTInterface)->GetATAIdentifyData(SMARTInterface,(void*)&id,512,NULL); if (ioresult) { fprintf(stderr,"GetATAIdentifyData failed\n"); exit(1); // should never be here } // we are only interested in ATA devices, not ATAPI if ( 0 != (id[0] & 0xF000)) { return; } memcpy(serialNumber,&id[10],20); serialNumber[20] = 0; memcpy(modelNumber,&id[27],40); modelNumber[40] = 0; memcpy(firmwareRev,&id[23],8); firmwareRev[8] = 0; fprintf(stdout,"Serial: %s\nModel: %s\nFirmware: %s\n",serialNumber, modelNumber, firmwareRev); if ( 1 != (id[82] & 0x0001)) { fprintf(stderr,"Device is not S.M.A.R.T.\n"); return; } else { fprintf(stderr,"Device supports S.M.A.R.T. operations\n"); if (2 == (id[84] & 0x0002)) { fprintf(stderr,"SMART self-test supported\n"); } if (1 == (id[84] & 0x0001)) { fprintf(stderr,"SMART error logging supported\n"); } } if ( 1 != (id[85] & 0x0001)) { fprintf(stderr,"S.M.A.R.T. operations are not enabled\n"); return; } else { fprintf(stderr,"S.M.A.R.T. operations are enabled\n"); } if (2 == (id[87] & 0x0002)) { fprintf(stderr,"SMART self-test enabled\n"); } if (1 == (id[87] & 0x0001)) { fprintf(stderr,"SMART error logging enabled\n"); } if (flags & 0x0003) { ioresult = (*SMARTInterface)->SMARTExecuteOffLineImmediate(SMARTInterface,(flags & 0x0002)?1:0); if (ioresult) return; // FIXME } // now we wait for completion. ioresult = (*SMARTInterface)->SMARTReadData(SMARTInterface,(ATASMARTData*)&smartData); if (ioresult) return; // FIXME if (flags & 0x1000) { ioresult = await_test_completion(SMARTInterface, flags); if (ioresult) return; // FIXME } // Whee, now we have a post-test smartData struct. Get the thresholds... ioresult = (*SMARTInterface)->SMARTReadDataThresholds(SMARTInterface,(ATASMARTDataThresholds*)&threshData); if (ioresult) return; // FIXME // ioresult = (*SMARTInterface)->SMARTReturnStatus(SMARTInterface,&failing); if (ioresult) exit(1); // FIXME fprintf(stdout,"Status is %s\n", failing ? "BAD" : "GOOD"); fprintf(stdout," %-50s\t%6s\t%6s\t%6s\n","TEST","THRESH","VALUE","STATUS"); fprintf(stdout,"------------------------------------------------------------------------------\n"); for (thirtyCount = 0; thirtyCount < 30; thirtyCount++) { ATASMARTAttributeStruct * attr = &smartData.attributes[thirtyCount]; ATASMARTThresholdsStruct *thresh = &threshData.thresholds[thirtyCount]; int status = attr->statusFlags[1] & 0x03; if (!attr->attributeID) continue; if (attr->attributeID != thresh->attributeID) continue; fprintf(stdout,"(%3i) %-50s\t",attr->attributeID,testname(attr->attributeID)); fprintf(stdout,"%6x\t",thresh->thresholdVal); fprintf(stdout,"%6x\t",attr->attributeValue); fprintf(stdout,"%6x\n",status); } } int main(int argc, const char ** argv) { int ch; kern_return_t result = KERN_SUCCESS; mach_port_t masterport = NULL; char* devname = NULL; CFMutableDictionaryRef matchingDictionary = NULL; io_iterator_t ata_iterator; io_registry_entry_t ata_drive; int flags = 0; // 1 is a short test, 0 no test, 2 long test. int foundDev = 0; // Get command line options; while ((ch = getopt(argc, (char * const *)argv, "vhsewbnd:")) != -1) { switch (ch) { case 'h': print_usage(); // help break; case 's': flags |= 0x0001 ; // simple test break; case 'e': flags |= 0x0002 ; // extended test break; case 'w': flags |= 0x1000 ; // wait for test completion break; case 'n': flags |= 0x2000 ; // Disable SMART operations on this device break; case 'b': flags |= 0x4000 ; // Enable SMART operations on this device break; case 'v': flags |= 0x0010 ; // verbose break; case 'd': devname = strdup(optarg); // specify the device to use. break; } } // get a mach_port result = IOMasterPort( MACH_PORT_NULL, &masterport ); if (KERN_SUCCESS != result) { fprintf(stderr,"IOMasterPort failed...\n"); exit(1); } // match all Disks matchingDictionary = IOServiceMatching(kIOBlockStorageDeviceClass); if (NULL == matchingDictionary) { fprintf(stderr,"IOServiceMatching failed...\n"); exit(1); } result = IOServiceGetMatchingServices(masterport,matchingDictionary, &ata_iterator); if (KERN_SUCCESS != result || 0 == ata_iterator) { fprintf(stderr,"IOServiceGetMatchingServices failed...\n"); exit(1); } while ((ata_drive = IOIteratorNext(ata_iterator))) { IOReturn ioresult = 0; SInt32 theScore = 0; IOCFPlugInInterface **SMARTPlugin = 0; IOATASMARTInterface **SMARTInterface = 0; io_registry_entry_t child_ata_drive; io_string_t pathName; char deviceFilePath[128] = {0}; ioresult = IOCreatePlugInInterfaceForService(ata_drive, kIOATASMARTUserClientTypeID, kIOCFPlugInInterfaceID, &SMARTPlugin, &theScore); if (ioresult) { IOObjectRelease(ata_drive); ata_drive = 0; continue; } // we got the plugin, but we need the interface, seems a little strange to me, but hey, this is mostly above my head. SMARTInterface = 0; (*SMARTPlugin)->QueryInterface(SMARTPlugin, CFUUIDGetUUIDBytes(kIOATASMARTInterfaceID), (void **)&SMARTInterface); if (!SMARTInterface) { IOObjectRelease(ata_drive); ata_drive = 0; (*SMARTPlugin)->Release(SMARTPlugin); continue; } // The child of the child should be an IOMedia object whcih will have the BSD name :/ IORegistryEntryGetPath(ata_drive, kIOServicePlane, pathName); ioresult = IORegistryEntryGetChildEntry(ata_drive,kIOServicePlane,&child_ata_drive); ioresult = IORegistryEntryGetChildEntry(child_ata_drive,kIOServicePlane,&child_ata_drive); IORegistryEntryGetPath(child_ata_drive, kIOServicePlane, pathName); MyGetDeviceFilePath( child_ata_drive, deviceFilePath, 128 ); fprintf(stderr,"BSD Path:%s\n", deviceFilePath); if (devname && strncmp(devname,deviceFilePath, strlen(deviceFilePath))) { continue; } if (flags & 0x2000) { ioresult = disable_smart(SMARTInterface); if (ioresult) { fprintf(stderr,"Disable SMART Failed\n"); exit(1); } continue; } if (flags & 0x4000) { ioresult = enable_smart(SMARTInterface); if (ioresult) { fprintf(stderr,"Enable SMART Failed\n"); exit(1); } continue; } // Now we have the interface we can finally do real stuff!!! doRealStuff(SMARTInterface,flags); foundDev++; IOObjectRelease(ata_drive); ata_drive = 0; } IOObjectRelease(ata_iterator); if (!foundDev && devname) { fprintf(stderr,"Did not find an ata drive at %s\n",devname); exit(1); } if (!foundDev) { fprintf(stderr,"No devices were found ... do you have another application open keeping a reference to the ATASMART plugin open?\n"); exit(1); } if (ata_drive) IOObjectRelease(ata_drive); return 0; }