C-Program to read the SMART-log of a SATA drive

Every SATA drive provides some basic health and statistics information; That information is called SMART(SELF-MONITORING, ANALYSIS AND REPORTING TECHNOLOGY). Information like Number of Bad Blocks, Height of Read/Write Head(in HDD), Host Pages Written, Flash Pages Written(in SSD) etc are sent in SMART log command.

Here in this article, we will see how to launch a READ SMART LOG command and how to extract various attributes from the raw data returned in the response of Command.

The READ SMART LOG command returns a 512byte data. Out of which 0-361 bytes are relevant to us and hold 30 attributes. The first 2 bytes gives the SMART Structure Version and rest all bytes hold the 30 attributes(each attribute is 12 bytes long).

below is the table that shows the bytes of smart data(credit: MICRON).

smart attribute

below-table(credit: MICRON) tells attributes format and most important of all those values are RAW VALUE(total 6 bytes – 5 to 11 byte) and attribute ID. And we will, with our code, try to extract that.

attribute formate

Let’s understand the method to send a read SMART log command:
NOTE: We will use SCSI GENRIC(sg) drivers to launch the command(though the drive and the command are ATA-based). The reason being that in Linux(yes we will use Linux environment), ATA drives register themselves as SCSI drives and not ATA drives as SCSI has been quite stable.
step1: allocate a buffer to store the smart-log-command data.
step2: prepare the CDB(COMMAND DESCRIPTOR BLOCK) of a Read SMART Log command.
step3: open the device file (/dev/sd<>   or /dev/sg<>)
step4: prepare the sg_io_hdr structure(structure used by SCSI generic driver)
step5: call the IOCTL to launch the command(IOCTL of SCSI driver).
step6: interpret the data returned by the command.

step1:

unsigned char buffer[BUFFER_SIZE];

step2:
For every command that we launch through SCSI driver, there must have some CDB. There are various sized commands. We will use 12 Bytes long CDB. And we will use ATA PASS THROUGH command of SCSI(SCSI has an internal layer to translate some of the basic SCSI-commands like read and write command into ATA read and writes but it also provides a separate command just for ATA called ATA-PASS-THROUGH) to send the READ SMART LOG command.

below is the table explaining 12 bytes of ATA PASS THROUGH CDB(credit: t10.org(see this PDF for the description of fields)).

ATA PASSTHROUGH command 12 byte

unsigned char smart_read_cdb[12] = {
0xa1, /* operation code */
0x0c, /* protocol- DMA */
0x0e, /* T_DIR = 1(from device), BYT_BLOCK = 1(block length), T_LENGTH = 2
(sector-count will hold the number of blocks to be transfered) */
0xd0,  /*  feature fixed – see the tabel below*/
1,        /* number of blocks to be transfered */
0,
0x4f,  /*  fixed – see the tabel below*/
0xc2,  /*  fixed – see the tabel below*/
0,
0xb0, /* read smart log command  */
0,
0};

below is the table of SMART command inputs (credit:  t13.org(ATA protocol document)).

smart input

step3:
opening the device file

if((fd = open(device_file_name, O_RDONLY)) < 0 ){
printf(“could not open device file\n”);
return 1;
}

step4: prepare the sg_io_hdr structure. This structure is sent with SG_IO magic number while calling IOCTL. The IOCTL function is used to do operations other than read, write, open and close over a file(device is a file in Linux). SG_IO is the magic number to identify that present IOCTL call is to launch the SCSI commands. You can read about SG_IO and its structure from here.

NOTE: there are many other fields in sg_io_hdr structure but these many are sufficient to simply launch a command.

sg_io_hdr_t io_hdr;
memset(&io_hdr,0,sizeof(sg_io_hdr_t)); /* this will take care of other fields that are not used here */
io_hdr.interface_id = ‘S’; /* fix for SCSI */
io_hdr.cmd_len = CMD_LENGTH; /* it is length of command, 12 here */
io_hdr.mx_sb_len = SENSE_BUFFER_SIZE; /* size of sense buffer – sense data is returned when some error happens*/
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; /* direction of data transfer, here from device */
io_hdr.dxfer_len = BUFFER_SIZE; /* size of buffer where returned data will be stored, 512 here */
io_hdr.dxferp = buffer; /* pointer to the buffer */
io_hdr.cmdp = smart_read_cdb; /* pointer to the CDB */
io_hdr.sbp = sense_buffer; /*  pointer to sense buffer*/
io_hdr.timeout = 20000; /* command timeout */

step5: call the IOCTL function.

if(ioctl( fd, SG_IO, &io_hdr) < 0){
printf(“IOCTL CALL FAILED\n”);
return 1;
}

step6: interpreting the data returned by READ SMART Log command.

double raw;
int id;

