]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - os_linux.cpp
Imported Upstream version 5.39.1+svn3077
[mirror_smartmontools-debian.git] / os_linux.cpp
index a2866eb9613f1a68e5b180da637c7fd9bee6c941..16722eae77381b2ec55108388dd8a9c08624cf35 100644 (file)
@@ -3,12 +3,12 @@
  *
  * Home page of code is: http://smartmontools.sourceforge.net
  *
- * Copyright (C) 2003-8 Bruce Allen <smartmontools-support@lists.sourceforge.net>
- * Copyright (C) 2003-8 Doug Gilbert <dougg@torque.net>
- * Copyright (C) 2008   Hank Wu <hank@areca.com.tw>
- * Copyright (C) 2008   Oliver Bock <brevilo@users.sourceforge.net>
- * Copyright (C) 2008-9 Christian Franke <smartmontools-support@lists.sourceforge.net>
- * Copyright (C) 2008   Jordan Hargrave <jordan_hargrave@dell.com>
+ * Copyright (C) 2003-10 Bruce Allen <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2003-10 Doug Gilbert <dougg@torque.net>
+ * Copyright (C) 2008    Hank Wu <hank@areca.com.tw>
+ * Copyright (C) 2008    Oliver Bock <brevilo@users.sourceforge.net>
+ * Copyright (C) 2008-10 Christian Franke <smartmontools-support@lists.sourceforge.net>
+ * Copyright (C) 2008    Jordan Hargrave <jordan_hargrave@dell.com>
  *
  *  Parts of this file are derived from code that was
  *
@@ -90,7 +90,7 @@
 
 #define ARGUSED(x) ((void)(x))
 
-const char *os_XXXX_c_cvsid="$Id: os_linux.cpp 2879 2009-08-29 17:19:00Z chrfranke $" \
+const char *os_XXXX_c_cvsid="$Id: os_linux.cpp 3076 2010-03-12 22:23:08Z chrfranke $" \
 ATACMDS_H_CVSID CONFIG_H_CVSID INT64_H_CVSID OS_LINUX_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
 
 /* for passing global control variables */
@@ -493,6 +493,7 @@ int linux_ata_device::ata_command_interface(smart_command_set command, int selec
 #define SG_IO_RESP_SENSE_LEN 64 /* large enough see buffer */
 #define LSCSI_DRIVER_MASK  0xf /* mask out "suggestions" */
 #define LSCSI_DRIVER_SENSE  0x8 /* alternate CHECK CONDITION indication */
+#define LSCSI_DID_ERROR 0x7 /* Need to work around aacraid driver quirk */
 #define LSCSI_DRIVER_TIMEOUT  0x6
 #define LSCSI_DID_TIME_OUT  0x3
 #define LSCSI_DID_BUS_BUSY  0x2
@@ -617,7 +618,10 @@ static int sg_io_cmnd_io(int dev_fd, struct scsi_cmnd_io * iop, int report,
                 (LSCSI_DID_TIME_OUT == io_hdr.host_status))
                 return -ETIMEDOUT;
             else
-                return -EIO;    /* catch all */
+               /* Check for DID_ERROR - workaround for aacraid driver quirk */
+               if (LSCSI_DID_ERROR != io_hdr.host_status) {
+                       return -EIO; /* catch all if not DID_ERR */
+               }
         }
         if (0 != masked_driver_status) {
             if (LSCSI_DRIVER_TIMEOUT == masked_driver_status)
@@ -827,17 +831,24 @@ class linux_scsi_device
   public /*extends*/ linux_smart_device
 {
 public:
-  linux_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type);
+  linux_scsi_device(smart_interface * intf, const char * dev_name,
+                    const char * req_type, bool scanning = false);
 
   virtual smart_device * autodetect_open();
 
   virtual bool scsi_pass_through(scsi_cmnd_io * iop);
+
+private:
+  bool m_scanning; ///< true if created within scan_smart_devices
 };
 
 linux_scsi_device::linux_scsi_device(smart_interface * intf,
-  const char * dev_name, const char * req_type)
+  const char * dev_name, const char * req_type, bool scanning /*= false*/)
 : smart_device(intf, dev_name, "scsi", req_type),
