Skip to content
Snippets Groups Projects
Select Git revision
  • 277354f6e45db6ce489eec207c42478c4706da6c
  • master default protected
2 results

test_parser.py

Blame
  • os_linux.cpp 23.87 KiB
    /* 
     *  os_linux.c
     * 
     * Home page of code is: http://smartmontools.sourceforge.net
     *
     * Copyright (C) 2003 Bruce Allen <smartmontools-support@lists.sourceforge.net>
     * Copyright (C) 2003 Doug Gilbert <dougg@torque.net>
     *
     *  Parts of this file are derived from code that was
     *
     *  Written By: Adam Radford <linux@3ware.com>
     *  Modifications By: Joel Jacobson <linux@3ware.com>
     *  		     Arnaldo Carvalho de Melo <acme@conectiva.com.br>
     *                    Brad Strand <linux@3ware.com>
     *
     *  Copyright (C) 1999-2003 3ware Inc.
     *
     *  Kernel compatablity By:	Andre Hedrick <andre@suse.com>
     *  Non-Copyright (C) 2000	Andre Hedrick <andre@suse.com>
     *
     * Other ars of this file are derived from code that was
     * 
     * Copyright (C) 1999-2000 Michael Cornwell <cornwell@acm.org>
     * Copyright (C) 2000 Andre Hedrick <andre@linux-ide.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/
     * 
     */
    
    // This file contains the linux-specific IOCTL parts of
    // smartmontools. It includes one interface routine for ATA devices,
    // one for SCSI devices, and one for ATA devices behind escalade
    // controllers.
    
    #include <string.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <scsi/scsi_ioctl.h>
    
    #include "atacmds.h"
    #include "config.h"
    #include "os_linux.h"
    #include "scsicmds.h"
    #include "smartd.h"
    #include "utility.h"
    
    const char *os_XXXX_c_cvsid="$Id: os_linux.cpp,v 1.9 2003/10/14 14:22:44 ballen4705 Exp $" \
    ATACMDS_H_CVSID CONFIG_H_CVSID OS_XXXX_H_CVSID SCSICMDS_H_CVSID SMARTD_H_CVSID UTILITY_H_CVSID;
    
    // to hold onto exit code for atexit routine
    extern int exitstatus;
    
    // keep track of memory allocated/deallocated
    extern long long bytes;
    
    // equivalent to open(path, flags)
    int deviceopen(const char *pathname, char *type){
      if (!strcmp(type,"SCSI")) 
        return open(pathname, O_RDWR | O_NONBLOCK);
      else if (!strcmp(type,"ATA")) 
        return open(pathname, O_RDONLY | O_NONBLOCK);
      else
        return -1;
    }
    
    // equivalent to close(file descriptor)
    int deviceclose(int fd){
      return close(fd);
    }
    
    // makes a list of device names to scan, for either ATA or SCSI
    // devices.  Return -1 if no memory remaining, else the number of
    // devices on the list, which can be >=0.
    int make_device_names (char*** devlist, const char* name) {
      char* base=NULL;
      char** tmp=NULL;
      int i, n=0;
      
      // set correct first name (base name) for ATA or SCSI devices
      if (!strcmp(name,"SCSI")) {
        n = MAXSCSIDEVICES;
        base = CustomStrDup("/dev/sda",1,__LINE__);
      }
      else if (!strcmp(name,"ATA")) {
        n = MAXATADEVICES;
        base = CustomStrDup("/dev/hda",1,__LINE__);
      }
      
      // bad type or no devices
      if (!n)
        return 0;
      
      // allocate storage for the list of device names that we'll be
      // making, and store first name
      if (!(tmp = *devlist = (char **)calloc(n, sizeof(char *))))
        return -1;
      
      bytes += n*sizeof(char*);
      tmp[0]=base;
      
      // make device names by incrementing the last letter of the base
      // name alphabetically a->b->c...
      for (i = 1; i < n; i++) {
        tmp[i] =  CustomStrDup(tmp[i-1],1,__LINE__);
        tmp[i][7]++; 
      }
      
      return n;
    }
    
    
    // PURPOSE
    //   This is an interface routine meant to isolate the OS dependent
    //   parts of the code, and to provide a debugging interface.  Each
    //   different port and OS needs to provide it's own interface.  This
    //   is the linux one.
    // DETAILED DESCRIPTION OF ARGUMENTS
    //   device: is the file descriptor provided by open()
    //   command: defines the different operations.
    //   select: additional input data if needed (which log, which type of
    //           self-test).
    //   data:   location to write output data, if needed (512 bytes).
    //   Note: not all commands use all arguments.
    // RETURN VALUES
    //  -1 if the command failed
    //   0 if the command succeeded,
    //   STATUS_CHECK routine: 
    //  -1 if the command failed
    //   0 if the command succeeded and disk SMART status is "OK"
    //   1 if the command succeeded and disk SMART status is "FAILING"
    
    
    // huge value of buffer size needed because HDIO_DRIVE_CMD assumes
    // that buff[3] is the data size.  Since the SMART_AUTOSAVE and
    // SMART_AUTO_OFFLINE use values of 0xf1 and 0xf8 we need the space.
    // Otherwise a 4+512 byte buffer would be enough.
    #define STRANGE_BUFFER_LENGTH (4+512*0xf8)
    
    int ata_command_interface(int device, smart_command_set command, int select, char *data){
      unsigned char buff[STRANGE_BUFFER_LENGTH];
      int retval, copydata=0;
    
      // The two linux IOCTL's that we use.  These MUST be consistent with
      // the same quantities in the kernel include file linux/hdreg.h
      const int HDIO_DRIVE_TASK = 0x031e;
      const int HDIO_DRIVE_CMD  = 0x031f;
      const int HDIO_DRIVE_CMD_OFFSET = 4;
    
      // See struct hd_drive_cmd_hdr in hdreg.h
      // buff[0]: ATA COMMAND CODE REGISTER
      // buff[1]: ATA SECTOR NUMBER REGISTER == LBA LOW REGISTER
      // buff[2]: ATA FEATURES REGISTER
      // buff[3]: ATA SECTOR COUNT REGISTER
      
      // clear out buff.  Large enough for HDIO_DRIVE_CMD (4+512 bytes)
      memset(buff, 0, STRANGE_BUFFER_LENGTH);
    
      buff[0]=WIN_SMART;
      switch (command){
      case READ_VALUES:
        buff[2]=SMART_READ_VALUES;
        copydata=buff[3]=1;
        break;
      case READ_THRESHOLDS:
        buff[2]=SMART_READ_THRESHOLDS;
        copydata=buff[1]=buff[3]=1;
        break;
      case READ_LOG:
        buff[2]=SMART_READ_LOG_SECTOR;
        buff[1]=select;
        copydata=buff[3]=1;
        break;
      case IDENTIFY:
        buff[0]=WIN_IDENTIFY;
        copydata=buff[3]=1;
        break;
      case PIDENTIFY:
        buff[0]=WIN_PIDENTIFY;
        copydata=buff[3]=1;
        break;
      case ENABLE:
        buff[2]=SMART_ENABLE;
        buff[1]=1;
        break;
      case DISABLE:
        buff[2]=SMART_DISABLE;
        buff[1]=1;
        break;
      case STATUS:
        // this command only says if SMART is working.  It could be
        // replaced with STATUS_CHECK below.
        buff[2]=SMART_STATUS;
        break;
      case AUTO_OFFLINE:
        buff[2]=SMART_AUTO_OFFLINE;
        buff[3]=select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
        break;
      case AUTOSAVE:
        buff[2]=SMART_AUTOSAVE;
        buff[3]=select;   // YET NOTE - THIS IS A NON-DATA COMMAND!!
        break;
      case IMMEDIATE_OFFLINE:
        buff[2]=SMART_IMMEDIATE_OFFLINE;
        buff[1]=select;
        break;
      case STATUS_CHECK:
        // This command uses HDIO_DRIVE_TASK and has different syntax than
        // the other commands.
        buff[1]=SMART_STATUS;
        break;
      default:
        pout("Unrecognized command %d in linux_ata_command_interface()\n"
    	 "Please contact " PACKAGE_BUGREPORT "\n", command);
        errno=ENOSYS;
        return -1;
      }
      
      // There are two different types of ioctls().  The HDIO_DRIVE_TASK
      // one is this:
      if (command==STATUS_CHECK){
    
        // NOT DOCUMENTED in /usr/src/linux/include/linux/hdreg.h. You
        // have to read the IDE driver source code.  Sigh.
        // buff[0]: ATA COMMAND CODE REGISTER
        // buff[1]: ATA FEATURES REGISTER
        // buff[2]: ATA SECTOR_COUNT
        // buff[3]: ATA SECTOR NUMBER
        // buff[4]: ATA CYL LO REGISTER
        // buff[5]: ATA CYL HI REGISTER
        // buff[6]: ATA DEVICE HEAD
    
        unsigned const char normal_lo=0x4f, normal_hi=0xc2;
        unsigned const char failed_lo=0xf4, failed_hi=0x2c;
        buff[4]=normal_lo;
        buff[5]=normal_hi;
        
        if ((retval=ioctl(device, HDIO_DRIVE_TASK, buff)))
          return -1;
        
        // Cyl low and Cyl high unchanged means "Good SMART status"
        if (buff[4]==normal_lo && buff[5]==normal_hi)
          return 0;
        
        // These values mean "Bad SMART status"
        if (buff[4]==failed_lo && buff[5]==failed_hi)
          return 1;
        
        // We haven't gotten output that makes sense; print out some debugging info
        syserror("Error SMART Status command failed");
        pout("Please get assistance from %s\n",PROJECTHOME);
        pout("Register values returned from SMART Status command are:\n");
        pout("CMD=0x%02x\n",(int)buff[0]);
        pout("FR =0x%02x\n",(int)buff[1]);
        pout("NS =0x%02x\n",(int)buff[2]);
        pout("SC =0x%02x\n",(int)buff[3]);
        pout("CL =0x%02x\n",(int)buff[4]);
        pout("CH =0x%02x\n",(int)buff[5]);
        pout("SEL=0x%02x\n",(int)buff[6]);
        return -1;   
      }
      
    #if (1)
      // Note to people doing ports to other OSes -- don't worry about
      // this block -- you can ignore it.  I have put it here because
      // under linux when you do IDENTIFY DEVICE to a packet device, it
      // generates an ugly kernel syslog error message.  This is harmless
      // but frightens users.  So this block is an attempt to detect
      // packet devices and make IDENTIFY DEVICE fail "nicely" without a
      // syslog error message.
    
      // This doesn't always work, since ATA-3 did not have a separate
      // IDENTIFY PACKET DEVICE command.  In fact while ATAPI was
      // introduced in ATA-3, IDENTIFY PACKET DEVICE only appeared in ATA
      // 4 revision 2.  Anyway, this doesn't matter, since packet devices
      // don't support SMART anyway.
    
      // for IDENTIFY command, check if device is a packet device, and if
      // it is, then we simulate a 'clean' error without calling the
      // lower-level ioctl that will generate a kernel error log message.
      if (command==IDENTIFY){
        unsigned char junk[512];
        const int HDIO_GET_IDENTITY=0x030d;
        if (!ioctl(device, HDIO_GET_IDENTITY, junk) && (junk[1]>>7)) {
          // device is PACKET device
          errno = 5;
          return -1;
        }
      }
    #endif
      
      // We are now doing the HDIO_DRIVE_CMD type ioctl.
      if ((retval=ioctl(device, HDIO_DRIVE_CMD, buff)))
        return -1;
    
      // if the command returns data, copy it back
      if (copydata)
        memcpy(data, buff+HDIO_DRIVE_CMD_OFFSET, 512);
      
      return 0; 
    }
    
    /* SCSI command transmission interface function, implementation is OS
     * specific. Returns 0 if SCSI command successfully launched and response
     * received, else returns a negative errno value */
    
    /* Linux specific code, FreeBSD could conditionally compile in CAM stuff 
     * instead of this. */
    
    /* #include <scsi/scsi.h>       bypass for now */
    /* #include <scsi/scsi_ioctl.h> bypass for now */
    
    #define MAX_DXFER_LEN 1024      /* can be increased if necessary */
    #define SEND_IOCTL_RESP_SENSE_LEN 16    /* ioctl limitation */
    #define DRIVER_SENSE  0x8       /* alternate CHECK CONDITION indication */
    
    #ifndef SCSI_IOCTL_SEND_COMMAND
    #define SCSI_IOCTL_SEND_COMMAND 1
    #endif
    #ifndef SCSI_IOCTL_TEST_UNIT_READY
    #define SCSI_IOCTL_TEST_UNIT_READY 2
    #endif
    
    struct linux_ioctl_send_command
    {
        int inbufsize;
        int outbufsize;
        UINT8 buff[MAX_DXFER_LEN + 16];
    };
    
    /* The Linux SCSI_IOCTL_SEND_COMMAND ioctl is primitive and it doesn't 
     * support: CDB length (guesses it from opcode), resid and timeout.
     * Patches in Linux 2.4.21 and 2.5.70 to extend SEND DIAGNOSTIC timeout
     * to 2 hours in order to allow long foreground extended self tests. */
    int do_scsi_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report)
    {
        struct linux_ioctl_send_command wrk;
        int status, buff_offset;
        size_t len;
    
        memcpy(wrk.buff, iop->cmnd, iop->cmnd_len);
        buff_offset = iop->cmnd_len;
        if (report > 0) {
            int k;
            const unsigned char * ucp = iop->cmnd;
            const char * np;
    
            np = scsi_get_opcode_name(ucp[0]);
            pout(" [%s: ", np ? np : "<unknown opcode>");
            for (k = 0; k < iop->cmnd_len; ++k)
                pout("%02x ", ucp[k]);
            if ((report > 1) && 
                (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) {
                int trunc = (iop->dxfer_len > 256) ? 1 : 0;
    
                pout("]\n  Outgoing data, len=%d%s:\n", (int)iop->dxfer_len,
                     (trunc ? " [only first 256 bytes shown]" : ""));
                dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
            }
            else
                pout("]");
        }
        switch (iop->dxfer_dir) {
            case DXFER_NONE:
                wrk.inbufsize = 0;
                wrk.outbufsize = 0;
                break;
            case DXFER_FROM_DEVICE:
                wrk.inbufsize = 0;
                if (iop->dxfer_len > MAX_DXFER_LEN)
                    return -EINVAL;
                wrk.outbufsize = iop->dxfer_len;
                break;
            case DXFER_TO_DEVICE:
                if (iop->dxfer_len > MAX_DXFER_LEN)
                    return -EINVAL;
                memcpy(wrk.buff + buff_offset, iop->dxferp, iop->dxfer_len);
                wrk.inbufsize = iop->dxfer_len;
                wrk.outbufsize = 0;
                break;
            default:
                pout("do_scsi_cmnd_io: bad dxfer_dir\n");
                return -EINVAL;
        }
        iop->resp_sense_len = 0;
        iop->scsi_status = 0;
        iop->resid = 0;
        status = ioctl(dev_fd, SCSI_IOCTL_SEND_COMMAND , &wrk);
        if (-1 == status) {
            if (report)
                pout("  status=-1, errno=%d [%s]\n", errno, strerror(errno));
            return -errno;
        }
        if (0 == status) {
            if (report > 0)
                pout("  status=0\n");
            if (DXFER_FROM_DEVICE == iop->dxfer_dir) {
                memcpy(iop->dxferp, wrk.buff, iop->dxfer_len);
                if (report > 1) {
                    int trunc = (iop->dxfer_len > 256) ? 1 : 0;
    
                    pout("  Incoming data, len=%d%s:\n", (int)iop->dxfer_len,
                         (trunc ? " [only first 256 bytes shown]" : ""));
                    dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1);
                }
            }
            return 0;
        }
        iop->scsi_status = status & 0x7e; /* bits 0 and 7 used to be for vendors */
        if (DRIVER_SENSE == ((status >> 24) & 0xf))
            iop->scsi_status = SCSI_STATUS_CHECK_CONDITION;
        len = (SEND_IOCTL_RESP_SENSE_LEN < iop->max_sense_len) ?
                    SEND_IOCTL_RESP_SENSE_LEN : iop->max_sense_len;
        if ((SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) && 
            iop->sensep && (len > 0)) {
            memcpy(iop->sensep, wrk.buff, len);
            iop->resp_sense_len = len;
            if (report > 1) {
                pout("  >>> Sense buffer, len=%d:\n", (int)len);
                dStrHex(wrk.buff, len , 1);
            }
        }
        if (report) {
            if (SCSI_STATUS_CHECK_CONDITION == iop->scsi_status) {
                pout("  status=%x: sense_key=%x asc=%x ascq=%x\n", status & 0xff,
                     wrk.buff[2] & 0xf, wrk.buff[12], wrk.buff[13]);
            }
            else
                pout("  status=0x%x\n", status);
        }
        if (iop->scsi_status > 0)
            return 0;
        else {
            if (report > 0)
                pout("  ioctl status=0x%x but scsi status=0, fail with EIO\n", 
                     status);
            return -EIO;      /* give up, assume no device there */
        }
    }
    
    void printwarning(smart_command_set command);
    
    // PURPOSE
    //   This is an interface routine meant to isolate the OS dependent
    //   parts of the code, and to provide a debugging interface.  Each
    //   different port and OS needs to provide it's own interface.  This
    //   is the linux interface to the 3ware 3w-xxxx driver.  It allows ATA
    //   commands to be passed through the SCSI driver.
    // DETAILED DESCRIPTION OF ARGUMENTS
    //   fd: is the file descriptor provided by open()
    //   disknum is the disk number (0 to 15) in the RAID array
    //   command: defines the different operations.
    //   select: additional input data if needed (which log, which type of
    //           self-test).
    //   data:   location to write output data, if needed (512 bytes).
    //   Note: not all commands use all arguments.
    // RETURN VALUES
    //  -1 if the command failed
    //   0 if the command succeeded,
    //   STATUS_CHECK routine: 
    //  -1 if the command failed
    //   0 if the command succeeded and disk SMART status is "OK"
    //   1 if the command succeeded and disk SMART status is "FAILING"
    
    int escalade_command_interface(int fd, int disknum, smart_command_set command, int select, char *data){
    
      // Structures for passing commands through 3Ware Escalade Linux Driver
      TW_Ioctl ioctlbuf;
      TW_Passthru passthru;
    
      // If command returns 512 bytes, set to 1, else 0
      int readdata=0;
    
      // Clear out the data structures
      memset (&ioctlbuf, 0, sizeof(TW_Ioctl));
      memset (&passthru, 0, sizeof(TW_Passthru));
    
      // Same for (almost) all commands - but some reset below
      passthru.byte0.opcode  = 0x11;
      passthru.request_id    = 0xFF;
      passthru.byte3.aport   = disknum;
      passthru.byte3.host_id = 0;
      passthru.status        = 0;           
      passthru.flags         = 0x1;
      passthru.drive_head    = 0x0;
      passthru.sector_num    = 0;
    
      // All SMART commands use this CL/CH signature.  These are magic
      // values from the ATA specifications.
      passthru.cylinder_lo = 0x4F;
      passthru.cylinder_hi = 0xC2;
      
      // SMART ATA COMMAND REGISTER value
      passthru.command = WIN_SMART;
      
      // Is this a command that returns 512 bytes?
      if (command == READ_VALUES ||
          command == READ_THRESHOLDS ||
          command == READ_LOG ||
          command == IDENTIFY) {
        readdata=1;
        passthru.byte0.sgloff = 0x5;
        passthru.size         = 0x7;
        passthru.param        = 0xD;
        passthru.sector_count = 0x1;
      }
      else {
        // Non data command -- but doesn't use large sector 
        // count register values.  passthru.param values are:
        // 0x00 - non data command without TFR write check,
        // 0x08 - non data command with TFR write check,
        passthru.byte0.sgloff = 0x0;
        passthru.size         = 0x5;
        passthru.param        = 0x8;
        passthru.sector_count = 0x0;
      }
      
      // Now set ATA registers depending upon command
      switch (command){
      case READ_VALUES:
        passthru.features = SMART_READ_VALUES;
        break;
      case READ_THRESHOLDS:
        passthru.features = SMART_READ_THRESHOLDS;
        break;
      case READ_LOG:
        passthru.features = SMART_READ_LOG_SECTOR;
        // log number to return
        passthru.sector_num  = select;
        break;
      case IDENTIFY:
        // ATA IDENTIFY DEVICE
        passthru.command     = 0xEc;
        passthru.features    = 0;
        passthru.cylinder_lo = 0;
        passthru.cylinder_hi = 0;
        break;
      case PIDENTIFY:
        // 3WARE controller can NOT have packet device internally
        pout("WARNING - NO DEVICE FOUND ON 3WARE CONTROLLER (disk %d)\n", disknum);
        errno=ENODEV;
        return -1;
      case ENABLE:
        passthru.features = SMART_ENABLE;
        break;
      case DISABLE:
        passthru.features = SMART_DISABLE;
        break;
      case AUTO_OFFLINE:
        passthru.features = SMART_AUTO_OFFLINE;
        // Enable or disable?
        passthru.sector_count = select;
        break;
      case AUTOSAVE:
        passthru.features = SMART_AUTOSAVE;
        // Enable or disable?
        passthru.sector_count = select;
        break;
      case IMMEDIATE_OFFLINE:
        passthru.features = SMART_IMMEDIATE_OFFLINE;
        // What test type to run?
        passthru.sector_num  = select;
        break;
      case STATUS_CHECK:
        passthru.features = SMART_STATUS;
        break;
      case STATUS:
        // This is JUST to see if SMART is enabled, by giving SMART status
        // command. But it doesn't say if status was good, or failing.
        // See below for the difference.
        passthru.features = SMART_STATUS;
        break;
      default:
        pout("Unrecognized command %d in linux_3ware_command_interface(disk %d)\n"
    	 "Please contact " PACKAGE_BUGREPORT "\n", command, disknum);
        errno=ENOSYS;
        return -1;
      }
    
      /* Copy the passthru command into the ioctl input buffer */
      memcpy(&ioctlbuf.input_data, &passthru, sizeof(TW_Passthru));
      ioctlbuf.cdb[0] = TW_IOCTL;
      ioctlbuf.opcode = TW_ATA_PASSTHRU;
    
      // CHECKME -- IS THIS RIGHT?? Even for non data I/O commands?
      ioctlbuf.input_length = 512;
      ioctlbuf.output_length = 512;
      
      /* Now send the command down through an ioctl() */
      if (ioctl(fd, SCSI_IOCTL_SEND_COMMAND, &ioctlbuf)) {
        // If error was provoked by driver, tell user how to fix it
        if ((command==AUTO_OFFLINE || command==AUTOSAVE) && select){
          printwarning(command);
          errno=ENOTSUP;
        }
        return -1;
      }
    
      // If this is a read data command, copy data to output buffer
      if (readdata){
        TW_Output *tw_output=(TW_Output *)&ioctlbuf;
        memcpy(data, tw_output->output_data, 512);
      }
      
      // We are finished with all commands except for STATUS_CHECK
      if (command!=STATUS_CHECK) {
        return 0;
      }
      else {
    
        // To find out if the SMART RETURN STATUS is good or failing, we
        // need to examine the values of the Cylinder Low and Cylinder
        // High Registers.
        
        TW_Output *tw_output=(TW_Output *)&ioctlbuf;
        TW_Passthru *tw_passthru_returned=(TW_Passthru *)&(tw_output->output_data);
        unsigned short cyl_lo=tw_passthru_returned->cylinder_lo;
        unsigned short cyl_hi=tw_passthru_returned->cylinder_hi;
        
        // If values in Cyl-LO and Cyl-HI are unchanged, SMART status is good.
        if (cyl_lo==0x4F && cyl_hi==0xC2)
          return 0;
        
        // If values in Cyl-LO and Cyl-HI are as follows, SMART status is FAIL
        if (cyl_lo==0xF4 && cyl_hi==0x2C)
          return 1;
        
        // Any other values mean that something has gone wrong with the command
        printwarning(command);
        errno=ENOSYS;
        return 0;
      }
    }
    
    // Utility function for printing warnings
    void printwarning(smart_command_set command){
      static int printed1=0,printed2=0,printed3=0;
      const char* message=
        "can not be passed through the 3ware 3w-xxxx driver.  This can be fixed by\n"
        "applying a simple 3w-xxxx driver patch that can be found here:\n"
        PROJECTHOME "\n"
        "Alternatively, upgrade your 3w-xxxx driver to version 1.02.00.037 or greater.\n\n";
    
      if (command==AUTO_OFFLINE && !printed1) {
        printed1=1;
        pout("The SMART AUTO-OFFLINE ENABLE command (smartmontools -o on option/Directive)\n%s", message);
      } 
      else if (command==AUTOSAVE && !printed2) {
        printed2=1;
        pout("The SMART AUTOSAVE ENABLE command (smartmontools -S on option/Directive)\n%s", message);
      }
      else if (command==STATUS_CHECK && !printed3) {
        printed3=1;
        pout("The SMART RETURN STATUS return value (smartmontools -H option/Directive)\n%s", message);
      }
      
      return;
    }
    
    // Guess device type (ata or scsi) based on device name (Linux
    // specific) SCSI device name in linux can be sd, sr, scd, st, nst,
    // osst, nosst and sg.
    static const char * lin_dev_prefix = "/dev/";
    static const char * lin_dev_ata_disk_plus = "h";
    static const char * lin_dev_ata_devfs_disk_plus = "ide/";
    static const char * lin_dev_scsi_devfs_disk_plus = "scsi/";
    static const char * lin_dev_scsi_disk_plus = "s";
    static const char * lin_dev_scsi_tape1 = "ns";
    static const char * lin_dev_scsi_tape2 = "os";
    static const char * lin_dev_scsi_tape3 = "nos";
    
    int guess_device_type(const char * dev_name) {
      int len;
      int dev_prefix_len = strlen(lin_dev_prefix);
      
      // if dev_name null, or string length zero
      if (!dev_name || !(len = strlen(dev_name)))
        return GUESS_DEVTYPE_DONT_KNOW;
      
      // Remove the leading /dev/... if it's there
      if (!strncmp(lin_dev_prefix, dev_name, dev_prefix_len)) {
        if (len <= dev_prefix_len)
          // if nothing else in the string, unrecognized
          return GUESS_DEVTYPE_DONT_KNOW;
        // else advance pointer to following characters
        dev_name += dev_prefix_len;
      }
      
      // form /dev/h* or h*
      if (!strncmp(lin_dev_ata_disk_plus, dev_name,
    	       strlen(lin_dev_ata_disk_plus)))
        return GUESS_DEVTYPE_ATA;
      
      // form /dev/ide/* or ide/*
      if (!strncmp(lin_dev_ata_devfs_disk_plus, dev_name,
    	       strlen(lin_dev_ata_devfs_disk_plus)))
        return GUESS_DEVTYPE_ATA;
    
      // form /dev/s* or s*
      if (!strncmp(lin_dev_scsi_disk_plus, dev_name,
    	       strlen(lin_dev_scsi_disk_plus)))
        return GUESS_DEVTYPE_SCSI;
    
      // form /dev/scsi/* or scsi/*
      if (!strncmp(lin_dev_scsi_devfs_disk_plus, dev_name,
    	       strlen(lin_dev_scsi_devfs_disk_plus)))
        return GUESS_DEVTYPE_SCSI;
      
      // form /dev/ns* or ns*
      if (!strncmp(lin_dev_scsi_tape1, dev_name,
    	       strlen(lin_dev_scsi_tape1)))
        return GUESS_DEVTYPE_SCSI;
      
      // form /dev/os* or os*
      if (!strncmp(lin_dev_scsi_tape2, dev_name,
    	       strlen(lin_dev_scsi_tape2)))
        return GUESS_DEVTYPE_SCSI;
      
      // form /dev/nos* or nos*
      if (!strncmp(lin_dev_scsi_tape3, dev_name,
    	       strlen(lin_dev_scsi_tape3)))
        return GUESS_DEVTYPE_SCSI;
      
      // we failed to recognize any of the forms
      return GUESS_DEVTYPE_DONT_KNOW;
    }