printf(“ID\t\tONLINE-OFFLINE\t\tRAW-VALUE\n”);
for(register int i = 2;i < 361 ; i = i+12 ){
id = (int)buffer[i];
printf(“%d\t\t”, id);

if(buffer[i+1] & 0x00000002)
printf(“ONLINE+OFFLINE\t\t”);
else printf(“OFFLINE       \t\t”);

raw = 0;
raw = (long long int)((buffer[i+5])|(buffer[i+6]<<8)|(buffer[i+7]<<16)|(buffer[i+8]<<24)|((long long int)buffer[i+9]<<32)|((long long int)buffer[i+10]<<40));
printf(“%lld”,(long long int)raw);
printf(“\n”);
}

below is the complete code for launching and interpreting READ SMART LOG command

GITHUB Link for downloading the Code.

NOTE: In this code, device file name is passed as an argument while executing from the terminal. Not all vendors give all 30 attributes but this code tries to extract all 30 thus might show 0 as raw value for attributes not provided by vendors. This code also extracts the information whether the attributes are calculated ONLINE or OFFLINE and displays that.

#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdio.h>
#include<sys/ioctl.h>
#include<stdlib.h>
#include<scsi/sg.h>
#include<scsi/scsi_ioctl.h>
#include<string.h>
#include <string.h>

#define BUFFER_SIZE 512
#define SENSE_BUFFER_SIZE 32
#define CMD_LENGTH 12

int main(int argc, char* argv[]){
int fd = 0;
unsigned char buffer[BUFFER_SIZE];
unsigned char sense_buffer[SENSE_BUFFER_SIZE];
///////////////////forming SMART READ CDB/////////////////
unsigned char smart_read_cdb[CMD_LENGTH] = {
0xa1,
0x0c,
0x0e,
0xd0,
1,
0,
0x4f,
0xc2,
0,
0xb0,
0,
0};

sg_io_hdr_t io_hdr;
char* device_file_name = 0;

if(argc<2){
printf(“please enter a device file\n”);
return 1;
}else device_file_name = argv[1];

/////////////opening device file//////////////////

if((fd = open(device_file_name, O_RDONLY)) < 0 ){
printf(“could not open device file\n”);
return 1;
}
//////////////////initialize your buffer///////////////

for(register int i = 0 ; i < BUFFER_SIZE; buffer[i++] = 0 );

//////////prepare the CDB ////////////////////////////
memset(&io_hdr,0,sizeof(sg_io_hdr_t));
io_hdr.interface_id = ‘S’;
io_hdr.cmd_len = CMD_LENGTH;
io_hdr.mx_sb_len = SENSE_BUFFER_SIZE;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = BUFFER_SIZE;
io_hdr.dxferp = buffer;
io_hdr.cmdp = smart_read_cdb;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 20000;

/////////////// calling IOCTL///////////////////////////
if(ioctl(fd,SG_IO,&io_hdr) < 0){
printf(“IOCTL CALL FAILED\n”);
return 1;
}

////////////////////extracting SMART attributes/////////////
double raw;
int id;

printf(“ID\t\tONLINE-OFFLINE\t\tRAW-VALUE\n”);
for(register int i = 2;i < 361 ; i = i+12 ){
id = (int)buffer[i];
printf(“%d\t\t”, id);

if(buffer[i+1] & 0x00000002)
printf(“ONLINE+OFFLINE\t\t”);
else printf(“OFFLINE \t\t”);

raw = 0;
raw = (long long int)((buffer[i+5])|(buffer[i+6]<<8)|(buffer[i+7]<<16)|(buffer[i+8]<<24)|((long long int)buffer[i+9]<<32)|((long long int)buffer[i+10]<<40));
printf(“%lld”,(long long int)raw);
printf(“\n”);
}
return 0;
}

NOTE: you people can write more terse interpreter by yourself this was just a basic attempt to extract the information.

OUTPUT: for TOSHIBA HDD

Screenshot from 2017-03-19 20-01-19

GOOD DAY,

8 thoughts on “C-Program to read the SMART-log of a SATA drive

  1. Pingback: A look at S.M.A.R.T. | semi/signal

      • I have been printing data[13] for the threshold the output of which is not matching with the smartctl output .
        For example.from.the code for reallocatavle.sector.count threshold value by extending the above come is data[13] is 11
        Where as when I compared the same.with the clvalue.from.command line
        Smartctl – a /dev/sda1
        The value is 50

        Like

  2. I have been printing data[13] for the threshold the output of which is not matching with the smartctl output .
    For example.from.the code for reallocatavle.sector.count threshold value by extending the above come is data[13] is 11
    Where as when I compared the same.with the clvalue.from.command line
    Smartctl – a /dev/sda1
    The value is 50

    Like

    • In the from loop also we are skipping evry 12 bytes for the meta. Data of each attributes.
      I was able to retrieve the threshold value.bybchanging the command from.0xd0 to 0xd1
      So in the same code.unchamge the command from 0xd0.to.0xd1 and at the same offset of raw value we can get threshold value i tested this code.on.several hdds and result were consistent with the smartctl output .
      Thanks for sharing the knowledge.

      Like

Leave a reply to Varmaan Cancel reply