/*
 * ataprint.c
 *
 * Home page of code is: http://smartmontools.sourceforge.net
 *
 * Copyright (C) 2002 Bruce Allen <smartmontools-support@lists.sourceforge.net>
 * Copyright (C) 1999-2000 Michael Cornwell <cornwell@acm.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * You should have received a copy of the GNU General Public License
 * (for example COPYING); if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * This code was originally developed as a Senior Thesis by Michael Cornwell
 * at the Concurrent Systems Laboratory (now part of the Storage Systems
 * Research Center), Jack Baskin School of Engineering, University of
 * California, Santa Cruz. http://ssrc.soe.ucsc.edu/
 *
 */

#include <ctype.h>
#include <stdio.h>
#include "ataprint.h"
#include "smartctl.h"
#include "extern.h"

const char *CVSid4="$Id: ataprint.c,v 1.29 2002/10/23 15:15:43 ballen4705 Exp $\n"
	           "\t" CVSID2 "\t" CVSID3 "\t" CVSID6 ;

// Function for printing ASCII byte-swapped strings, skipping white
// space. This is needed on little-endian architectures, eg Intel,
// Alpha. If someone wants to run this on SPARC they'll need to test
// for the Endian-ness and skip the byte swapping if it's big-endian.
void printswap(char *in, unsigned int n){
  unsigned int i;
  char out[64];

  // swap bytes
  for (i=0;i<n;i+=2){
    unsigned int j=i+1;
    out[i]=in[j];
    out[j]=in[i];
  }

  // find the end of the white space
  for (i=0;i<n && isspace(out[i]);i++);

  // and do the printing starting from first non-white space
  if (n-i)
    pout("%.*s\n",(int)(n-i),out+i);
  else
    pout("[No Information Found]\n");

  return;
}


void ataPrintDriveInfo (struct hd_driveid drive){
  int version;
  const char *description;
  char unknown[64];

  // print out model, serial # and firmware versions  (byte-swap ASCI strings)
  pout("Device Model:     ");
  printswap(drive.model,40);

  pout("Serial Number:    ");
  printswap(drive.serial_no,20);

  pout("Firmware Version: ");
  printswap(drive.fw_rev,8);

  // now get ATA version info
  version=ataVersionInfo(&description,drive);

  // unrecognized minor revision code
  if (!description){
    sprintf(unknown,"Unrecognized. Minor revision code: 0x%02x",drive.minor_rev_num);
    description=unknown;
  }
  
  
  // SMART Support was first added into the ATA/ATAPI-3 Standard with
  // Revision 3 of the document, July 25, 1995.  Look at the "Document
  // Status" revision commands at the beginning of
  // http://www.t13.org/project/d2008r6.pdf to see this.  So it's not
  // enough to check if we are ATA-3.  Version=-3 indicates ATA-3
  // BEFORE Revision 3.
  pout("ATA Version is:   %i\n",version>0?version:-1*version);
  pout("ATA Standard is:  %s\n",description);
  
  if (version>=3)
    return;
  
  pout("SMART is only available in ATA Version 3 Revision 3 or greater.\n");
  pout("We will try to proceed in spite of this.\n");
  return;
}


/* void PrintSmartOfflineStatus ( struct ata_smart_values data) 
   prints verbose value Off-line data collection status byte */

void PrintSmartOfflineStatus ( struct ata_smart_values data)
{
   pout ("Off-line data collection status: ");	
   
   switch (data.offline_data_collection_status){
   case 0x00:
   case 0x80:
     pout ("(0x%02x)\tOffline data collection activity was\n\t\t\t\t\t",
	     data.offline_data_collection_status);
     pout("never started.\n");
     break;
   case 0x01:
   case 0x81:
     pout ("(0x%02x)\tReserved.\n",
	     data.offline_data_collection_status);
     break;
   case 0x02:
   case 0x82:
     pout ("(0x%02x)\tOffline data collection activity \n\t\t\t\t\t",
	     data.offline_data_collection_status);
     pout ("completed without error.\n");
     break;
   case 0x03:
   case 0x83:
     pout ("(0x%02x)\tReserved.\n",
	     data.offline_data_collection_status);
     break;
   case 0x04:
   case 0x84:
     pout ("(0x%02x)\tOffline data collection activity was \n\t\t\t\t\t",
	     data.offline_data_collection_status);
     pout ("suspended by an interrupting command from host.\n");
     break;
   case 0x05:
   case 0x85:
     pout ("(0x%02x)\tOffline data collection activity was \n\t\t\t\t\t",
	     data.offline_data_collection_status);
     pout ("aborted by an interrupting command from host.\n");
     break;
   case 0x06:
   case 0x86:
     pout ("(0x%02x)\tOffline data collection activity was \n\t\t\t\t\t",
	     data.offline_data_collection_status);
     pout ("aborted by the device with a fatal error.\n");
     break;
   default:
     if ( ((data.offline_data_collection_status >= 0x07) &&
	   (data.offline_data_collection_status <= 0x3f)) ||
	  ((data.offline_data_collection_status >= 0xc0) &&
	   (data.offline_data_collection_status <= 0xff)) )
       {
	 pout ("(0x%02x)\tVendor Specific.\n",
		 data.offline_data_collection_status);
       } 
     else 
       {
	 pout ("(0x%02x)\tReserved.\n",
		 data.offline_data_collection_status);
       }
   }
}



