]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - knowndrives.cpp
Enhance dh_clean to clean up better
[mirror_smartmontools-debian.git] / knowndrives.cpp
index 6018fb8df90dddb93172c6de682f0a5a40173b84..25d3babcb7d0a321ea1161245ebc50c97bf4ee18 100644 (file)
@@ -4,7 +4,8 @@
  * Home page of code is: http://smartmontools.sourceforge.net
  * Address of support mailing list: smartmontools-support@lists.sourceforge.net
  *
- * Copyright (C) 2003-7 Philip Williams, Bruce Allen
+ * Copyright (C) 2003-11 Philip Williams, Bruce Allen
+ * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
  *
  * 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
@@ -12,8 +13,7 @@
  * 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.
+ * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 #include "int64.h"
 #include <stdio.h>
 #include "atacmds.h"
-#include "ataprint.h"
-#include "extern.h"
 #include "knowndrives.h"
-#include "utility.h" // includes <regex.h>
+#include "utility.h"
 
-const char *knowndrives_c_cvsid="$Id: knowndrives.cpp,v 1.162 2007/11/05 00:29:11 geoffk1 Exp $"
-ATACMDS_H_CVSID ATAPRINT_H_CVSID CONFIG_H_CVSID EXTERN_H_CVSID INT64_H_CVSID KNOWNDRIVES_H_CVSID UTILITY_H_CVSID;
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef _WIN32
+#include <io.h> // access()
+#endif
+
+#include <stdexcept>
+
+const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3719 2012-12-03 21:19:33Z chrfranke $"
+                                     KNOWNDRIVES_H_CVSID;
 
 #define MODEL_STRING_LENGTH                         40
 #define FIRMWARE_STRING_LENGTH                       8
 #define TABLEPRINTWIDTH                             19
 
-// See vendorattributeargs[] array in atacmds.cpp for definitions.
-#define PRESET_9_MINUTES                   {   9,  1 }
-#define PRESET_9_TEMP                      {   9,  2 }
-#define PRESET_9_SECONDS                   {   9,  3 }
-#define PRESET_9_HALFMINUTES               {   9,  4 }
-#define PRESET_192_EMERGENCYRETRACTCYCLECT { 192,  1 }
-#define PRESET_193_LOADUNLOAD              { 193,  1 }
-#define PRESET_194_10XCELSIUS              { 194,  1 }
-#define PRESET_194_UNKNOWN                 { 194,  2 }
-#define PRESET_198_OFFLINESCANUNCSECTORCT  { 198,  1 }
-#define PRESET_200_WRITEERRORCOUNT         { 200,  1 }
-#define PRESET_201_DETECTEDTACOUNT         { 201,  1 }         
-#define PRESET_220_TEMP                    { 220,  1 }
-
-/* Arrays of preset vendor-specific attribute options for use in
- * knowndrives[]. */
-
-extern int64_t bytes;
-
-// to hold onto exit code for atexit routine
-extern int exitstatus;
-
-// These three are common to several models.
-const unsigned char vendoropts_9_minutes[][2] = {
-  PRESET_9_MINUTES,
-  {0,0}
-};
-const unsigned char vendoropts_9_halfminutes[][2] = {
-  PRESET_9_HALFMINUTES,
-  {0,0}
-};
-const unsigned char vendoropts_9_seconds[][2] = {
-  PRESET_9_SECONDS,
-  {0,0}
-};
 