-  linux_smart_device(O_RDWR | O_NONBLOCK, O_RDONLY | O_NONBLOCK)
+  // If opened with O_RDWR, a SATA disk in standby mode
+  // may spin-up after device close().
+  linux_smart_device(O_RDONLY | O_NONBLOCK),
+  m_scanning(scanning)
 {
 }
 
@@ -902,6 +913,8 @@ linux_megaraid_device::~linux_megaraid_device() throw()
 
 smart_device * linux_megaraid_device::autodetect_open()
 {
+  int report = con->reportscsiioctl; 
+
   // Open device
   if (!open())
     return this;
@@ -924,22 +937,17 @@ smart_device * linux_megaraid_device::autodetect_open()
   if (len < 36)
       return this;
 
-  printf("Got MegaRAID inquiry.. %s\n", req_buff+8);
+  if (report)
+    printf("Got MegaRAID inquiry.. %s\n", req_buff+8);
 
   // Use INQUIRY to detect type
-  smart_device * newdev = 0;
-  try {
+  {
     // SAT or USB ?
-    newdev = smi()->autodetect_sat_device(this, req_buff, len);
+    ata_device * newdev = smi()->autodetect_sat_device(this, req_buff, len);
     if (newdev)
       // NOTE: 'this' is now owned by '*newdev'
       return newdev;
   }
-  catch (...) {
-    // Cleanup if exception occurs after newdev was allocated
-    delete newdev;
-    throw;
-  }
 
   // Nothing special found
   return this;
@@ -951,6 +959,7 @@ bool linux_megaraid_device::open()
   char line[128];
   int   mjr, n1;
   FILE *fp;
+  int report = con->reportscsiioctl; 
 
   if (!linux_smart_device::open())
     return false;
@@ -963,7 +972,7 @@ bool linux_megaraid_device::open()
   else if (ioctl(get_fd(), SCSI_IOCTL_GET_BUS_NUMBER, &m_hba) != 0) {
     int err = errno;
     linux_smart_device::close();
-    return set_err(err, "can't get hba");
+    return set_err(err, "can't get bus number");
   }
 
   /* Perform mknod of device ioctl node */
@@ -972,13 +981,15 @@ bool linux_megaraid_device::open()
        n1=0;
        if (sscanf(line, "%d megaraid_sas_ioctl%n", &mjr, &n1) == 1 && n1 == 22) {
           n1=mknod("/dev/megaraid_sas_ioctl_node", S_IFCHR, makedev(mjr, 0));
-          printf("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno);
+          if(report > 0)
+            printf("Creating /dev/megaraid_sas_ioctl_node = %d\n", n1 >= 0 ? 0 : errno);
           if (n1 >= 0 || errno == EEXIST)
              break;
        }
        else if (sscanf(line, "%d megadev%n", &mjr, &n1) == 1 && n1 == 11) {
           n1=mknod("/dev/megadev0", S_IFCHR, makedev(mjr, 0));
-          printf("Creating /dev/megadev0 = %d\n", n1 >= 0 ? 0 : errno);
+          if(report > 0)
+            printf("Creating /dev/megadev0 = %d\n", n1 >= 0 ? 0 : errno);
           if (n1 >= 0 || errno == EEXIST)
              break;
        }
@@ -1043,7 +1054,18 @@ bool linux_megaraid_device::scsi_pass_through(scsi_cmnd_io *iop)
   if (iop->cmnd[0] == 0x00)
     return true;
   if (iop->cmnd[0] == 0x85 && iop->cmnd[1] == 0x06) {
-    pout("Rejecting SMART/ATA command to controller\n");
+    if(report > 0)
+      pout("Rejecting SMART/ATA command to controller\n");
+    // Emulate SMART STATUS CHECK drive reply
+    // smartctl fail to work without this
+    if(iop->cmnd[2]==0x2c) {
+      iop->resp_sense_len=22; // copied from real response
+      iop->sensep[0]=0x72; // descriptor format
+      iop->sensep[7]=0x0e; // additional length
+      iop->sensep[8]=0x09; // description pointer
+      iop->sensep[17]=0x4f; // low cylinder GOOD smart status
+      iop->sensep[19]=0xc2; // high cylinder GOOD smart status
+    }
     return true;
   }
 
@@ -1530,7 +1552,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
       passthru->size++;
   }
   else
-    set_err(EINVAL);
+    return set_err(EINVAL);
 
   // Now send the command down through an ioctl()
   int ioctlreturn;
@@ -1594,7 +1616,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
   }
 
   // Return register values
-  {
+  if (passthru) {
     ata_out_regs_48bit & r = out.out_regs;
     r.error           = passthru->features;
     r.sector_count_16 = passthru->sector_count;
@@ -1607,7 +1629,7 @@ bool linux_escalade_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out
 
   // look for nonexistent devices/ports
   if (   in.in_regs.command == ATA_IDENTIFY_DEVICE
-      && !nonempty((unsigned char *)in.buffer, in.size)) {
+      && !nonempty(in.buffer, in.size)) {
     return set_err(ENODEV, "No drive on port %d", m_disknum);
   }
 
@@ -2631,8 +2653,13 @@ smart_device * linux_scsi_device::autodetect_open()
     return this;
 
   // No Autodetection if device type was specified by user
-  if (*get_req_type())
-    return this;
+  bool sat_only = false;
+  if (*get_req_type()) {
+    // Detect SAT if device object was created by scan_smart_devices().
+    if (!(m_scanning && !strcmp(get_req_type(), "sat")))
+      return this;
+    sat_only = true;
+  }
 
   // The code below is based on smartd.cpp:SCSIFilterKnown()
 
@@ -2653,12 +2680,17 @@ smart_device * linux_scsi_device::autodetect_open()
 
   int avail_len = req_buff[4] + 5;
   int len = (avail_len < req_len ? avail_len : req_len);
-  if (len < 36)
-      return this;
+  if (len < 36) {
+    if (sat_only) {
+      close();
+      set_err(EIO, "INQUIRY too short for SAT");
+    }
+    return this;
+  }
 
   // Use INQUIRY to detect type
-  smart_device * newdev = 0;
-  try {
+  if (!sat_only) {
+
     // 3ware ?
     if (!memcmp(req_buff + 8, "3ware", 5) || !memcmp(req_buff + 8, "AMCC", 4)) {
       close();
@@ -2667,29 +2699,40 @@ smart_device * linux_scsi_device::autodetect_open()
       return this;
     }
 
+    // DELL?
+    if (!memcmp(req_buff + 8, "DELL    PERC", 12) || !memcmp(req_buff + 8, "MegaRAID", 8)) {
+      close();
+      set_err(EINVAL, "DELL or MegaRaid controller, please try adding '-d megaraid,N'");
+      return this;
+    }
+
     // Marvell ?
     if (len >= 42 && !memcmp(req_buff + 36, "MVSATA", 6)) {
       //pout("Device %s: using '-d marvell' for ATA disk with Marvell driver\n", get_dev_name());
       close();
-      newdev = new linux_marvell_device(smi(), get_dev_name(), get_req_type());
+      smart_device_auto_ptr newdev(
+        new linux_marvell_device(smi(), get_dev_name(), get_req_type())
+      );
       newdev->open(); // TODO: Can possibly pass open fd
       delete this;
-      return newdev;
+      return newdev.release();
     }
+  }
 
-    // SAT or USB ?
-    newdev = smi()->autodetect_sat_device(this, req_buff, len);
+  // SAT or USB ?
+  {
+    smart_device * newdev = smi()->autodetect_sat_device(this, req_buff, len);
     if (newdev)
       // NOTE: 'this' is now owned by '*newdev'
       return newdev;
   }
-  catch (...) {
-    // Cleanup if exception occurs after newdev was allocated
-    delete newdev;
-    throw;
-  }
 
   // Nothing special found
+
+  if (sat_only) {
+    close();
+    set_err(EIO, "Not a SAT device");
+  }
   return this;
 }
 
@@ -2709,17 +2752,17 @@ static bool read_id(const std::string & path, unsigned short & id)
   return ok;
 }
 
-// Get USB bridge ID for "/dev/sdX"
-static bool get_usb_id(const char * path, unsigned short & vendor_id,
+// Get USB bridge ID for "sdX"
+static bool get_usb_id(const char * name, unsigned short & vendor_id,
                        unsigned short & product_id, unsigned short & version)
 {
-  // Only "/dev/sdX" supported
-  if (!(!strncmp(path, "/dev/sd", 7) && !strchr(path + 7, '/')))
+  // Only "sdX" supported
+  if (!(!strncmp(name, "sd", 2) && !strchr(name, '/')))
     return false;
 
   // Start search at dir referenced by symlink "/sys/block/sdX/device"
   // -> "/sys/devices/.../usb*/.../host*/target*/..."
-  std::string dir = strprintf("/sys/block/%s/device", path + 5);
+  std::string dir = strprintf("/sys/block/%s/device", name);
 
   // Stop search at "/sys/devices"
   struct stat st;
@@ -2755,7 +2798,7 @@ class linux_smart_interface
 : public /*implements*/ smart_interface
 {
 public:
-  virtual const char * get_app_examples(const char * appname);
+  virtual std::string get_app_examples(const char * appname);
 
   virtual bool scan_smart_devices(smart_device_list & devlist, const char * type,
     const char * pattern = 0);
@@ -2769,20 +2812,20 @@ protected:
 
   virtual smart_device * get_custom_smart_device(const char * name, const char * type);
 
-  virtual const char * get_valid_custom_dev_types_str();
+  virtual std::string get_valid_custom_dev_types_str();
 
 private:
   bool get_dev_list(smart_device_list & devlist, const char * pattern,
-    bool scan_ata, bool scan_scsi, const char * req_type);
+    bool scan_ata, bool scan_scsi, const char * req_type, bool autodetect);
 
   smart_device * missing_option(const char * opt);
 };
 
-const char * linux_smart_interface::get_app_examples(const char * appname)
+std::string linux_smart_interface::get_app_examples(const char * appname)
 {
   if (!strcmp(appname, "smartctl"))
     return smartctl_examples;
-  return 0;
+  return "";
 }
 
 
@@ -2790,7 +2833,8 @@ const char * linux_smart_interface::get_app_examples(const char * appname)
 // have device entries for devices that exist.  So if we get the equivalent of
 // ls /dev/hd[a-t], we have all the ATA devices on the system
 bool linux_smart_interface::get_dev_list(smart_device_list & devlist,
-  const char * pattern, bool scan_ata, bool scan_scsi, const char * req_type)
+  const char * pattern, bool scan_ata, bool scan_scsi,
+  const char * req_type, bool autodetect)
 {
   // Use glob to look for any directory entries matching the pattern
   glob_t globbuf;
@@ -2867,10 +2911,15 @@ bool linux_smart_interface::get_dev_list(smart_device_list & devlist,
 
     if (name) {
       // Found a name, add device to list.
-      if (is_scsi)
-        devlist.add(new linux_scsi_device(this, name, req_type));
+      smart_device * dev;
+      if (autodetect)
+        dev = autodetect_smart_device(name);
+      else if (is_scsi)
+        dev = new linux_scsi_device(this, name, req_type, true /*scanning*/);
       else
-        devlist.add(new linux_ata_device(this, name, req_type));
+        dev = new linux_ata_device(this, name, req_type);
+      if (dev) // autodetect_smart_device() may return nullptr.
+        devlist.push_back(dev);
     }
   }
 
@@ -2892,14 +2941,19 @@ bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist,
     type = "";
 
   bool scan_ata  = (!*type || !strcmp(type, "ata" ));
-  bool scan_scsi = (!*type || !strcmp(type, "scsi"));
+  // "sat" detection will be later handled in linux_scsi_device::autodetect_open()
+  bool scan_scsi = (!*type || !strcmp(type, "scsi") || !strcmp(type, "sat"));
   if (!(scan_ata || scan_scsi))
     return true;
 
   if (scan_ata)
-    get_dev_list(devlist,"/dev/hd[a-t]", true, false, type);
-  if (scan_scsi)
-    get_dev_list(devlist, "/dev/sd[a-z]", false, true, type);
+    get_dev_list(devlist, "/dev/hd[a-t]", true, false, type, false);
+  if (scan_scsi) {
+    bool autodetect = !*type; // Try USB autodetection if no type specifed
+    get_dev_list(devlist, "/dev/sd[a-z]", false, true, type, autodetect);
+    // Support up to 104 devices
+    get_dev_list(devlist, "/dev/sd[a-c][a-z]", false, true, type, autodetect);
+  }
 
   // if we found traditional links, we are done
   if (devlist.size() > 0)
@@ -2907,7 +2961,7 @@ bool linux_smart_interface::scan_smart_devices(smart_device_list & devlist,
 
   // else look for devfs entries without traditional links
   // TODO: Add udev support
-  return get_dev_list(devlist, "/dev/discs/disc*", scan_ata, scan_scsi, type);
+  return get_dev_list(devlist, "/dev/discs/disc*", scan_ata, scan_scsi, type, false);
 }
 
 ata_device * linux_smart_interface::get_ata_device(const char * name, const char * type)
@@ -2926,6 +2980,12 @@ smart_device * linux_smart_interface::missing_option(const char * opt)
   return 0;
 }
 
+// Return true if STR starts with PREFIX.
+static bool str_starts_with(const char * str, const char * prefix)
+{
+  return !strncmp(str, prefix, strlen(prefix));
+}
+
 // 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.
@@ -2952,8 +3012,15 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
   if (!dev_name || !(len = strlen(dev_name)))
     return 0;
 
+  // Dereference if /dev/disk/by-*/* symlink
+  char linkbuf[100];
+  if (   str_starts_with(dev_name, "/dev/disk/by-")
+      && readlink(dev_name, linkbuf, sizeof(linkbuf)) > 0
+      && str_starts_with(linkbuf, "../../")) {
+    dev_name = linkbuf + sizeof("../../")-1;
+  }
   // Remove the leading /dev/... if it's there
-  if (!strncmp(lin_dev_prefix, dev_name, dev_prefix_len)) {
+  else if (!strncmp(lin_dev_prefix, dev_name, dev_prefix_len)) {
     if (len <= dev_prefix_len)
       // if nothing else in the string, unrecognized
       return 0;
@@ -2977,7 +3044,7 @@ smart_device * linux_smart_interface::autodetect_smart_device(const char * name)
 
     // Try to detect possible USB->(S)ATA bridge
     unsigned short vendor_id = 0, product_id = 0, version = 0;
-    if (get_usb_id(name, vendor_id, product_id, version)) {
+    if (get_usb_id(dev_name, vendor_id, product_id, version)) {
       const char * usbtype = get_usb_dev_type_by_id(vendor_id, product_id, version);
       if (!usbtype)
         return 0;
@@ -3124,7 +3191,7 @@ smart_device * linux_smart_interface::get_custom_smart_device(const char * name,
   return 0;
 }
 
-const char * linux_smart_interface::get_valid_custom_dev_types_str()
+std::string linux_smart_interface::get_valid_custom_dev_types_str()
 {
   return "marvell, areca,N, 3ware,N, hpt,L/M/N, megaraid,N"
 #ifdef HAVE_LINUX_CCISS_IOCTL_H