void PrintSmartSelfExecStatus ( struct ata_smart_values data)
{
   pout ("Self-test execution status:      ");
   
   switch (data.self_test_exec_status >> 4)
   {
      case 0:
        pout ("(%4d)\tThe previous self-test routine completed\n\t\t\t\t\t",
                data.self_test_exec_status);
        pout ("without error or no self-test has ever \n\t\t\t\t\tbeen run.\n");
        break;
       case 1:
         pout ("(%4d)\tThe self-test routine was aborted by\n\t\t\t\t\t",
                 data.self_test_exec_status);
         pout ("the host.\n");
         break;
       case 2:
         pout ("(%4d)\tThe self-test routine was interrupted\n\t\t\t\t\t",
                 data.self_test_exec_status);
         pout ("by the host with a hard or soft reset.\n");
         break;
       case 3:
          pout ("(%4d)\tA fatal error or unknown test error\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("occurred while the device was executing\n\t\t\t\t\t");
          pout ("its self-test routine and the device \n\t\t\t\t\t");
          pout ("was unable to complete the self-test \n\t\t\t\t\t");
          pout ("routine.\n");
          break;
       case 4:
          pout ("(%4d)\tThe previous self-test completed having\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("a test element that failed and the test\n\t\t\t\t\t");
          pout ("element that failed is not known.\n");
          break;
       case 5:
          pout ("(%4d)\tThe previous self-test completed having\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("the electrical element of the test\n\t\t\t\t\t");
          pout ("failed.\n");
          break;
       case 6:
          pout ("(%4d)\tThe previous self-test completed having\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("the servo (and/or seek) element of the \n\t\t\t\t\t");
          pout ("test failed.\n");
          break;
       case 7:
          pout ("(%4d)\tThe previous self-test completed having\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("the read element of the test failed.\n");
          break;
       case 15:
          pout ("(%4d)\tSelf-test routine in progress...\n\t\t\t\t\t",
                  data.self_test_exec_status);
          pout ("%1d0%% of test remaining.\n", 
                  data.self_test_exec_status & 0x0f);
          break;
       default:
          pout ("(%4d)\tReserved.\n",
                  data.self_test_exec_status);
          break;
   }
	
}



void PrintSmartTotalTimeCompleteOffline ( struct ata_smart_values data)
{
   pout ("Total time to complete off-line \n");
   pout ("data collection: \t\t (%4d) seconds.\n", 
           data.total_time_to_complete_off_line);
}



void PrintSmartOfflineCollectCap ( struct ata_smart_values data)
{
   pout ("Offline data collection\n");
   pout ("capabilities: \t\t\t (0x%02x) ",
            data.offline_data_collection_capability);

   if (data.offline_data_collection_capability == 0x00)
   {
      pout ("\tOff-line data collection not supported.\n");
   } 
   else 
   {
      pout( "%s\n", isSupportExecuteOfflineImmediate(data)?
              "SMART execute Offline immediate." :
              "No SMART execute Offline immediate.");

      pout( "\t\t\t\t\t%s\n", isSupportAutomaticTimer(data)? 
              "Automatic timer ON/OFF support.":
              "No Automatic timer ON/OFF support.");
		
      pout( "\t\t\t\t\t%s\n", isSupportOfflineAbort(data)? 
              "Abort Offline collection upon new\n\t\t\t\t\tcommand.":
              "Suspend Offline collection upon new\n\t\t\t\t\tcommand.");

      pout( "\t\t\t\t\t%s\n", isSupportOfflineSurfaceScan(data)? 
              "Offline surface scan supported.":
              "No Offline surface scan supported.");

      pout( "\t\t\t\t\t%s\n", isSupportSelfTest(data)? 
              "Self-test supported.":
              "No Self-test supported.");
    }
}



void PrintSmartCapability ( struct ata_smart_values data)
{
   pout ("SMART capabilities:            ");
   pout ("(0x%04x)\t", data.smart_capability);
   
   if (data.smart_capability == 0x00)
   {
       pout ("Automatic saving of SMART data\t\t\t\t\tis not implemented.\n");
   } 
   else 
   {
	
      pout( "%s\n", (data.smart_capability & 0x01)? 
              "Saves SMART data before entering\n\t\t\t\t\tpower-saving mode.":
              "Does not save SMART data before\n\t\t\t\t\tentering power-saving mode.");
		
      if ( data.smart_capability & 0x02 )
      {
          pout ("\t\t\t\t\tSupports SMART auto save timer.\n");
      }
   }
}



void PrintSmartErrorLogCapability ( struct ata_smart_values data)
{

   pout ("Error logging capability:       ");
    
   if ( isSmartErrorLogCapable(data) )
   {
      pout (" (0x%02x)\tError logging supported.\n",
               data.errorlog_capability);
   }
   else {
       pout (" (0x%02x)\tError logging NOT supported.\n",
                data.errorlog_capability);
   }
}



void PrintSmartShortSelfTestPollingTime ( struct ata_smart_values data)
{
   if ( isSupportSelfTest(data) )
   {
      pout ("Short self-test routine \n");
      pout ("recommended polling time: \t (%4d) minutes.\n", 
               data.short_test_completion_time);

   }
   else
   {
      pout ("Short self-test routine \n");
      pout ("recommended polling time: \t        Not Supported.\n");
   }
}


void PrintSmartExtendedSelfTestPollingTime ( struct ata_smart_values data)
{
   if ( isSupportSelfTest(data) )
   {
      pout ("Extended self-test routine \n");
      pout ("recommended polling time: \t (%4d) minutes.\n", 
               data.extend_test_completion_time);
   }
   else
   {
      pout ("Extended self-test routine \n");
      pout ("recommended polling time: \t        Not Supported.\n");
   }
}


// onlyfailed=0 : print all attribute values
// onlyfailed=1:  just ones that are currently failed and have prefailure bit set
// onlyfailed=2:  ones that are failed, or have failed with or without prefailure bit set
void PrintSmartAttribWithThres (struct ata_smart_values data, 
				struct ata_smart_thresholds thresholds,
				int onlyfailed){
  int i,j;
  long long rawvalue;
  int needheader=1;
    
  // step through all vendor attributes
  for (i=0; i<NUMBER_ATA_SMART_ATTRIBUTES; i++){
    char *status;
    struct ata_smart_attribute *disk=data.vendor_attributes+i;
    struct ata_smart_threshold_entry *thre=thresholds.thres_entries+i;
    
    // consider only valid attributes
    if (disk->id && thre->id){
      char *type;
      int failednow,failedever;

      failednow =disk->current <= thre->threshold;
      failedever=disk->worst   <= thre->threshold;
      
      // These break out of the loop if we are only printing certain entries...
      if (onlyfailed==1 && (!disk->status.flag.prefailure || !failednow))
	continue;
      
      if (onlyfailed==2 && !failedever)
	continue;
      
      // print header only if needed
      if (needheader){
	if (!onlyfailed){
	  pout ("SMART Attributes Data Structure revision number: %i\n",data.revnumber);
	  pout ("Vendor Specific SMART Attributes with Thresholds:\n");
	}
	pout("ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE     WHEN_FAILED RAW_VALUE\n");
	needheader=0;
      }
      
      // is this currently failed, or has it ever failed?
      if (failednow)
	status="FAILING_NOW";
      else if (failedever)
	status="In_the_past";
      else
	status="    -";
      
      // Print name of attribute
      ataPrintSmartAttribName(disk->id);
      
      // printing line for each valid attribute
      type=disk->status.flag.prefailure?"Pre-fail":"Old_age";
      pout(" 0x%04x   %.3i   %.3i   %.3i    %-9s%-12s", 
	     disk->status.all, disk->current, disk->worst,
	     thre->threshold, type, status);
      
      // convert the six individual bytes to a long long (8 byte) integer
      rawvalue = 0;
      for (j = 0 ; j < 6 ; j++)
	rawvalue |= disk->raw[j] << (8*j) ;
      
      // This switch statement is where we handle Raw attributes
      // that are stored in an unusual vendor-specific format,
      switch (disk->id){
	// Power on time
      case 9:
	if (smart009minutes)
	  // minutes
	  pout ("%llu h + %2llu m\n",rawvalue/60,rawvalue%60);
	else
	   // hours
	  pout ("%llu\n", rawvalue);  //stored in hours
	break;
	
	// Temperature
      case 194:
	pout ("%u", disk->raw[0]);
	if (rawvalue==disk->raw[0])
	  pout("\n");
	else
	  // The other bytes are in use. Try IBM's model
	  pout(" (Lifetime Min/Max %u/%u)\n",disk->raw[2],
		 disk->raw[4]);
	break;
      default:
	pout("%llu\n", rawvalue);
      }	    
    }
  }
  if (!needheader) pout("\n");
}


void ataPrintGeneralSmartValues(struct ata_smart_values data){
  pout ("General SMART Values:\n");
  
  PrintSmartOfflineStatus(data); 
  
  if (isSupportSelfTest(data)){
    PrintSmartSelfExecStatus (data);
  }
  
  PrintSmartTotalTimeCompleteOffline(data);
  PrintSmartOfflineCollectCap(data);
  PrintSmartCapability(data);
  
  PrintSmartErrorLogCapability(data);
  if (isSupportSelfTest(data)){
    PrintSmartShortSelfTestPollingTime (data);
    PrintSmartExtendedSelfTestPollingTime (data);
  }
  pout("\n");
}

// Is not (currently) used in ANY code
void ataPrintSmartThresholds (struct ata_smart_thresholds data)
{
   int i;

   pout ("SMART Thresholds\n");
   pout ("SMART Threshold Revision Number: %i\n", data.revnumber);
	
   for ( i = 0 ; i < NUMBER_ATA_SMART_ATTRIBUTES ; i++) {
      if (data.thres_entries[i].id)	
          pout ("Attribute %3i threshold: %02x (%2i)\n", 
                   data.thres_entries[i].id, 
                   data.thres_entries[i].threshold, 
                   data.thres_entries[i].threshold);
   }
}


// Returns nonzero if region of memory contains non-zero entries
int nonempty(unsigned char *testarea,int n){
  int i;
  for (i=0;i<n;i++)
    if (testarea[i])
      return 1;
  return 0;
}
  
void ataPrintSmartErrorlog (struct ata_smart_errorlog data){
  int i,j,k;
  
  pout ("SMART Error Log Version: %i\n", data.revnumber);
  
  // if no errors logged, return
  if (!data.error_log_pointer){
    pout ("No Errors Logged\n\n");
    return;
  }
  QUIETON;
  // if log pointer out of range, return
  if ( data.error_log_pointer>5 ){
    pout("Invalid Error log index = %02x (T13/1321D rev 1c"
	 "Section 8.41.6.8.2.2 gives valid range from 1 to 5)\n\n",
	 data.error_log_pointer);
    return;
  }
  
  // starting printing error log info
  if (data.ata_error_count<=5)
    pout ( "ATA Error Count:y %u\n", data.ata_error_count);
  else
    pout ( "ATA Error Count: %u (only the most recent five errors are shown below)\n",
	   data.ata_error_count);
  
  pout("\tDCR = Device Control Register\n");
  pout("\tFR  = Features Register\n");
  pout("\tSC  = Sector Count Register\n");
  pout("\tSN  = Sector Number Register\n");
  pout("\tCL  = Cylinder Low Register\n");
  pout("\tCH  = Cylinder High Register\n");
  pout("\tD/H = Device/Head Register\n");
  pout("\tCR  = Content written to Command Register\n");
  pout("\tER  = Error register\n");
  pout("\tSTA = Status register\n");
  pout("Timestamp is seconds since the previous disk power-on.\n");
  pout("Note: timestamp \"wraps\" after 2^32 msec = 49.710 days.\n\n");
  
  // now step through the five error log data structures (table 39 of spec)
  for (k = 4; k >= 0; k-- ) {
    
    // The error log data structure entries are a circular buffer
    i=(data.error_log_pointer+k)%5;
    
    // Spec says: unused error log structures shall be zero filled
    if (nonempty((unsigned char*)&(data.errorlog_struct[i]),sizeof(data.errorlog_struct[i]))){
      char *msgstate;
      switch (data.errorlog_struct[i].error_struct.state){
      case 0x00: msgstate="in an unknown state";break;
      case 0x01: msgstate="sleeping"; break;
      case 0x02: msgstate="in standby mode"; break;
      case 0x03: msgstate="active or idle"; break;
      case 0x04: msgstate="doing SMART off-line or self test"; break;
      default:   msgstate="in a vendor specific or reserved state";
      }
      pout("Error Log Structure %i:\n",5-k);
      // See table 42 of ATA5 spec
      pout("Error occurred at disk power-on lifetime: %u hours\n",
	     data.errorlog_struct[i].error_struct.timestamp);
      pout("When the command that caused the error occurred, the device was %s.\n",msgstate);
      pout("After command completion occurred, registers were:\n");
      pout("ER:%02x SC:%02x SN:%02x CL:%02x CH:%02x D/H:%02x ST:%02x\n",
	     data.errorlog_struct[i].error_struct.error_register,
	     data.errorlog_struct[i].error_struct.sector_count,
	     data.errorlog_struct[i].error_struct.sector_number,
	     data.errorlog_struct[i].error_struct.cylinder_low,
	     data.errorlog_struct[i].error_struct.cylinder_high,
	     data.errorlog_struct[i].error_struct.drive_head,
	     data.errorlog_struct[i].error_struct.status);
      pout("Sequence of commands leading to the command that caused the error were:\n");
      pout("DCR   FR   SC   SN   CL   CH   D/H   CR   Timestamp\n");
      for ( j = 4; j >= 0; j--){
	struct ata_smart_errorlog_command_struct *thiscommand=&(data.errorlog_struct[i].commands[j]);
	
	// Spec says: unused data command structures shall be zero filled
	if (nonempty((unsigned char*)thiscommand,sizeof(*thiscommand)))
	  pout ( " %02x   %02x   %02x   %02x   %02x   %02x    %02x   %02x     %u.%03u\n", 
		   thiscommand->devicecontrolreg,
		   thiscommand->featuresreg,
		   thiscommand->sector_count,
		   thiscommand->sector_number,
		   thiscommand->cylinder_low,
		   thiscommand->cylinder_high,
		   thiscommand->drive_head,
		   thiscommand->commandreg,
		   (unsigned int)(thiscommand->timestamp / 1000),
		   (unsigned int)(thiscommand->timestamp % 1000)); 
      }
      pout("\n");
    }
  }
  QUIETOFF;
  return;  
}

// return value is number of entries found where the self-test showed an error
int ataPrintSmartSelfTestlog (struct ata_smart_selftestlog data,int allentries){
  int i,j,noheaderprinted=1;
  int retval=0;

  if (allentries)
    pout("SMART Self-test log, version number %u\n",data.revnumber);
  if (data.revnumber!=0x01 && allentries)
    pout("Warning - structure revision number does not match spec!\n");
  if (data.mostrecenttest==0){
    if (allentries)
      pout("No self-tests have been logged\n\n");
    return 0;
  }

  // print log      
  for (i=20;i>=0;i--){    
    struct ata_smart_selftestlog_struct *log;
    // log is a circular buffer
    j=(i+data.mostrecenttest)%21;
    log=data.selftest_struct+j;

    if (nonempty((unsigned char*)log,sizeof(*log))){
      char *msgtest,*msgstat,percent[64],firstlba[64];
      int errorfound=0;

      // test name
      switch(log->selftestnumber){
      case   0: msgtest="Off-line           "; break;
      case   1: msgtest="Short off-line     "; break;
      case   2: msgtest="Extended off-line  "; break;
      case 127: msgtest="Abort off-line test"; break;
      case 129: msgtest="Short captive      "; break;
      case 130: msgtest="Extended captive   "; break;
      default:  msgtest="Unknown test       ";
      }
      
      // test status
      switch((log->selfteststatus)>>4){
      case  0:msgstat="Completed                    "; break;
      case  1:msgstat="Aborted by host              "; break;
      case  2:msgstat="Interrupted (host reset)     "; break;
      case  3:msgstat="Fatal or unknown error       "; errorfound=1; break;
      case  4:msgstat="Completed: unknown failure   "; errorfound=1; break;
      case  5:msgstat="Completed: electrical failure"; errorfound=1; break;
      case  6:msgstat="Completed: servo/seek failure"; errorfound=1; break;
      case  7:msgstat="Completed: read failure      "; errorfound=1; break;
      case 15:msgstat="Test in progress             "; break;
      default:msgstat="Unknown test status          ";
      }
      
      retval+=errorfound;

      sprintf(percent,"%1d0%%",(log->selfteststatus)&0xf);
      if (log->lbafirstfailure==0xffffffff || log->lbafirstfailure==0x00000000)
	sprintf(firstlba,"%s","");
      else	
	sprintf(firstlba,"0x%08x",log->lbafirstfailure);

      if (noheaderprinted && (allentries || errorfound)){
	pout("Num  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error\n");
	noheaderprinted=0;
      }
      
      if (allentries || errorfound)
	pout("#%2d  %s %s %s  %8u         %s\n",21-i,msgtest,msgstat,
	     percent,log->timestamp,firstlba);
    }
  }
  return retval;
}

void ataPseudoCheckSmart ( struct ata_smart_values data, 
                           struct ata_smart_thresholds thresholds) {
  int i;
  int failed = 0;
  for (i = 0 ; i < NUMBER_ATA_SMART_ATTRIBUTES ; i++) {
    if (data.vendor_attributes[i].id &&   
	thresholds.thres_entries[i].id &&
	data.vendor_attributes[i].status.flag.prefailure &&
	(data.vendor_attributes[i].current <= thresholds.thres_entries[i].threshold) &&
	(thresholds.thres_entries[i].threshold != 0xFE)){
      pout("Attribute ID %i Failed\n",data.vendor_attributes[i].id);
      failed = 1;
    } 
  }   
  pout("%s\n", ( failed )?
	 "SMART overall-health self-assessment test result: FAILED!\n"
	 "Drive failure expected in less than 24 hours. SAVE ALL DATA":
	 "SMART overall-health self-assessment test result: PASSED");
}

void ataPrintSmartAttribName ( unsigned char id ){
  char *name;
  switch (id){
    
  case 1:
    name="Raw_Read_Error_Rate";
    break;
  case 2:
    name="Throughput_Performance";
    break;
  case 3:
    name="Spin_Up_Time";
    break;
  case 4:
    name="Start_Stop_Count";
    break;
  case 5:
    name="Reallocated_Sector_Ct";
    break;
  case 6:
    name="Read_Channel_Margin";
    break;
  case 7:
    name="Seek_Error_Rate";
    break;
  case 8:
    name="Seek_Time_Performance";
    break;
  case 9:
    name="Power_On_Hours";
    break;
  case 10:
    name="Spin_Retry_Count";
    break;
  case 11:
    name="Calibration_Retry_Count";
    break;
  case 12:
    name="Power_Cycle_Count";
    break;
  case 13:
    name="Read_Soft_Error_Rate";
    break;
  case 191:
    name="G-Sense_Error_Rate";
    break;
  case 192:
    name="Power-Off_Retract_Count";
    break;
  case 193:
    name="Load_Cycle_Count";
    break;
  case 194:
    name="Temperature_Centigrade";
    break;
  case 195:
    name="Hardware_ECC_Recovered";
    break;
  case 196:
    name="Reallocated_Event_Count";
    break;
  case 197:
    name="Current_Pending_Sector";
    break;
  case 198:
    name="Offline_Uncorrectable";
    break;
  case 199:
    name="UDMA_CRC_Error_Count";
    break;
  case 220:
    name="Disk_Shift";
    break;
  case 221:
    name="G-Sense_Error_Rate";
    break;
  case 222:
    name="Loaded_Hours";
    break;
  case 223:
    name="Load_Retry_Count";
    break;
  case 224:
    name="Load_Friction";
    break;
  case 225:
    name="Load_Cycle_Count";
    break;
  case 226:
    name="Load-in_Time";
    break;
  case 227:
    name="Torq-amp_Count";
    break;
  case 228:
    name="Power-off_Retract_Count";
    break;
  default:
    name="Unknown_Attribute";
    break;
  }
  pout("%3d %-23s",id,name);
}	

/****
 Called by smartctl to access ataprint  
**/


// Initialize to zero just in case some SMART routines don't work
struct hd_driveid drive;
struct ata_smart_values smartval;
struct ata_smart_thresholds smartthres;
struct ata_smart_errorlog smarterror;
struct ata_smart_selftestlog smartselftest;

int ataPrintMain (int fd){
  int timewait,code;
  int returnval=0;
  
  // Start by getting Drive ID information.  We need this, to know if SMART is supported.
  if (ataReadHDIdentity(fd,&drive)){
    pout("Smartctl: Hard Drive Read Identity Failed\n\n");
    returnval|=FAILID;
  }
  
  // Print most drive identity information if requested
  if (driveinfo){
    pout("=== START OF INFORMATION SECTION ===\n");
    ataPrintDriveInfo(drive);
  }
  
  // now check if drive supports SMART; otherwise time to exit
  if (!ataSmartSupport(drive)){
    pout("SMART support is: Unavailable - device lacks SMART capability.\n");
    pout("                  Checking to be sure by trying SMART ENABLE command.\n");
    if (ataEnableSmart(fd)){
      pout("                  No SMART functionality found. Sorry.\n");
      return returnval|FAILSMART;
    }
    else
      pout("                  SMART appears to work.  Continuing.\n"); 
    if (!driveinfo) pout("\n");
  }
  
  // Now print remaining drive info: is SMART enabled?    
  if (driveinfo){
    pout("SMART support is: Available - device has SMART capability.\n");
    if (ataDoesSmartWork(fd))
      pout("SMART support is: Enabled\n");
    else
      pout("SMART support is: Disabled\n");
    pout("\n");
  }

  
  // START OF THE ENABLE/DISABLE SECTION OF THE CODE
  if (smartenable || smartdisable || 
      smartautosaveenable || smartautosavedisable || 
      smartautoofflineenable || smartautoofflinedisable)
    pout("=== START OF ENABLE/DISABLE COMMANDS SECTION ===\n");
  
  // Enable/Disable SMART commands
  if (smartenable){
    if (ataEnableSmart(fd)) {
      pout("Smartctl: SMART Enable Failed.\n\n");
      returnval|=FAILSMART;
    }
    else
      pout("SMART Enabled.\n");
  }
  
  // From here on, every command requires that SMART be enabled...
  if (!ataDoesSmartWork(fd)) {
    pout("SMART Disabled. Use option -%c to enable it.\n", SMARTENABLE );
    return returnval;
  }
  
  // Turn off SMART on device
  if (smartdisable){    
    if (ataDisableSmart(fd)) {
      pout( "Smartctl: SMART Disable Failed.\n\n");
      returnval|=FAILSMART;
    }
    pout("SMART Disabled. Use option -%c to enable it.\n",SMARTENABLE);
    return returnval;		
  }
  
  // Let's ALWAYS issue this command to get the SMART status
  code=ataSmartStatus2(fd);
  if (code==-1)
    returnval|=FAILSMART;
  
  // Enable/Disable Auto-save attributes
  if (smartautosaveenable){
    if (ataEnableAutoSave(fd)){
      pout( "Smartctl: SMART Enable Attribute Autosave Failed.\n\n");
      returnval|=FAILSMART;
    }
    else
      pout("SMART Attribute Autosave Enabled.\n");
  }
  if (smartautosavedisable){
    if (ataDisableAutoSave(fd)){
      pout( "Smartctl: SMART Disable Attribute Autosave Failed.\n\n");
      returnval|=FAILSMART;
    }
    else
      pout("SMART Attribute Autosave Disabled.\n");
  }
  
  // for everything else read values and thresholds are needed
  if (ataReadSmartValues(fd, &smartval)){
    pout("Smartctl: SMART Read Values failed.\n\n");
    returnval|=FAILSMART;
  }
  if (ataReadSmartThresholds(fd, &smartthres)){
    pout("Smartctl: SMART Read Thresholds failed.\n\n");
    returnval|=FAILSMART;
  }

  // Enable/Disable Off-line testing
  if (smartautoofflineenable){
    if (!isSupportAutomaticTimer(smartval)){
      pout("Warning: device does not support SMART Automatic Timers.\n\n");
    }
    if (ataEnableAutoOffline(fd)){
      pout( "Smartctl: SMART Enable Automatic Offline Failed.\n\n");
      returnval|=FAILSMART;
    }
    else
      pout ("SMART Automatic Offline Testing Enabled every four hours.\n");
  }
  if (smartautoofflinedisable){
    if (!isSupportAutomaticTimer(smartval)){
      pout("Warning: device does not support SMART Automatic Timers.\n\n");
    }
    if (ataDisableAutoOffline(fd)){
      pout("Smartctl: SMART Disable Automatic Offline Failed.\n\n");
      returnval|=FAILSMART;
    }
    else
      pout("SMART Automatic Offline Testing Disabled.\n");
  }

  // all this for a newline!
  if (smartenable || smartdisable || 
      smartautosaveenable || smartautosavedisable || 
      smartautoofflineenable || smartautoofflinedisable)
    pout("\n");

  // START OF READ-ONLY OPTIONS APART FROM -p and -i
  if (checksmart || generalsmartvalues || smartvendorattrib || smarterrorlog || smartselftestlog)
    pout("=== START OF READ SMART DATA SECTION ===\n");
  
  // Check SMART status (use previously returned value)
  if (checksmart){
    if (code) {
      QUIETON;
      pout("SMART overall-health self-assessment test result: FAILED!\n"
	     "Drive failure expected in less than 24 hours. SAVE ALL DATA.\n");
      QUIETOFF;
      if (ataCheckSmart(smartval, smartthres,1)){
	QUIETON;
	returnval|=FAILATTR;
	pout("Failed Attributes:\n");
	PrintSmartAttribWithThres(smartval, smartthres,1);
      }
      else
	pout("No failed Attributes found.\n\n");   
      returnval|=FAILSTATUS;
      QUIETOFF;
    }
    else {
      pout("SMART overall-health self-assessment test result: PASSED\n");
      if (ataCheckSmart(smartval, smartthres,0)){
	QUIETON;
	pout("Please note the following marginal attributes:\n");
	PrintSmartAttribWithThres(smartval, smartthres,2);
	returnval|=FAILAGE;
      }
      else
	pout("\n");
    }
    QUIETOFF;
  }
  
  // Print general SMART values
  if (generalsmartvalues)
    ataPrintGeneralSmartValues(smartval); 
  
  // Print vendor-specific attributes
  if (smartvendorattrib){
    QUIETON;
    PrintSmartAttribWithThres(smartval, smartthres,quietmode?2:0);
    QUIETOFF;
  }
  
  // Print SMART error log
  if (smarterrorlog){
    if (!isSmartErrorLogCapable(smartval))
      pout("Warning: device does not support Error Logging\n");
    if (ataReadErrorLog(fd, &smarterror)){
      pout("Smartctl: SMART Errorlog Read Failed\n");
      returnval|=FAILSMART;
    }
    else {
      // turn on quiet mode inside this
      ataPrintSmartErrorlog(smarterror);
      QUIETOFF;
    }
  }
  
  // Print SMART self-test log
  if (smartselftestlog){
    if (!isSmartErrorLogCapable(smartval))
      pout("Warning: device does not support Self Test Logging\n");
    else {
      if(ataReadSelfTestLog(fd, &smartselftest)){
	pout("Smartctl: SMART Self Test Log Read Failed\n");
	returnval|=FAILSMART;
      }
      else {
	QUIETON;
	if (ataPrintSmartSelfTestlog(smartselftest,!quietmode))
	  returnval|=FAILLOG;
	QUIETOFF;
	pout("\n");
      }
    } 
  }
  
  // START OF THE TESTING SECTION OF THE CODE.  IF NO TESTING, RETURN
  if (testcase==-1)
    return returnval;
  
  pout("=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\n");
  // if doing a self-test, be sure it's supported by the hardware
  if (testcase==OFFLINE_FULL_SCAN &&  !isSupportExecuteOfflineImmediate(smartval))
    pout("Warning: device does not support Execute Off-Line Immediate function.\n\n");
  else if (!isSupportSelfTest(smartval))
    pout ("Warning: device does not support Self-Test functions.\n\n");
  
  // Now do the test
  if (ataSmartTest(fd, testcase))
    return returnval|=FAILSMART;
  
  // Tell user how long test will take to complete  
  if ((timewait=TestTime(smartval,testcase))){ 
    pout ("Please wait %d %s for test to complete.\n",
	    timewait, testcase==OFFLINE_FULL_SCAN?"seconds":"minutes");
    
    if (testcase!=SHORT_CAPTIVE_SELF_TEST && testcase!=EXTEND_CAPTIVE_SELF_TEST)
      pout ("Use smartctl -%c to abort test.\n", SMARTSELFTESTABORT);	
  }    
  return returnval;
}