-const unsigned char vendoropts_Maxtor_4D080H4[][2] = {
-  PRESET_9_MINUTES,
-  PRESET_194_UNKNOWN,
-  {0,0}
+// Builtin table of known drives.
+// Used as a default if not read from
+// "/usr/{,/local}share/smartmontools/drivedb.h"
+// or any other file specified by '-B' option,
+// see read_default_drive_databases() below.
+// The drive_settings structure is described in drivedb.h.
+const drive_settings builtin_knowndrives[] = {
+#include "drivedb.h"
 };
 
-const unsigned char vendoropts_Fujitsu_MHS2020AT[][2] = {
-  PRESET_9_SECONDS,
-  PRESET_192_EMERGENCYRETRACTCYCLECT,
-  PRESET_198_OFFLINESCANUNCSECTORCT,
-  PRESET_200_WRITEERRORCOUNT,
-  PRESET_201_DETECTEDTACOUNT,
-  {0,0}
-};
 
-const unsigned char vendoropts_Fujitsu_MHR2040AT[][2] = {
-  PRESET_9_SECONDS,
-  PRESET_192_EMERGENCYRETRACTCYCLECT,
-  PRESET_198_OFFLINESCANUNCSECTORCT,
-  PRESET_200_WRITEERRORCOUNT,
-  {0,0}
-};
+/// Drive database class. Stores custom entries read from file.
+/// Provides transparent access to concatenation of custom and
+/// default table.
+class drive_database
+{
+public:
+  drive_database();
 
-const unsigned char vendoropts_Samsung_SV4012H[][2] = {
-  PRESET_9_HALFMINUTES,
-  {0,0}
-};
+  ~drive_database();
 
-const unsigned char vendoropts_Samsung_SV1204H[][2] = {
-  PRESET_9_HALFMINUTES,
-  PRESET_194_10XCELSIUS,
-  {0,0}
-};
+  /// Get total number of entries.
+  unsigned size() const
+    { return m_custom_tab.size() + m_builtin_size; }
+
+  /// Get number of custom entries.
+  unsigned custom_size() const
+    { return m_custom_tab.size(); }
+
+  /// Array access.
+  const drive_settings & operator[](unsigned i);
+
+  /// Append new custom entry.
+  void push_back(const drive_settings & src);
+
+  /// Append builtin table.
+  void append(const drive_settings * builtin_tab, unsigned builtin_size)
+    { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; }
 
-const unsigned char vendoropts_Hitachi_DK23XX[][2] = {
-  PRESET_9_MINUTES,
-  PRESET_193_LOADUNLOAD,
-  {0,0}
+private:
+  const drive_settings * m_builtin_tab;
+  unsigned m_builtin_size;
+
+  std::vector<drive_settings> m_custom_tab;
+  std::vector<char *> m_custom_strings;
+
+  const char * copy_string(const char * str);
+
+  drive_database(const drive_database &);
+  void operator=(const drive_database &);
 };
 
-const char same_as_minus_F[]="Fixes byte order in some SMART data (same as -F samsung)";
-const char same_as_minus_F2[]="Fixes byte order in some SMART data (same as -F samsung2)";
-const char same_as_minus_F3[]="Fixes completed self-test reported as in progress (same as -F samsung3)";
+drive_database::drive_database()
+: m_builtin_tab(0), m_builtin_size(0)
+{
+}
 
-const char may_need_minus_F_disabled[] ="May need -F samsung disabled; see manual for details.";
-const char may_need_minus_F2_disabled[]="May need -F samsung2 disabled; see manual for details.";
-const char may_need_minus_F2_enabled[] ="May need -F samsung2 enabled; see manual for details.";
-const char may_need_minus_F_enabled[]  ="May need -F samsung or -F samsung2 enabled; see manual for details.";
-const char may_need_minus_F3_enabled[] ="May need -F samsung3 enabled; see manual for details.";
+drive_database::~drive_database()
+{
+  for (unsigned i = 0; i < m_custom_strings.size(); i++)
+    delete [] m_custom_strings[i];
+}
 
-/* Special-purpose functions for use in knowndrives[]. */
-void specialpurpose_reverse_samsung(smartmonctrl *con)
+const drive_settings & drive_database::operator[](unsigned i)
 {
-  if (con->fixfirmwarebug==FIX_NOTSPECIFIED)
-    con->fixfirmwarebug = FIX_SAMSUNG;
+  return (i < m_custom_tab.size() ? m_custom_tab[i]
+          : m_builtin_tab[i - m_custom_tab.size()] );
 }
-void specialpurpose_reverse_samsung2(smartmonctrl *con)
+
+void drive_database::push_back(const drive_settings & src)
 {
-  if (con->fixfirmwarebug==FIX_NOTSPECIFIED)
-    con->fixfirmwarebug = FIX_SAMSUNG2;
+  drive_settings dest;
+  dest.modelfamily    = copy_string(src.modelfamily);
+  dest.modelregexp    = copy_string(src.modelregexp);
+  dest.firmwareregexp = copy_string(src.firmwareregexp);
+  dest.warningmsg     = copy_string(src.warningmsg);
+  dest.presets        = copy_string(src.presets);
+  m_custom_tab.push_back(dest);
 }
-void specialpurpose_fix_samsung3(smartmonctrl *con)
+
+const char * drive_database::copy_string(const char * src)
 {
-  if (con->fixfirmwarebug==FIX_NOTSPECIFIED)
-    con->fixfirmwarebug = FIX_SAMSUNG3;
+  size_t len = strlen(src);
+  char * dest = new char[len+1];
+  memcpy(dest, src, len+1);
+  try {
+    m_custom_strings.push_back(dest);
+  }
+  catch (...) {
+    delete [] dest; throw;
+  }
+  return dest;
 }
 
-/* Table of settings for known drives terminated by an element containing all
- * zeros.  The drivesettings structure is described in knowndrives.h.  Note
- * that lookupdrive() will search knowndrives[] from the start to end or
- * until it finds the first match, so the order in knowndrives[] is important
- * for distinct entries that could match the same drive. */
-
-// Note that the table just below uses EXTENDED REGULAR EXPRESSIONS.
-// A good on-line reference for these is:
-// http://www.zeus.com/extra/docsystem/docroot/apps/web/docs/modules/access/regex.html
-
-const drivesettings knowndrives[] = {
-  { "IBM Deskstar 60GXP series",  // ER60A46A firmware
-    "(IBM-|Hitachi )?IC35L0[12346]0AVER07",
-    "^ER60A46A$",
-    NULL, NULL, NULL, NULL
-  },
-  { "IBM Deskstar 60GXP series",  // All other firmware
-    "(IBM-|Hitachi )?IC35L0[12346]0AVER07",
-    ".*",
-    "IBM Deskstar 60GXP drives may need upgraded SMART firmware.\n"
-    "Please see http://www.geocities.com/dtla_update/index.html#rel and\n"
-    "http://www-3.ibm.com/pc/support/site.wss/document.do?lndocid=MIGR-42215 or\n"
-    "http://www-1.ibm.com/support/docview.wss?uid=psg1MIGR-42215",
-    NULL, NULL, NULL
-  },
-  { "IBM Deskstar 40GV & 75GXP series (A5AA/A6AA firmware)",
-    "(IBM-)?DTLA-30[57]0[123467][05]",
-    "^T[WX][123468AG][OF]A[56]AA$",
-    NULL, NULL, NULL, NULL
-  },
-  { "IBM Deskstar 40GV & 75GXP series (all other firmware)",
-    "(IBM-)?DTLA-30[57]0[123467][05]",
-    ".*",
-    "IBM Deskstar 40GV and 75GXP drives may need upgraded SMART firmware.\n"
-    "Please see http://www.geocities.com/dtla_update/ and\n"
-    "http://www-3.ibm.com/pc/support/site.wss/document.do?lndocid=MIGR-42215 or\n"
-    "http://www-1.ibm.com/support/docview.wss?uid=psg1MIGR-42215",
-    NULL, NULL, NULL
-  },
-  { NULL, // ExcelStor J240, J340, J360, J680, and J880
-    "^ExcelStor Technology J(24|34|36|68|88)0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // Fujitsu M1623TAU
-    "^FUJITSU M1623TAU$",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHG series",
-    "^FUJITSU MHG2...ATU?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHH series",
-    "^FUJITSU MHH2...ATU?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHJ series",
-    "^FUJITSU MHJ2...ATU?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHK series",
-    "^FUJITSU MHK2...ATU?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { NULL,  // Fujitsu MHL2300AT
-    "^FUJITSU MHL2300AT$",
-    ".*",
-    "This drive's firmware has a harmless Drive Identity Structure\n"
-      "checksum error bug.",
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { NULL,  // MHM2200AT, MHM2150AT, MHM2100AT, MHM2060AT
-    "^FUJITSU MHM2(20|15|10|06)0AT$",
-    ".*",
-    "This drive's firmware has a harmless Drive Identity Structure\n"
-      "checksum error bug.",
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHN series",
-    "^FUJITSU MHN2...AT$",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { NULL, // Fujitsu MHR2020AT
-    "^FUJITSU MHR2020AT$",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { NULL, // Fujitsu MHR2040AT
-    "^FUJITSU MHR2040AT$",
-    ".*",    // Tested on 40BA
-    NULL,
-    vendoropts_Fujitsu_MHR2040AT,
-    NULL, NULL
-  },
-  { "Fujitsu MHSxxxxAT family",
-    "^FUJITSU MHS20[6432]0AT(  .)?$",
-    ".*",
-    NULL,
-    vendoropts_Fujitsu_MHS2020AT,
-    NULL, NULL
-  },
-  { "Fujitsu MHT series",
-    "^FUJITSU MHT2...(AH|AS|AT|BH)U?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHU series",
-    "^FUJITSU MHU2...ATU?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MHV series",
-    "^FUJITSU MHV2...(AH|AS|AT|BH|BS|BT)",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { "Fujitsu MPA..MPG series",
-    "^FUJITSU MP[A-G]3...A[HTEV]U?",
-    ".*",
-    NULL,
-    vendoropts_9_seconds,
-    NULL, NULL
-  },
-  { NULL, // Samsung SV4012H (known firmware)
-    "^SAMSUNG SV4012H$",
-    "^RM100-08$",
-    NULL,
-    vendoropts_Samsung_SV4012H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // Samsung SV4012H (all other firmware)
-    "^SAMSUNG SV4012H$",
-    ".*",
-    may_need_minus_F_disabled,
-    vendoropts_Samsung_SV4012H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // Samsung SV0412H (known firmware)
-    "^SAMSUNG SV0412H$",
-    "^SK100-01$",
-    NULL,
-    vendoropts_Samsung_SV1204H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // Samsung SV0412H (all other firmware)
-    "^SAMSUNG SV0412H$",
-    ".*",
-    may_need_minus_F_disabled,
-    vendoropts_Samsung_SV1204H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // Samsung SV1204H (known firmware)
-    "^SAMSUNG SV1204H$",
-    "^RK100-1[3-5]$",
-    NULL,
-    vendoropts_Samsung_SV1204H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // Samsung SV1204H (all other firmware)
-    "^SAMSUNG SV1204H$",
-    ".*",
-    may_need_minus_F_disabled,
-    vendoropts_Samsung_SV1204H,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // SAMSUNG SV0322A tested with FW JK200-35
-    "^SAMSUNG SV0322A$",
-    ".*",
-    NULL,
-    NULL,
-    NULL,
-    NULL
-  },
-  { NULL, // SAMSUNG SP40A2H with RR100-07 firmware
-    "^SAMSUNG SP40A2H$",
-    "^RR100-07$",
-    NULL,
-    vendoropts_9_halfminutes,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { NULL, // SAMSUNG SP8004H with QW100-61 firmware
-    "^SAMSUNG SP8004H$",
-    "^QW100-61$",
-    NULL,
-    vendoropts_9_halfminutes,
-    specialpurpose_reverse_samsung,
-    same_as_minus_F
-  },
-  { "SAMSUNG SpinPoint T133 series", // tested with HD300LJ/ZT100-12, HD400LJ/ZZ100-14, HD401LJ/ZZ100-15
-    "^SAMSUNG HD(250KD|(30[01]|320|40[01])L[DJ])$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "SAMSUNG SpinPoint T166 series", // tested with HD501LJ/CR100-10
-    "^SAMSUNG HD(080G|160H|32[01]K|403L|50[01]L)J$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "SAMSUNG SpinPoint P120 series", // VF100-37 firmware, tested with SP2514N/VF100-37
-    "^SAMSUNG SP(16[01]3|2[05][01]4)[CN]$",
-    "^VF100-37$",
-    NULL, NULL,
-    specialpurpose_fix_samsung3,
-    same_as_minus_F3
-  },
-  { "SAMSUNG SpinPoint P120 series", // other firmware, tested with SP2504C/VT100-33
-    "^SAMSUNG SP(16[01]3|2[05][01]4)[CN]$",
-    ".*",
-    may_need_minus_F3_enabled,
-    NULL, NULL, NULL
-  },
-  { "SAMSUNG SpinPoint P80 SD series", // tested with HD160JJ/ZM100-33
-    "^SAMSUNG HD(080H|120I|160J)J$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "SAMSUNG SpinPoint P80 series", // BH100-35 firmware, tested with SP0842N/BH100-35
-    "^SAMSUNG SP(0451|08[0124]2|12[0145]3|16[0145]4)[CN]$",
-    "^BH100-35$",
-    NULL, NULL,
-    specialpurpose_fix_samsung3,
-    same_as_minus_F3
-  },
-  { "SAMSUNG SpinPoint P80 series", // firmware *-35 or later
-    "^SAMSUNG SP(0451|08[0124]2|12[0145]3|16[0145]4)[CN]$",
-    ".*-3[5-9]$",
-    may_need_minus_F3_enabled,
-    NULL, NULL, NULL
-  },
-  { "SAMSUNG SpinPoint P80 series", // firmware *-26...34, tested with SP1614C/SW100-34
-    "^SAMSUNG SP(0451|08[0124]2|12[0145]3|16[0145]4)[CN]$",
-    ".*-(2[6789]|3[0-4])$",
-    NULL,
-    vendoropts_9_halfminutes,
-    NULL, NULL
-  },
-  { 
-    NULL, // Any other Samsung disk with *-23 *-24 firmware
-    // SAMSUNG SP1213N (TL100-23 firmware)
-    // SAMSUNG SP0802N (TK100-23 firmware)
-    // Samsung SP1604N, tested with FW TM100-23 and TM100-24
-    "^SAMSUNG .*$",
-    ".*-2[34]$",
-    NULL,
-    vendoropts_Samsung_SV4012H,
-    specialpurpose_reverse_samsung2,
-    same_as_minus_F2
-  },
-  { NULL, // All Samsung drives with '.*-25' firmware
-    "^SAMSUNG.*",
-    ".*-25$",
-    may_need_minus_F2_disabled,
-    vendoropts_Samsung_SV4012H,
-    specialpurpose_reverse_samsung2,
-    same_as_minus_F2
-  },
-  { NULL, // All Samsung drives with '.*-26 or later (currently to -39)' firmware
-    "^SAMSUNG.*",
-    ".*-(2[6789]|3[0-9])$",
-    NULL,
-    vendoropts_Samsung_SV4012H,
-    NULL,
-    NULL
-  },
-  { NULL, // Samsung ALL OTHER DRIVES
-    "^SAMSUNG.*",
-    ".*",
-    may_need_minus_F_enabled,
-    NULL, NULL, NULL
-  },
-  { "Maxtor Fireball 541DX family",
-    "^Maxtor 2B0(0[468]|1[05]|20)H1$",
-    ".*",
-    NULL,
-    vendoropts_Maxtor_4D080H4,
-    NULL, NULL
-  },
-  { "Maxtor Fireball 3 family",
-    "^Maxtor 2F0[234]0[JL]0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 2160 Ultra ATA family",
-    "^Maxtor 8(2160D2|3228D3|3240D3|4320D4|6480D6|8400D8|8455D8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 2880 Ultra ATA family",
-    "^Maxtor 9(0510D4|0576D4|0648D5|0720D5|0840D6|0845D6|0864D6|1008D7|1080D8|1152D8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 3400 Ultra ATA family",
-    "^Maxtor 9(1(360|350|202)D8|1190D7|10[12]0D6|0840D5|06[48]0D4|0510D3|1(350|202)E8|1010E6|0840E5|0640E4)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax D540X-4G family",
-    "^Maxtor 4G(120J6|160J[68])$",
-    ".*",
-    NULL,
-    vendoropts_Maxtor_4D080H4,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax D540X-4K family",
-    "^MAXTOR 4K(020H1|040H2|060H3|080H4)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus D740X family",
-    "^MAXTOR 6L0(20[JL]1|40[JL]2|60[JL]3|80[JL]4)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 5120 Ultra ATA 33 family",
-    "^Maxtor 9(0512D2|0680D3|0750D3|0913D4|1024D4|1360D6|1536D6|1792D7|2048D8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 6800 Ultra ATA 66 family",
-    "^Maxtor 9(2732U8|2390U7|2049U6|1707U5|1366U4|1024U3|0845U3|0683U2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax D540X-4D",
-    "^Maxtor 4D0(20H1|40H2|60H3|80H4)$",
-    ".*",
-    NULL,
-    vendoropts_Maxtor_4D080H4,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 16 family",
-    "^Maxtor 4(R0[68]0[JL]0|R1[26]0L0|A160J0|R120L4)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 4320 Ultra ATA family",
-    "^Maxtor (91728D8|91512D7|91303D6|91080D5|90845D4|90645D3|90648D[34]|90432D2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 17 VL family",
-    "^Maxtor 9(0431U1|0641U2|0871U2|1301U3|1741U4)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 20 VL family",
-    "^Maxtor (94091U8|93071U6|92561U5|92041U4|91731U4|91531U3|91361U3|91021U2|90841U2|90651U2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax VL 30 family",
-    "^Maxtor (33073U4|32049U3|31536U2|30768U1)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 36 family",
-    "^Maxtor (93652U8|92739U6|91826U4|91369U3|90913U2|90845U2|90435U1)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 40 ATA 66 series",
-    "^Maxtor 9(0684U2|1024U2|1362U3|1536U3|2049U4|2562U5|3073U6|4098U8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 40 series (Ultra ATA 66 and Ultra ATA 100)",
-    "^Maxtor (54098[UH]8|53073[UH]6|52732[UH]6|52049[UH]4|51536[UH]3|51369[UH]3|51024[UH]2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 40 VL Ultra ATA 100 series",
-    "^Maxtor 3(1024H1|1535H2|2049H2|3073H3|4098H4)( B)?$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 45 Ulta ATA 100 family",
-    "^Maxtor 5(4610H6|4098H6|3073H4|2049H3|1536H2|1369H2|1023H2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 60 ATA 66 family",
-    "^Maxtor 9(1023U2|1536U2|2049U3|2305U3|3073U4|4610U6|6147U8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 60 ATA 100 family",
-    "^Maxtor 9(1023H2|1536H2|2049H3|2305H3|3073H4|4610H6|6147H8)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 60 family",
-    "^Maxtor 5T0(60H6|40H4|30H3|20H2|10H1)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 80 family",
-    "^Maxtor (98196H8|96147H6)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 536DX family",
-    "^Maxtor 4W(100H6|080H6|060H4|040H3|030H2)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 8 family",
-    "^Maxtor 6(E0[234]|K04)0L0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 10 family (ATA/133 and SATA/150)",
-    "^Maxtor 6(B(30|25|20|16|12|08)0[MPRS]|L(080[MLP]|(100|120)[MP]|160[MP]|200[MPRS]|250[RS]|300[RS]))0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 10 family (SATA/300)",
-    "^Maxtor 6V(080E|160E|200E|250F|300F|320F)0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Maxtor DiamondMax Plus 9 family",
-    "^Maxtor 6Y((060|080|120|160)L0|(060|080|120|160|200|250)P0|(060|080|120|160|200|250)M0)$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor DiamondMax 11 family",
-    "^Maxtor 6H[45]00[FR]0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Maxtor MaXLine Plus II",
-    "^Maxtor 7Y250[PM]0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor MaXLine II family",
-    "^Maxtor [45]A(25|30|32)0[JN]0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor MaXLine III family (ATA/133 and SATA/150)",
-    "^Maxtor 7L(25|30)0[SR]0$",
-    ".*",
-    NULL,
-    vendoropts_9_minutes,
-    NULL, NULL
-  },
-  { "Maxtor MaXLine III family (SATA/300)",
-    "^Maxtor 7V(25|30)0F0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Maxtor MaXLine Pro 500 family",  // There is also a 7H500R0 model, but I
-    "^Maxtor 7H500F0$",               // haven't added it because I suspect
-    ".*",                             // it might need vendoropts_9_minutes
-    NULL, NULL, NULL, NULL            // and nobody has submitted a report yet
-  },
-  { NULL, // HITACHI_DK14FA-20B
-    "^HITACHI_DK14FA-20B$",
-    ".*",
-    NULL,
-    vendoropts_Hitachi_DK23XX,
-    NULL, NULL
-  },
-  { "HITACHI Travelstar DK23XX/DK23XXB series",
-    "^HITACHI_DK23..-..B?$",
-    ".*",
-    NULL,
-    vendoropts_Hitachi_DK23XX,
-    NULL, NULL
-  },
-  { "Hitachi Endurastar J4K20/N4K20 (formerly DK23FA-20J)",
-    "^(HITACHI_DK23FA-20J|HTA422020F9AT[JN]0)$",
-    ".*",
-    NULL,
-    vendoropts_Hitachi_DK23XX,
-    NULL, NULL
-  },
-  { "IBM Deskstar 14GXP and 16GP series",
-    "^IBM-DTTA-3(7101|7129|7144|5032|5043|5064|5084|5101|5129|5168)0$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Deskstar 25GP and 22GXP family",
-    "^IBM-DJNA-3(5(101|152|203|250)|7(091|135|180|220))0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "IBM Travelstar 4GT family",
-    "^IBM-DTCA-2(324|409)0$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Travelstar 25GS, 18GT, and 12GN family",
-    "^IBM-DARA-2(25|18|15|12|09|06)000$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Travelstar 48GH, 30GN, and 15GN family",
-    "^(IBM-|Hitachi )?IC25(T048ATDA05|N0(30|20|15|12|10|07|06|05)ATDA04)-.$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Travelstar 32GH, 30GT, and 20GN family",
-    "^IBM-DJSA-2(32|30|20|10|05)$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Travelstar 4GN family",
-    "^IBM-DKLA-2(216|324|432)0$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Deskstar 37GP and 34GXP family",
-    "^IBM-DPTA-3(5(375|300|225|150)|7(342|273|205|136))0$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM/Hitachi Travelstar 60GH and 40GN family",
-    "^(IBM-|Hitachi )?IC25(T060ATC[SX]05|N0[4321]0ATC[SX]04)-.$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM/Hitachi Travelstar 40GNX family",
-    "^(IBM-|Hitachi )?IC25N0[42]0ATC[SX]05-.$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar 80GN family",
-    "^(Hitachi )?IC25N0[23468]0ATMR04-.$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Travelstar 5K80 family",
-    "^HTS5480[8642]0M9AT00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar 5K100 series",
-    "^HTS5410[1864]0G9(AT|SA)00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar E5K100 series",
-    "^HTE541040G9(AT|SA)00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar 5K160 series",
-    "^(Hitachi )?HTS5416([468]0|1[26])J9(AT|SA)00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar 7K60",
-    "^HTS726060M9AT00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar 7K100",
-    "^HTS7210[168]0G9(AT|SA)00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar E7K100",
-    "^HTE7210[168]0G9(AT|SA)00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "Hitachi Travelstar E7K60 family",
-    "^HTE7260[46]0M9AT00$",
-    ".*",
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM/Hitachi Deskstar 120GXP family",
-    "^(IBM-)?IC35L((020|040|060|080|120)AVVA|0[24]0AVVN)07-[01]$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "IBM/Hitachi Deskstar GXP-180 family",
-    "^(IBM-)?IC35L(030|060|090|120|180)AVV207-[01]$",
-    ".*", 
-    NULL, NULL, NULL, NULL 
-  },
-  { "IBM Travelstar 14GS",
-    "^IBM-DCYA-214000$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "IBM Travelstar 4LP",
-    "^IBM-DTNA-2(180|216)0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Deskstar 7K80 series",
-    "^(Hitachi )?HDS7280([48]0PLAT20|(40)?PLA320|80PLA380)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Deskstar 7K250 series",
-    "^(Hitachi )?HDS7225((40|80|12|16)VLAT20|(12|16|25)VLAT80|(80|12|16|25)VLSA80)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Deskstar T7K250 series",
-    "^(Hitachi )?HDT7225((25|20|16)DLA(T80|380))$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Deskstar 7K400 series",
-    "^(Hitachi )?HDS724040KL(AT|SA)80$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Hitachi Deskstar 7K500 series",
-    "^(Hitachi )?HDS725050KLA(360|T80)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Toshiba 2.5\" HDD series (30-60 GB)",
-    "^TOSHIBA MK((6034|4032)GSX|(6034|4032)GAX|(6026|4026|4019|3019)GAXB?|(6025|6021|4025|4021|4018|3021|3018)GAS|(4036|3029)GACE?|(4018|3017)GAP)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Toshiba 2.5\" HDD series (80 GB and above)",
-    "^TOSHIBA MK(80(25GAS|26GAX|32GAX|32GSX)|10(31GAS|32GAX)|12(33GAS|34G[AS]X)|2035GSS)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // TOSHIBA MK6022GAX
-    "^TOSHIBA MK6022GAX$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // TOSHIBA MK6409MAV
-    "^TOSHIBA MK6409MAV$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // TOS MK3019GAXB SUN30G
-    "^TOS MK3019GAXB SUN30G$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // TOSHIBA MK2016GAP, MK2017GAP, MK2018GAP, MK2018GAS, MK2023GAS
-    "^TOSHIBA MK20(1[678]GAP|(18|23)GAS)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Momentus family",
-    "^ST9(20|28|40|48)11A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Momentus 42 family",
-    "^ST9(2014|3015|4019)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Momentus 4200.2 Series",
-    "^ST9(100822|808210|60821|50212|402113|30219)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Momentus 5400.2 series",
-    "^ST9(808211|60822|408114|308110|120821|10082[34]|8823|6812|4813|3811)AS?$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Momentus 7200.1 series",
-    "^ST9(10021|80825|6023|4015)AS?$", 
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Medalist 2110, 3221, 4321, 6531, and 8641",
-    "^ST3(2110|3221|4321|6531|8641)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U Series X family",
-    "^ST3(10014A(CE)?|20014A)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U7 family",
-    "^ST3(30012|40012|60012|80022|120020)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U Series 6 family",
-    "^ST3(8002|6002|4081|3061|2041)0A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U Series 5 family",
-    "^ST3(40823|30621|20413|15311|10211)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U4 family",
-    "^ST3(2112|4311|6421|8421)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U8 family",
-    "^ST3(8410|4313|17221|13021)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate U10 family",
-    "^ST3(20423|15323|10212)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda ATA II family",
-    "^ST3(3063|2042|1532|1021)0A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda ATA III family",
-    "^ST3(40824|30620|20414|15310|10215)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda ATA IV family",
-    "^ST3(20011|30011|40016|60021|80021)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda ATA V family",
-    "^ST3(12002(3A|4A|9A|3AS)|800(23A|15A|23AS)|60(015A|210A)|40017A)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda 5400.1",
-    "^ST340015A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda 7200.7 and 7200.7 Plus family",
-    "^ST3(200021A|200822AS?|16002[13]AS?|12002[26]AS?|1[26]082[78]AS|8001[13]AS?|80817AS|60014A|40111AS|40014AS?)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda 7200.8 family",
-    "^ST3(400[68]32|300[68]31|250[68]23|200826)AS?$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda 7200.9 family",
-    "^ST3(500[68]41|400[68]33|300[68]22|250[68]24|250[68]24|200827|160[28]12|120814|120[28]13|80[28]110|402111)AS?$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Barracuda 7200.10 family",
-    "^ST3(750[68]4|500[68]3|400[68]2|320[68]2|300[68]2|250[68]2|20082)0AS?$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Medalist 17240, 13030, 10231, 8420, and 4310",
-    "^ST3(17240|13030|10231|8420|4310)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate Medalist 17242, 13032, 10232, 8422, and 4312",
-    "^ST3(1724|1303|1023|842|431)2A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Seagate NL35 family",
-    "^ST3(250623|250823|400632|400832|250824|250624|400633|400833|500641|500841)NS$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Protege",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD([2468]00E|1[26]00A)B-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar family",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD(2|3|4|6|8|10|12|16|18|20|25)00BB-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar WDxxxAB series",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD(3|4|6)00AB-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar WDxxxAA series",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD...?AA(-.*)?$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar WDxxxBA series",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD...BA$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // Western Digital Caviar AC12500, AC14300, AC23200, AC24300, AC25100,
-          // AC36400, AC38400
-    "^WDC AC(125|143|232|243|251|364|384)00.?",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar Serial ATA family",
-    "^WDC WD(4|8|20|32)00BD-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar SE family",
-  /* Western Digital drives with this comment all appear to use Attribute 9 in
-   * a  non-standard manner.  These entries may need to be updated when it
-   * is understood exactly how Attribute 9 should be interpreted.
-   * UPDATE: this is probably explained by the WD firmware bug described in the
-   * smartmontools FAQ */
-    "^WDC WD((4|6|8|10|12|16|18|20|25|30|32)00JB|(12|20|25)00PB)-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar SE (Serial ATA) family",
-    "^WDC WD((4|8|12|16|20|25|32)00JD|(12|16|20|25|30|32)00JS|1600AAJS)-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar SE16 family",
-    "^WDC WD((25|32|40|50)00KS|4000KD)-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Caviar RE Serial ATA series",
-    "^WDC WD((12|16|25|32)00SD|2500YD|4000Y[RS]|5000YS)-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Raptor family",
-    "^WDC WD((360|740|800)GD|(360|740|1500)ADFD)-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Western Digital Scorpio family",
-    "^WDC WD((12|10|8|6|4)00(UE|VE|BEAS|BEVS))-.*$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL,  // QUANTUM BIGFOOT TS10.0A
-    "^QUANTUM BIGFOOT TS10.0A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALLlct15 20 and QUANTUM FIREBALLlct15 30
-    "^QUANTUM FIREBALLlct15 [123]0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "QUANTUM FIREBALLlct20 series",
-    "^QUANTUM FIREBALLlct20 [234]0$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALL CX10.2A
-    "^QUANTUM FIREBALL CX10.2A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Quantum Fireball Plus LM series",
-    "^QUANTUM FIREBALLP LM(10.2|15|20.5|30)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Quantum Fireball CR series",
-    "^QUANTUM FIREBALL CR(4.3|8.4)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALLP AS10.2, AS20.5, AS30.0, and AS40.0
-    "^QUANTUM FIREBALLP AS(10.2|20.5|30.0|40.0)$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALL EX6.4A
-    "^QUANTUM FIREBALL EX6.4A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALL ST3.2A
-    "^QUANTUM FIREBALL ST(3.2|4.3)A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALL EX3.2A
-    "^QUANTUM FIREBALL EX3.2A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { NULL, // QUANTUM FIREBALLP KX27.3
-    "^QUANTUM FIREBALLP KX27.3$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Quantum Fireball Plus KA series",
-    "^QUANTUM FIREBALLP KA(9|10).1$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  { "Quantum Fireball SE series",
-    "^QUANTUM FIREBALL SE4.3A$",
-    ".*",
-    NULL, NULL, NULL, NULL
-  },
-  /*------------------------------------------------------------
-   *  End of table.  Do not add entries below this marker.
-   *------------------------------------------------------------ */
-  {NULL, NULL, NULL, NULL, NULL, NULL, NULL}
-};
+
+/// The drive database.
+static drive_database knowndrives;
+
+
+// Return true if modelfamily string describes entry for USB ID
+static bool is_usb_modelfamily(const char * modelfamily)
+{
+  return !strncmp(modelfamily, "USB:", 4);
+}
+
+// Return true if entry for USB ID
+static inline bool is_usb_entry(const drive_settings * dbentry)
+{
+  return is_usb_modelfamily(dbentry->modelfamily);
+}
+
+// Compile regular expression, print message on failure.
+static bool compile(regular_expression & regex, const char *pattern)
+{
+  if (!regex.compile(pattern, REG_EXTENDED)) {
+    pout("Internal error: unable to compile regular expression \"%s\": %s\n"
+         "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n",
+      pattern, regex.get_errmsg());
+    return false;
+  }
+  return true;
+}
+
+// Compile & match a regular expression.
+static bool match(const char * pattern, const char * str)
+{
+  regular_expression regex;
+  if (!compile(regex, pattern))
+    return false;
+  return regex.full_match(str);
+}
 
 // Searches knowndrives[] for a drive with the given model number and firmware
 // string.  If either the drive's model or firmware strings are not set by the
-// manufacturer then values of NULL may be used.  Returns the index of the
-// first match in knowndrives[] or -1 if no match if found.
-int lookupdrive(const char *model, const char *firmware)
+// manufacturer then values of NULL may be used.  Returns the entry of the
+// first match in knowndrives[] or 0 if no match if found.
+static const drive_settings * lookup_drive(const char * model, const char * firmware)
 {
-  regex_t regex;
-  int i, index;
-  const char *empty = "";
+  if (!model)
+    model = "";
+  if (!firmware)
+    firmware = "";
+
+  for (unsigned i = 0; i < knowndrives.size(); i++) {
+    // Skip USB entries
+    if (is_usb_entry(&knowndrives[i]))
+      continue;
 
-  model = model ? model : empty;
-  firmware = firmware ? firmware : empty;
+    // Check whether model matches the regular expression in knowndrives[i].
+    if (!match(knowndrives[i].modelregexp, model))
+      continue;
 
-  for (i = 0, index = -1; index == -1 && knowndrives[i].modelregexp; i++) {
-    // Attempt to compile regular expression.
-    if (compileregex(&regex, knowndrives[i].modelregexp, REG_EXTENDED))
-      goto CONTINUE;
+    // Model matches, now check firmware. "" matches always.
+    if (!(  !*knowndrives[i].firmwareregexp
+          || match(knowndrives[i].firmwareregexp, firmware)))
+      continue;
 
-    // Check whether model matches the regular expression in knowndrives[i].
-    if (!regexec(&regex, model, 0, NULL, 0)) {
-      // model matches, now check firmware.
-      if (!knowndrives[i].firmwareregexp)
-        // The firmware regular expression in knowndrives[i] is NULL, which is
-        // considered a match.
-        index = i;
-      else {
-        // Compare firmware against the regular expression in knowndrives[i].
-        regfree(&regex);  // Recycle regex.
-        if (compileregex(&regex, knowndrives[i].firmwareregexp, REG_EXTENDED))
-          goto CONTINUE;
-        if (!regexec(&regex, firmware, 0, NULL, 0))
-          index = i;
-      }
+    // Found
+    return &knowndrives[i];
+  }
+
+  // Not found
+  return 0;
+}
+
+
+// Parse drive or USB options in preset string, return false on error.
+static bool parse_db_presets(const char * presets, ata_vendor_attr_defs * defs,
+                             firmwarebug_defs * firmwarebugs, std::string * type)
+{
+  for (int i = 0; ; ) {
+    i += strspn(presets+i, " \t");
+    if (!presets[i])
+      break;
+    char opt, arg[80+1+13]; int len = -1;
+    if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0))
+      return false;
+    if (opt == 'v' && defs) {
+      // Parse "-v N,format[,name]"
+      if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE))
+        return false;
+    }
+    else if (opt == 'F' && firmwarebugs) {
+      firmwarebug_defs bug;
+      if (!parse_firmwarebug_def(arg, bug))
+        return false;
+      // Don't set if user specified '-F none'.
+      if (!firmwarebugs->is_set(BUG_NONE))
+        firmwarebugs->set(bug);
     }
-  CONTINUE:
-    regfree(&regex);
+    else if (opt == 'd' && type) {
+        // TODO: Check valid types
+        *type = arg;
+    }
+    else
+      return false;
+
+    i += len;
   }
+  return true;
+}
 
-  return index;
+// Parse '-v' and '-F' options in preset string, return false on error.
+static inline bool parse_presets(const char * presets,
+                                 ata_vendor_attr_defs & defs,
+                                 firmwarebug_defs & firmwarebugs)
+{
+  return parse_db_presets(presets, &defs, &firmwarebugs, 0);
 }
 
+// Parse '-d' option in preset string, return false on error.
+static inline bool parse_usb_type(const char * presets, std::string & type)
+{
+  return parse_db_presets(presets, 0, 0, &type);
+}
 
-// Shows all presets for drives in knowndrives[].
-void showonepreset(const drivesettings *drivetable){
-  
-  const unsigned char (* presets)[2] = drivetable->vendoropts;
-  int first_preset = 1;
-  
+// Parse "USB: [DEVICE] ; [BRIDGE]" string
+static void parse_usb_names(const char * names, usb_dev_info & info)
+{
+  int n1 = -1, n2 = -1, n3 = -1;
+  sscanf(names, "USB: %n%*[^;]%n; %n", &n1, &n2, &n3);
+  if (0 < n1 && n1 < n2)
+    info.usb_device.assign(names+n1, n2-n1);
+  else
+    sscanf(names, "USB: ; %n", &n3);
+  if (0 < n3)
+    info.usb_bridge = names+n3;
+}
+
+// Search drivedb for USB device with vendor:product ID.
+int lookup_usb_device(int vendor_id, int product_id, int bcd_device,
+                      usb_dev_info & info, usb_dev_info & info2)
+{
+  // Format strings to match
+  char usb_id_str[16], bcd_dev_str[16];
+  snprintf(usb_id_str, sizeof(usb_id_str), "0x%04x:0x%04x", vendor_id, product_id);
+  if (bcd_device >= 0)
+    snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device);
+  else
+    bcd_dev_str[0] = 0;
+
+  int found = 0;
+  for (unsigned i = 0; i < knowndrives.size(); i++) {
+    const drive_settings & dbentry = knowndrives[i];
+
+    // Skip drive entries
+    if (!is_usb_entry(&dbentry))
+      continue;
+
+    // Check whether USB vendor:product ID matches
+    if (!match(dbentry.modelregexp, usb_id_str))
+      continue;
+
+    // Parse '-d type'
+    usb_dev_info d;
+    if (!parse_usb_type(dbentry.presets, d.usb_type))
+      return 0; // Syntax error
+    parse_usb_names(dbentry.modelfamily, d);
+
+    // If two entries with same vendor:product ID have different
+    // types, use bcd_device (if provided by OS) to select entry.
+    if (  *dbentry.firmwareregexp && *bcd_dev_str
+        && match(dbentry.firmwareregexp, bcd_dev_str)) {
+      // Exact match including bcd_device
+      info = d; found = 1;
+      break;
+    }
+    else if (!found) {
+      // First match without bcd_device
+      info = d; found = 1;
+    }
+    else if (info.usb_type != d.usb_type) {
+      // Another possible match with different type
+      info2 = d; found = 2;
+      break;
+    }
+
+    // Stop search at first matching entry with empty bcd_device
+    if (!*dbentry.firmwareregexp)
+      break;
+  }
+
+  return found;
+}
+
+// Shows one entry of knowndrives[], returns #errors.
+static int showonepreset(const drive_settings * dbentry)
+{
   // Basic error check
-  if (!drivetable || !drivetable->modelregexp){
-    pout("Null known drive table pointer. Please report\n"
+  if (!(   dbentry
+        && dbentry->modelfamily
+        && dbentry->modelregexp && *dbentry->modelregexp
+        && dbentry->firmwareregexp
+        && dbentry->warningmsg
+        && dbentry->presets                             )) {
+    pout("Invalid drive database entry. Please report\n"
          "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n");
-    return;
-  }
-  
-  // print model and firmware regular expressions
-  pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL REGEXP:", drivetable->modelregexp);
-  pout("%-*s %s\n", TABLEPRINTWIDTH, "FIRMWARE REGEXP:", drivetable->firmwareregexp ?
-       drivetable->firmwareregexp : "");
-  pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", drivetable->modelfamily ?
-       drivetable->modelfamily : "");
-  
-  // if there are any presets, then show them
-  if (presets && (*presets)[0]) while (1) {
-    char out[256];
-    const int attr = (*presets)[0], val  = (*presets)[1];
-    unsigned char fakearray[MAX_ATTRIBUTE_NUM];
-
-    // if we are at the end of the attribute list, break out
-    if (!attr)  
-      break;
-    
-    // This is a hack. ataPrintSmartAttribName() needs a pointer to an
-    // "array" to dereference, so we provide such a pointer.
-    fakearray[attr]=val;
-    ataPrintSmartAttribName(out, attr, fakearray);
-
-    // Use leading zeros instead of spaces so that everything lines up.
-    out[0] = (out[0] == ' ') ? '0' : out[0];
-    out[1] = (out[1] == ' ') ? '0' : out[1];
-    pout("%-*s %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", out);
-    first_preset = 0;
-    presets++;
+    return 1;
   }
-  else
-    pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
 
-  
-  // Is a special purpose function defined?  If so, describe it
-  if (drivetable->specialpurpose){
-    pout("%-*s ", TABLEPRINTWIDTH, "OTHER PRESETS:");
-    pout("%s\n", drivetable->functiondesc ?
-         drivetable->functiondesc : "A special purpose function "
-         "is defined for this drive"); 
+  bool usb = is_usb_entry(dbentry);
+
+  // print and check model and firmware regular expressions
+  int errcnt = 0;
+  regular_expression regex;
+  pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"),
+       dbentry->modelregexp);
+  if (!compile(regex, dbentry->modelregexp))
+    errcnt++;
+
+  pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"),
+       *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change)
+  if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp))
+    errcnt++;
+
+  if (!usb) {
+    pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily);
+
+    // if there are any presets, then show them
+    firmwarebug_defs firmwarebugs;
+    bool first_preset = true;
+    if (*dbentry->presets) {
+      ata_vendor_attr_defs defs;
+      if (!parse_presets(dbentry->presets, defs, firmwarebugs)) {
+        pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
+        errcnt++;
+      }
+      for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) {
+        if (defs[i].priority != PRIOR_DEFAULT) {
+          std::string name = ata_get_smart_attr_name(i, defs);
+          // Use leading zeros instead of spaces so that everything lines up.
+          pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "",
+               i, name.c_str());
+          // Check max name length suitable for smartctl -A output
+          const unsigned maxlen = 23;
+          if (name.size() > maxlen) {
+            pout("%*s\n", TABLEPRINTWIDTH+6+maxlen, "Error: Attribute name too long ------^");
+            errcnt++;
+          }
+          first_preset = false;
+        }
+      }
+    }
+    if (first_preset)
+      pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
+
+    // describe firmwarefix
+    for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) {
+      if (!firmwarebugs.is_set((firmwarebug_t)b))
+        continue;
+      const char * fixdesc;
+      switch ((firmwarebug_t)b) {
+        case BUG_NOLOGDIR:
+          fixdesc = "Avoids reading GP/SMART Log Directories (same as -F nologdir)";
+          break;
+        case BUG_SAMSUNG:
+          fixdesc = "Fixes byte order in some SMART data (same as -F samsung)";
+          break;
+        case BUG_SAMSUNG2:
+          fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)";
+          break;
+        case BUG_SAMSUNG3:
+          fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)";
+          break;
+        case BUG_XERRORLBA:
+          fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)";
+          break;
+        default:
+          fixdesc = "UNKNOWN"; errcnt++;
+          break;
+      }
+      pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc);
+    }
   }
-  
-  // Print any special warnings
-  if (drivetable->warningmsg){
-    pout("%-*s ", TABLEPRINTWIDTH, "WARNINGS:");
-    pout("%s\n", drivetable->warningmsg);
+  else {
+    // Print USB info
+    usb_dev_info info; parse_usb_names(dbentry->modelfamily, info);
+    pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Device:",
+      (!info.usb_device.empty() ? info.usb_device.c_str() : "[unknown]"));
+    pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Bridge:",
+      (!info.usb_bridge.empty() ? info.usb_bridge.c_str() : "[unknown]"));
+
+    if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) {
+      pout("Syntax error in USB type string \"%s\"\n", dbentry->presets);
+      errcnt++;
+    }
+    pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type",
+      (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]"));
   }
-  
-  return;
+
+  // Print any special warnings
+  if (*dbentry->warningmsg)
+    pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg);
+  return errcnt;
 }
 
 // Shows all presets for drives in knowndrives[].
-// Returns <0 on syntax error in regular expressions.
-int showallpresets(void){
-  int i;
-  int rc = 0;
-  regex_t regex;
-
+// Returns #syntax errors.
+int showallpresets()
+{
   // loop over all entries in the knowndrives[] table, printing them
   // out in a nice format
-  for (i=0; knowndrives[i].modelregexp; i++){
-    showonepreset(&knowndrives[i]);
+  int errcnt = 0;
+  for (unsigned i = 0; i < knowndrives.size(); i++) {
+    errcnt += showonepreset(&knowndrives[i]);
     pout("\n");
   }
 
-  // Check all regular expressions
-  for (i=0; knowndrives[i].modelregexp; i++){
-    if (compileregex(&regex, knowndrives[i].modelregexp, REG_EXTENDED))
-      rc = -1;
-    if (knowndrives[i].firmwareregexp) {
-      if (compileregex(&regex, knowndrives[i].firmwareregexp, REG_EXTENDED))
-        rc = -1;
-    }
-  }
+  pout("Total number of entries  :%5u\n"
+       "Entries read from file(s):%5u\n\n",
+    knowndrives.size(), knowndrives.custom_size());
+
   pout("For information about adding a drive to the database see the FAQ on the\n");
   pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n");
-  return rc;
+
+  if (errcnt > 0)
+    pout("\nFound %d syntax error(s) in database.\n"
+         "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt);
+  return errcnt;
 }
 
 // Shows all matching presets for a drive in knowndrives[].
 // Returns # matching entries.
-int showmatchingpresets(const char *model, const char *firmware){
-  int i;
+int showmatchingpresets(const char *model, const char *firmware)
+{
   int cnt = 0;
   const char * firmwaremsg = (firmware ? firmware : "(any)");
-  regex_t regex;
 
-  for (i=0; knowndrives[i].modelregexp; i++){
-    if (i > 0)
-      regfree(&regex);
-    if (compileregex(&regex, knowndrives[i].modelregexp, REG_EXTENDED))
-      continue;
-    if (regexec(&regex, model, 0, NULL, 0))
+  for (unsigned i = 0; i < knowndrives.size(); i++) {
+    if (!match(knowndrives[i].modelregexp, model))
       continue;
-    if (firmware && knowndrives[i].firmwareregexp) {
-      regfree(&regex);
-      if (compileregex(&regex, knowndrives[i].firmwareregexp, REG_EXTENDED))
-        continue;
-      if (regexec(&regex, firmware, 0, NULL, 0))
+    if (   firmware && *knowndrives[i].firmwareregexp
+        && !match(knowndrives[i].firmwareregexp, firmware))
         continue;
-    }
+    // Found
     if (++cnt == 1)
       pout("Drive found in smartmontools Database.  Drive identity strings:\n"
            "%-*s %s\n"
@@ -1330,7 +487,6 @@ int showmatchingpresets(const char *model, const char *firmware){
     showonepreset(&knowndrives[i]);
     pout("\n");
   }
-  regfree(&regex);
   if (cnt == 0)
     pout("No presets are defined for this drive.  Its identity strings:\n"
          "MODEL:    %s\n"
@@ -1341,16 +497,17 @@ int showmatchingpresets(const char *model, const char *firmware){
 }
 
 // Shows the presets (if any) that are available for the given drive.
-void showpresets(const struct ata_identify_device *drive){
-  int i;
+void show_presets(const ata_identify_device * drive)
+{
   char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
 
   // get the drive's model/firmware strings
-  format_ata_string(model, (char *)drive->model, MODEL_STRING_LENGTH);
-  format_ata_string(firmware, (char *)drive->fw_rev, FIRMWARE_STRING_LENGTH);
-  
+  ata_format_id_string(model, drive->model, sizeof(model)-1);
+  ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
+
   // and search to see if they match values in the table
-  if ((i = lookupdrive(model, firmware)) < 0) {
+  const drive_settings * dbentry = lookup_drive(model, firmware);
+  if (!dbentry) {
     // no matches found
     pout("No presets are defined for this drive.  Its identity strings:\n"
          "MODEL:    %s\n"
@@ -1367,67 +524,362 @@ void showpresets(const struct ata_identify_device *drive){
        "%-*s %s\n"
        "match smartmontools Drive Database entry:\n",
        TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware);
-  showonepreset(&knowndrives[i]);
-  return;
+  showonepreset(dbentry);
 }
 
-// Sets preset vendor attribute options in opts by finding the entry
-// (if any) for the given drive in knowndrives[].  Values that have
-// already been set in opts will not be changed.  Returns <0 if drive
-// not recognized else index >=0 into drive database.
-int applypresets(const struct ata_identify_device *drive, unsigned char **optsptr,
-                smartmonctrl *con) {
-  int i;
-  unsigned char *opts;
-  char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
-  
-  if (*optsptr==NULL)
-    bytes+=MAX_ATTRIBUTE_NUM;
-  
-  if (*optsptr==NULL && !(*optsptr=(unsigned char *)calloc(MAX_ATTRIBUTE_NUM,1))){
-    pout("Unable to allocate memory in applypresets()");
-    bytes-=MAX_ATTRIBUTE_NUM;
-    EXIT(1);
-  }
-  
-  opts=*optsptr;
-  
+// Searches drive database and sets preset vendor attribute
+// options in defs and firmwarebugs.
+// Values that have already been set will not be changed.
+// Returns pointer to database entry or nullptr if none found
+const drive_settings * lookup_drive_apply_presets(
+  const ata_identify_device * drive, ata_vendor_attr_defs & defs,
+  firmwarebug_defs & firmwarebugs)
+{
   // get the drive's model/firmware strings
-  format_ata_string(model, (char *)drive->model, MODEL_STRING_LENGTH);
-  format_ata_string(firmware, (char *)drive->fw_rev, FIRMWARE_STRING_LENGTH);
-  
+  char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
+  ata_format_id_string(model, drive->model, sizeof(model)-1);
+  ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
+
   // Look up the drive in knowndrives[].
-  if ((i = lookupdrive(model, firmware)) >= 0) {
-    
-    // if vendoropts is non-NULL then Attribute interpretation presets
-    if (knowndrives[i].vendoropts) {
-      const unsigned char (* presets)[2];
-      
-      // For each attribute in list of attribute/val pairs...
-      presets = knowndrives[i].vendoropts;
-      while (1) {
-       const int attr = (*presets)[0];
-       const int val  = (*presets)[1];
-       
-       if (!attr)  
-         break;
-       
-       // ... set attribute if user hasn't already done so.
-       if (!opts[attr])
-         opts[attr] = val;
-       presets++;
+  const drive_settings * dbentry = lookup_drive(model, firmware);
+  if (!dbentry)
+    return 0;
+
+  if (*dbentry->presets) {
+    // Apply presets
+    if (!parse_presets(dbentry->presets, defs, firmwarebugs))
+      pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
+  }
+  return dbentry;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Parser for drive database files
+
+// Abstract pointer to read file input.
+// Operations supported: c = *p; c = p[1]; ++p;
+class stdin_iterator
+{
+public:
+  explicit stdin_iterator(FILE * f)
+    : m_f(f) { get(); get(); }
+
+  stdin_iterator & operator++()
+    { get(); return *this; }
+
+  char operator*() const
+    { return m_c; }
+
+  char operator[](int i) const
+    {
+      if (i != 1)
+        fail();
+      return m_next;
+    }
+
+private:
+  FILE * m_f;
+  char m_c, m_next;
+  void get();
+  void fail() const;
+};
+
+void stdin_iterator::get()
+{
+  m_c = m_next;
+  int ch = getc(m_f);
+  m_next = (ch != EOF ? ch : 0);
+}
+
+void stdin_iterator::fail() const
+{
+  throw std::runtime_error("stdin_iterator: wrong usage");
+}
+
+
+// Use above as parser input 'pointer'. Can easily be changed later
+// to e.g. 'const char *' if above is too slow.
+typedef stdin_iterator parse_ptr;
+
+// Skip whitespace and comments.
+static parse_ptr skip_white(parse_ptr src, const char * path, int & line)
+{
+  for ( ; ; ++src) switch (*src) {
+    case ' ': case '\t':
+      continue;
+
+    case '\n':
+      ++line;
+      continue;
+
+    case '/':
+      switch (src[1]) {
+        case '/':
+          // skip '// comment'
+          ++src; ++src;
+          while (*src && *src != '\n')
+            ++src;
+          if (*src)
+            ++line;
+          break;
+        case '*':
+          // skip '/* comment */'
+          ++src; ++src;
+          for (;;) {
+            if (!*src) {
+              pout("%s(%d): Missing '*/'\n", path, line);
+              return src;
+            }
+            char c = *src; ++src;
+            if (c == '\n')
+              ++line;
+            else if (c == '*' && *src == '/')
+              break;
+          }
+          break;
+        default:
+          return src;
       }
+      continue;
+
+    default:
+      return src;
+  }
+}
+
+// Info about a token.
+struct token_info
+{
+  char type;
+  int line;
+  std::string value;
+
+  token_info() : type(0), line(0) { }
+};
+
+// Get next token.
+static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line)
+{
+  src = skip_white(src, path, line);
+  switch (*src) {
+    case '{': case '}': case ',':
+      // Simple token
+      token.type = *src; token.line = line;
+      ++src;
+      break;
+
+    case '"':
+      // String constant
+      token.type = '"'; token.line = line;
+      token.value = "";
+      do {
+        for (++src; *src != '"'; ++src) {
+          char c = *src;
+          if (!c || c == '\n' || (c == '\\' && !src[1])) {
+            pout("%s(%d): Missing terminating '\"'\n", path, line);
+            token.type = '?'; token.line = line;
+            return src;
+          }
+          if (c == '\\') {
+            c = *++src;
+            switch (c) {
+              case 'n' : c = '\n'; break;
+              case '\n': ++line; break;
+              case '\\': case '"': break;
+              default:
+                pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c);
+                token.type = '?'; token.line = line;
+                continue;
+            }
+          }
+          token.value += c;
+        }
+        // Lookahead to detect string constant concatentation
+        src = skip_white(++src, path, line);
+      } while (*src == '"');
+      break;
+
+    case 0:
+      // EOF
+      token.type = 0; token.line = line;
+      break;
+
+    default:
+      pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src);
+      token.type = '?'; token.line = line;
+      while (*src && *src != '\n')
+        ++src;
+      break;
+  }
+
+  return src;
+}
+
+// Parse drive database from abstract input pointer.
+static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path)
+{
+  int state = 0, field = 0;
+  std::string values[5];
+  bool ok = true;
+
+  token_info token; int line = 1;
+  src = get_token(src, token, path, line);
+  for (;;) {
+    // EOF is ok after '}', trailing ',' is also allowed.
+    if (!token.type && (state == 0 || state == 4))
+      break;
+
+    // Check expected token
+    const char expect[] = "{\",},";
+    if (token.type != expect[state]) {
+      if (token.type != '?')
+        pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]);
+      ok = false;
+      // Skip to next entry
+      while (token.type && token.type != '{')
+        src = get_token(src, token, path, line);
+      state = 0;
+      if (token.type)
+        continue;
+      break;
+    }
+
+    // Interpret parser state
+    switch (state) {
+      case 0: // ... ^{...}
+        state = 1; field = 0;
+        break;
+      case 1: // {... ^"..." ...}
+        switch (field) {
+          case 1: case 2:
+            if (!token.value.empty()) {
+              regular_expression regex;
+              if (!regex.compile(token.value.c_str(), REG_EXTENDED)) {
+                pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg());
+                ok = false;
+              }
+            }
+            else if (field == 1) {
+              pout("%s(%d): Missing regular expression for drive model\n", path, token.line);
+              ok = false;
+            }
+            break;
+          case 4:
+            if (!token.value.empty()) {
+              if (!is_usb_modelfamily(values[0].c_str())) {
+                ata_vendor_attr_defs defs; firmwarebug_defs fix;
+                if (!parse_presets(token.value.c_str(), defs, fix)) {
+                  pout("%s(%d): Syntax error in preset option string\n", path, token.line);
+                  ok = false;
+                }
+              }
+              else {
+                std::string type;
+                if (!parse_usb_type(token.value.c_str(), type)) {
+                  pout("%s(%d): Syntax error in USB type string\n", path, token.line);
+                  ok = false;
+                }
+              }
+            }
+            break;
+        }
+        values[field] = token.value;
+        state = (++field < 5 ? 2 : 3);
+        break;
+      case 2: // {... "..."^, ...}
+        state = 1;
+        break;
+      case 3: // {...^}, ...
+        {
+          drive_settings entry;
+          entry.modelfamily    = values[0].c_str();
+          entry.modelregexp    = values[1].c_str();
+          entry.firmwareregexp = values[2].c_str();
+          entry.warningmsg     = values[3].c_str();
+          entry.presets        = values[4].c_str();
+          db.push_back(entry);
+        }
+        state = 4;
+        break;
+      case 4: // {...}^, ...
+        state = 0;
+        break;
+      default:
+        pout("Bad state %d\n", state);
+        return false;
     }
-    
-    // If a special-purpose function is defined for this drive then
-    // call it. Note that if command line arguments or Directives
-    // over-ride this choice, then the specialpurpose function that is
-    // called must deal with this.
-    if (knowndrives[i].specialpurpose)
-      (*knowndrives[i].specialpurpose)(con);
+    src = get_token(src, token, path, line);
   }
-  
-  // return <0 if drive wasn't recognized, or index>=0 into database
-  // if it was
-  return i;
+  return ok;
+}
+
+// Read drive database from file.
+bool read_drive_database(const char * path)
+{
+  stdio_file f(path, "r"
+#ifdef __CYGWIN__ // Allow files with '\r\n'.
+                      "t"
+#endif
+                         );
+  if (!f) {
+    pout("%s: cannot open drive database file\n", path);
+    return false;
+  }
+
+  return parse_drive_database(parse_ptr(f), knowndrives, path);
+}
+
+// Get path for additional database file
+const char * get_drivedb_path_add()
+{
+#ifndef _WIN32
+  return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h";
+#else
+  static std::string path = get_exe_dir() + "/drivedb-add.h";
+  return path.c_str();
+#endif
+}
+
+#ifdef SMARTMONTOOLS_DRIVEDBDIR
+
+// Get path for default database file
+const char * get_drivedb_path_default()
+{
+#ifndef _WIN32
+  return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h";
+#else
+  static std::string path = get_exe_dir() + "/drivedb.h";
+  return path.c_str();
+#endif
+}
+
+#endif
+
+// Read drive databases from standard places.
+bool read_default_drive_databases()
+{
+  // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h
+  const char * db1 = get_drivedb_path_add();
+  if (!access(db1, 0)) {
+    if (!read_drive_database(db1))
+      return false;
+  }
+
+#ifdef SMARTMONTOOLS_DRIVEDBDIR
+  // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h
+  const char * db2 = get_drivedb_path_default();
+  if (!access(db2, 0)) {
+    if (!read_drive_database(db2))
+      return false;
+  }
+  else
+#endif
+  {
+    // Append builtin table.
+    knowndrives.append(builtin_knowndrives,
+      sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0]));
+  }
+
+  return true;
 }