smartmontools SVN Rev 3317
Utility to control and monitor storage systems with "S.M.A.R.T."
knowndrives.cpp
Go to the documentation of this file.
00001 /*
00002  * knowndrives.cpp
00003  *
00004  * Home page of code is: http://smartmontools.sourceforge.net
00005  * Address of support mailing list: smartmontools-support@lists.sourceforge.net
00006  *
00007  * Copyright (C) 2003-11 Philip Williams, Bruce Allen
00008  * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
00009  *
00010  * This program is free software; you can redistribute it and/or modify
00011  * it under the terms of the GNU General Public License as published by
00012  * the Free Software Foundation; either version 2, or (at your option)
00013  * any later version.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
00017  *
00018  */
00019 
00020 #include "config.h"
00021 #include "int64.h"
00022 #include <stdio.h>
00023 #include "atacmds.h"
00024 #include "knowndrives.h"
00025 #include "utility.h"
00026 
00027 #ifdef HAVE_UNISTD_H
00028 #include <unistd.h>
00029 #endif
00030 #ifdef _WIN32
00031 #include <io.h> // access()
00032 #endif
00033 
00034 #include <stdexcept>
00035 
00036 const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3719 2012-12-03 21:19:33Z chrfranke $"
00037                                      KNOWNDRIVES_H_CVSID;
00038 
00039 #define MODEL_STRING_LENGTH                         40
00040 #define FIRMWARE_STRING_LENGTH                       8
00041 #define TABLEPRINTWIDTH                             19
00042 
00043 
00044 // Builtin table of known drives.
00045 // Used as a default if not read from
00046 // "/usr/{,/local}share/smartmontools/drivedb.h"
00047 // or any other file specified by '-B' option,
00048 // see read_default_drive_databases() below.
00049 // The drive_settings structure is described in drivedb.h.
00050 const drive_settings builtin_knowndrives[] = {
00051 #include "drivedb.h"
00052 };
00053 
00054 
00055 /// Drive database class. Stores custom entries read from file.
00056 /// Provides transparent access to concatenation of custom and
00057 /// default table.
00058 class drive_database
00059 {
00060 public:
00061   drive_database();
00062 
00063   ~drive_database();
00064 
00065   /// Get total number of entries.
00066   unsigned size() const
00067     { return m_custom_tab.size() + m_builtin_size; }
00068 
00069   /// Get number of custom entries.
00070   unsigned custom_size() const
00071     { return m_custom_tab.size(); }
00072 
00073   /// Array access.
00074   const drive_settings & operator[](unsigned i);
00075 
00076   /// Append new custom entry.
00077   void push_back(const drive_settings & src);
00078 
00079   /// Append builtin table.
00080   void append(const drive_settings * builtin_tab, unsigned builtin_size)
00081     { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; }
00082 
00083 private:
00084   const drive_settings * m_builtin_tab;
00085   unsigned m_builtin_size;
00086 
00087   std::vector<drive_settings> m_custom_tab;
00088   std::vector<char *> m_custom_strings;
00089 
00090   const char * copy_string(const char * str);
00091 
00092   drive_database(const drive_database &);
00093   void operator=(const drive_database &);
00094 };
00095 
00096 drive_database::drive_database()
00097 : m_builtin_tab(0), m_builtin_size(0)
00098 {
00099 }
00100 
00101 drive_database::~drive_database()
00102 {
00103   for (unsigned i = 0; i < m_custom_strings.size(); i++)
00104     delete [] m_custom_strings[i];
00105 }
00106 
00107 const drive_settings & drive_database::operator[](unsigned i)
00108 {
00109   return (i < m_custom_tab.size() ? m_custom_tab[i]
00110           : m_builtin_tab[i - m_custom_tab.size()] );
00111 }
00112 
00113 void drive_database::push_back(const drive_settings & src)
00114 {
00115   drive_settings dest;
00116   dest.modelfamily    = copy_string(src.modelfamily);
00117   dest.modelregexp    = copy_string(src.modelregexp);
00118   dest.firmwareregexp = copy_string(src.firmwareregexp);
00119   dest.warningmsg     = copy_string(src.warningmsg);
00120   dest.presets        = copy_string(src.presets);
00121   m_custom_tab.push_back(dest);
00122 }
00123 
00124 const char * drive_database::copy_string(const char * src)
00125 {
00126   size_t len = strlen(src);
00127   char * dest = new char[len+1];
00128   memcpy(dest, src, len+1);
00129   try {
00130     m_custom_strings.push_back(dest);
00131   }
00132   catch (...) {
00133     delete [] dest; throw;
00134   }
00135   return dest;
00136 }
00137 
00138 
00139 /// The drive database.
00140 static drive_database knowndrives;
00141 
00142 
00143 // Return true if modelfamily string describes entry for USB ID
00144 static bool is_usb_modelfamily(const char * modelfamily)
00145 {
00146   return !strncmp(modelfamily, "USB:", 4);
00147 }
00148 
00149 // Return true if entry for USB ID
00150 static inline bool is_usb_entry(const drive_settings * dbentry)
00151 {
00152   return is_usb_modelfamily(dbentry->modelfamily);
00153 }
00154 
00155 // Compile regular expression, print message on failure.
00156 static bool compile(regular_expression & regex, const char *pattern)
00157 {
00158   if (!regex.compile(pattern, REG_EXTENDED)) {
00159     pout("Internal error: unable to compile regular expression \"%s\": %s\n"
00160          "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n",
00161       pattern, regex.get_errmsg());
00162     return false;
00163   }
00164   return true;
00165 }
00166 
00167 // Compile & match a regular expression.
00168 static bool match(const char * pattern, const char * str)
00169 {
00170   regular_expression regex;
00171   if (!compile(regex, pattern))
00172     return false;
00173   return regex.full_match(str);
00174 }
00175 
00176 // Searches knowndrives[] for a drive with the given model number and firmware
00177 // string.  If either the drive's model or firmware strings are not set by the
00178 // manufacturer then values of NULL may be used.  Returns the entry of the
00179 // first match in knowndrives[] or 0 if no match if found.
00180 static const drive_settings * lookup_drive(const char * model, const char * firmware)
00181 {
00182   if (!model)
00183     model = "";
00184   if (!firmware)
00185     firmware = "";
00186 
00187   for (unsigned i = 0; i < knowndrives.size(); i++) {
00188     // Skip USB entries
00189     if (is_usb_entry(&knowndrives[i]))
00190       continue;
00191 
00192     // Check whether model matches the regular expression in knowndrives[i].
00193     if (!match(knowndrives[i].modelregexp, model))
00194       continue;
00195 
00196     // Model matches, now check firmware. "" matches always.
00197     if (!(  !*knowndrives[i].firmwareregexp
00198           || match(knowndrives[i].firmwareregexp, firmware)))
00199       continue;
00200 
00201     // Found
00202     return &knowndrives[i];
00203   }
00204 
00205   // Not found
00206   return 0;
00207 }
00208 
00209 
00210 // Parse drive or USB options in preset string, return false on error.
00211 static bool parse_db_presets(const char * presets, ata_vendor_attr_defs * defs,
00212                              firmwarebug_defs * firmwarebugs, std::string * type)
00213 {
00214   for (int i = 0; ; ) {
00215     i += strspn(presets+i, " \t");
00216     if (!presets[i])
00217       break;
00218     char opt, arg[80+1+13]; int len = -1;
00219     if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0))
00220       return false;
00221     if (opt == 'v' && defs) {
00222       // Parse "-v N,format[,name]"
00223       if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE))
00224         return false;
00225     }
00226     else if (opt == 'F' && firmwarebugs) {
00227       firmwarebug_defs bug;
00228       if (!parse_firmwarebug_def(arg, bug))
00229         return false;
00230       // Don't set if user specified '-F none'.
00231       if (!firmwarebugs->is_set(BUG_NONE))
00232         firmwarebugs->set(bug);
00233     }
00234     else if (opt == 'd' && type) {
00235         // TODO: Check valid types
00236         *type = arg;
00237     }
00238     else
00239       return false;
00240 
00241     i += len;
00242   }
00243   return true;
00244 }
00245 
00246 // Parse '-v' and '-F' options in preset string, return false on error.
00247 static inline bool parse_presets(const char * presets,
00248                                  ata_vendor_attr_defs & defs,
00249                                  firmwarebug_defs & firmwarebugs)
00250 {
00251   return parse_db_presets(presets, &defs, &firmwarebugs, 0);
00252 }
00253 
00254 // Parse '-d' option in preset string, return false on error.
00255 static inline bool parse_usb_type(const char * presets, std::string & type)
00256 {
00257   return parse_db_presets(presets, 0, 0, &type);
00258 }
00259 
00260 // Parse "USB: [DEVICE] ; [BRIDGE]" string
00261 static void parse_usb_names(const char * names, usb_dev_info & info)
00262 {
00263   int n1 = -1, n2 = -1, n3 = -1;
00264   sscanf(names, "USB: %n%*[^;]%n; %n", &n1, &n2, &n3);
00265   if (0 < n1 && n1 < n2)
00266     info.usb_device.assign(names+n1, n2-n1);
00267   else
00268     sscanf(names, "USB: ; %n", &n3);
00269   if (0 < n3)
00270     info.usb_bridge = names+n3;
00271 }
00272 
00273 // Search drivedb for USB device with vendor:product ID.
00274 int lookup_usb_device(int vendor_id, int product_id, int bcd_device,
00275                       usb_dev_info & info, usb_dev_info & info2)
00276 {
00277   // Format strings to match
00278   char usb_id_str[16], bcd_dev_str[16];
00279   snprintf(usb_id_str, sizeof(usb_id_str), "0x%04x:0x%04x", vendor_id, product_id);
00280   if (bcd_device >= 0)
00281     snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device);
00282   else
00283     bcd_dev_str[0] = 0;
00284 
00285   int found = 0;
00286   for (unsigned i = 0; i < knowndrives.size(); i++) {
00287     const drive_settings & dbentry = knowndrives[i];
00288 
00289     // Skip drive entries
00290     if (!is_usb_entry(&dbentry))
00291       continue;
00292 
00293     // Check whether USB vendor:product ID matches
00294     if (!match(dbentry.modelregexp, usb_id_str))
00295       continue;
00296 
00297     // Parse '-d type'
00298     usb_dev_info d;
00299     if (!parse_usb_type(dbentry.presets, d.usb_type))
00300       return 0; // Syntax error
00301     parse_usb_names(dbentry.modelfamily, d);
00302 
00303     // If two entries with same vendor:product ID have different
00304     // types, use bcd_device (if provided by OS) to select entry.
00305     if (  *dbentry.firmwareregexp && *bcd_dev_str
00306         && match(dbentry.firmwareregexp, bcd_dev_str)) {
00307       // Exact match including bcd_device
00308       info = d; found = 1;
00309       break;
00310     }
00311     else if (!found) {
00312       // First match without bcd_device
00313       info = d; found = 1;
00314     }
00315     else if (info.usb_type != d.usb_type) {
00316       // Another possible match with different type
00317       info2 = d; found = 2;
00318       break;
00319     }
00320 
00321     // Stop search at first matching entry with empty bcd_device
00322     if (!*dbentry.firmwareregexp)
00323       break;
00324   }
00325 
00326   return found;
00327 }
00328 
00329 // Shows one entry of knowndrives[], returns #errors.
00330 static int showonepreset(const drive_settings * dbentry)
00331 {
00332   // Basic error check
00333   if (!(   dbentry
00334         && dbentry->modelfamily
00335         && dbentry->modelregexp && *dbentry->modelregexp
00336         && dbentry->firmwareregexp
00337         && dbentry->warningmsg
00338         && dbentry->presets                             )) {
00339     pout("Invalid drive database entry. Please report\n"
00340          "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n");
00341     return 1;
00342   }
00343 
00344   bool usb = is_usb_entry(dbentry);
00345 
00346   // print and check model and firmware regular expressions
00347   int errcnt = 0;
00348   regular_expression regex;
00349   pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"),
00350        dbentry->modelregexp);
00351   if (!compile(regex, dbentry->modelregexp))
00352     errcnt++;
00353 
00354   pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"),
00355        *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change)
00356   if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp))
00357     errcnt++;
00358 
00359   if (!usb) {
00360     pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily);
00361 
00362     // if there are any presets, then show them
00363     firmwarebug_defs firmwarebugs;
00364     bool first_preset = true;
00365     if (*dbentry->presets) {
00366       ata_vendor_attr_defs defs;
00367       if (!parse_presets(dbentry->presets, defs, firmwarebugs)) {
00368         pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
00369         errcnt++;
00370       }
00371       for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) {
00372         if (defs[i].priority != PRIOR_DEFAULT) {
00373           std::string name = ata_get_smart_attr_name(i, defs);
00374           // Use leading zeros instead of spaces so that everything lines up.
00375           pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "",
00376                i, name.c_str());
00377           // Check max name length suitable for smartctl -A output
00378           const unsigned maxlen = 23;
00379           if (name.size() > maxlen) {
00380             pout("%*s\n", TABLEPRINTWIDTH+6+maxlen, "Error: Attribute name too long ------^");
00381             errcnt++;
00382           }
00383           first_preset = false;
00384         }
00385       }
00386     }
00387     if (first_preset)
00388       pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
00389 
00390     // describe firmwarefix
00391     for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) {
00392       if (!firmwarebugs.is_set((firmwarebug_t)b))
00393         continue;
00394       const char * fixdesc;
00395       switch ((firmwarebug_t)b) {
00396         case BUG_NOLOGDIR:
00397           fixdesc = "Avoids reading GP/SMART Log Directories (same as -F nologdir)";
00398           break;
00399         case BUG_SAMSUNG:
00400           fixdesc = "Fixes byte order in some SMART data (same as -F samsung)";
00401           break;
00402         case BUG_SAMSUNG2:
00403           fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)";
00404           break;
00405         case BUG_SAMSUNG3:
00406           fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)";
00407           break;
00408         case BUG_XERRORLBA:
00409           fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)";
00410           break;
00411         default:
00412           fixdesc = "UNKNOWN"; errcnt++;
00413           break;
00414       }
00415       pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc);
00416     }
00417   }
00418   else {
00419     // Print USB info
00420     usb_dev_info info; parse_usb_names(dbentry->modelfamily, info);
00421     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Device:",
00422       (!info.usb_device.empty() ? info.usb_device.c_str() : "[unknown]"));
00423     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Bridge:",
00424       (!info.usb_bridge.empty() ? info.usb_bridge.c_str() : "[unknown]"));
00425 
00426     if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) {
00427       pout("Syntax error in USB type string \"%s\"\n", dbentry->presets);
00428       errcnt++;
00429     }
00430     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type",
00431       (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]"));
00432   }
00433 
00434   // Print any special warnings
00435   if (*dbentry->warningmsg)
00436     pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg);
00437   return errcnt;
00438 }
00439 
00440 // Shows all presets for drives in knowndrives[].
00441 // Returns #syntax errors.
00442 int showallpresets()
00443 {
00444   // loop over all entries in the knowndrives[] table, printing them
00445   // out in a nice format
00446   int errcnt = 0;
00447   for (unsigned i = 0; i < knowndrives.size(); i++) {
00448     errcnt += showonepreset(&knowndrives[i]);
00449     pout("\n");
00450   }
00451 
00452   pout("Total number of entries  :%5u\n"
00453        "Entries read from file(s):%5u\n\n",
00454     knowndrives.size(), knowndrives.custom_size());
00455 
00456   pout("For information about adding a drive to the database see the FAQ on the\n");
00457   pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n");
00458 
00459   if (errcnt > 0)
00460     pout("\nFound %d syntax error(s) in database.\n"
00461          "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt);
00462   return errcnt;
00463 }
00464 
00465 // Shows all matching presets for a drive in knowndrives[].
00466 // Returns # matching entries.
00467 int showmatchingpresets(const char *model, const char *firmware)
00468 {
00469   int cnt = 0;
00470   const char * firmwaremsg = (firmware ? firmware : "(any)");
00471 
00472   for (unsigned i = 0; i < knowndrives.size(); i++) {
00473     if (!match(knowndrives[i].modelregexp, model))
00474       continue;
00475     if (   firmware && *knowndrives[i].firmwareregexp
00476         && !match(knowndrives[i].firmwareregexp, firmware))
00477         continue;
00478     // Found
00479     if (++cnt == 1)
00480       pout("Drive found in smartmontools Database.  Drive identity strings:\n"
00481            "%-*s %s\n"
00482            "%-*s %s\n"
00483            "match smartmontools Drive Database entry:\n",
00484            TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg);
00485     else if (cnt == 2)
00486       pout("and match these additional entries:\n");
00487     showonepreset(&knowndrives[i]);
00488     pout("\n");
00489   }
00490   if (cnt == 0)
00491     pout("No presets are defined for this drive.  Its identity strings:\n"
00492          "MODEL:    %s\n"
00493          "FIRMWARE: %s\n"
00494          "do not match any of the known regular expressions.\n",
00495          model, firmwaremsg);
00496   return cnt;
00497 }
00498 
00499 // Shows the presets (if any) that are available for the given drive.
00500 void show_presets(const ata_identify_device * drive)
00501 {
00502   char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
00503 
00504   // get the drive's model/firmware strings
00505   ata_format_id_string(model, drive->model, sizeof(model)-1);
00506   ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
00507 
00508   // and search to see if they match values in the table
00509   const drive_settings * dbentry = lookup_drive(model, firmware);
00510   if (!dbentry) {
00511     // no matches found
00512     pout("No presets are defined for this drive.  Its identity strings:\n"
00513          "MODEL:    %s\n"
00514          "FIRMWARE: %s\n"
00515          "do not match any of the known regular expressions.\n"
00516          "Use -P showall to list all known regular expressions.\n",
00517          model, firmware);
00518     return;
00519   }
00520   
00521   // We found a matching drive.  Print out all information about it.
00522   pout("Drive found in smartmontools Database.  Drive identity strings:\n"
00523        "%-*s %s\n"
00524        "%-*s %s\n"
00525        "match smartmontools Drive Database entry:\n",
00526        TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware);
00527   showonepreset(dbentry);
00528 }
00529 
00530 // Searches drive database and sets preset vendor attribute
00531 // options in defs and firmwarebugs.
00532 // Values that have already been set will not be changed.
00533 // Returns pointer to database entry or nullptr if none found
00534 const drive_settings * lookup_drive_apply_presets(
00535   const ata_identify_device * drive, ata_vendor_attr_defs & defs,
00536   firmwarebug_defs & firmwarebugs)
00537 {
00538   // get the drive's model/firmware strings
00539   char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
00540   ata_format_id_string(model, drive->model, sizeof(model)-1);
00541   ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
00542 
00543   // Look up the drive in knowndrives[].
00544   const drive_settings * dbentry = lookup_drive(model, firmware);
00545   if (!dbentry)
00546     return 0;
00547 
00548   if (*dbentry->presets) {
00549     // Apply presets
00550     if (!parse_presets(dbentry->presets, defs, firmwarebugs))
00551       pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
00552   }
00553   return dbentry;
00554 }
00555 
00556 
00557 /////////////////////////////////////////////////////////////////////////////
00558 // Parser for drive database files
00559 
00560 // Abstract pointer to read file input.
00561 // Operations supported: c = *p; c = p[1]; ++p;
00562 class stdin_iterator
00563 {
00564 public:
00565   explicit stdin_iterator(FILE * f)
00566     : m_f(f) { get(); get(); }
00567 
00568   stdin_iterator & operator++()
00569     { get(); return *this; }
00570 
00571   char operator*() const
00572     { return m_c; }
00573 
00574   char operator[](int i) const
00575     {
00576       if (i != 1)
00577         fail();
00578       return m_next;
00579     }
00580 
00581 private:
00582   FILE * m_f;
00583   char m_c, m_next;
00584   void get();
00585   void fail() const;
00586 };
00587 
00588 void stdin_iterator::get()
00589 {
00590   m_c = m_next;
00591   int ch = getc(m_f);
00592   m_next = (ch != EOF ? ch : 0);
00593 }
00594 
00595 void stdin_iterator::fail() const
00596 {
00597   throw std::runtime_error("stdin_iterator: wrong usage");
00598 }
00599 
00600 
00601 // Use above as parser input 'pointer'. Can easily be changed later
00602 // to e.g. 'const char *' if above is too slow.
00603 typedef stdin_iterator parse_ptr;
00604 
00605 // Skip whitespace and comments.
00606 static parse_ptr skip_white(parse_ptr src, const char * path, int & line)
00607 {
00608   for ( ; ; ++src) switch (*src) {
00609     case ' ': case '\t':
00610       continue;
00611 
00612     case '\n':
00613       ++line;
00614       continue;
00615 
00616     case '/':
00617       switch (src[1]) {
00618         case '/':
00619           // skip '// comment'
00620           ++src; ++src;
00621           while (*src && *src != '\n')
00622             ++src;
00623           if (*src)
00624             ++line;
00625           break;
00626         case '*':
00627           // skip '/* comment */'
00628           ++src; ++src;
00629           for (;;) {
00630             if (!*src) {
00631               pout("%s(%d): Missing '*/'\n", path, line);
00632               return src;
00633             }
00634             char c = *src; ++src;
00635             if (c == '\n')
00636               ++line;
00637             else if (c == '*' && *src == '/')
00638               break;
00639           }
00640           break;
00641         default:
00642           return src;
00643       }
00644       continue;
00645 
00646     default:
00647       return src;
00648   }
00649 }
00650 
00651 // Info about a token.
00652 struct token_info
00653 {
00654   char type;
00655   int line;
00656   std::string value;
00657 
00658   token_info() : type(0), line(0) { }
00659 };
00660 
00661 // Get next token.
00662 static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line)
00663 {
00664   src = skip_white(src, path, line);
00665   switch (*src) {
00666     case '{': case '}': case ',':
00667       // Simple token
00668       token.type = *src; token.line = line;
00669       ++src;
00670       break;
00671 
00672     case '"':
00673       // String constant
00674       token.type = '"'; token.line = line;
00675       token.value = "";
00676       do {
00677         for (++src; *src != '"'; ++src) {
00678           char c = *src;
00679           if (!c || c == '\n' || (c == '\\' && !src[1])) {
00680             pout("%s(%d): Missing terminating '\"'\n", path, line);
00681             token.type = '?'; token.line = line;
00682             return src;
00683           }
00684           if (c == '\\') {
00685             c = *++src;
00686             switch (c) {
00687               case 'n' : c = '\n'; break;
00688               case '\n': ++line; break;
00689               case '\\': case '"': break;
00690               default:
00691                 pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c);
00692                 token.type = '?'; token.line = line;
00693                 continue;
00694             }
00695           }
00696           token.value += c;
00697         }
00698         // Lookahead to detect string constant concatentation
00699         src = skip_white(++src, path, line);
00700       } while (*src == '"');
00701       break;
00702 
00703     case 0:
00704       // EOF
00705       token.type = 0; token.line = line;
00706       break;
00707 
00708     default:
00709       pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src);
00710       token.type = '?'; token.line = line;
00711       while (*src && *src != '\n')
00712         ++src;
00713       break;
00714   }
00715 
00716   return src;
00717 }
00718 
00719 // Parse drive database from abstract input pointer.
00720 static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path)
00721 {
00722   int state = 0, field = 0;
00723   std::string values[5];
00724   bool ok = true;
00725 
00726   token_info token; int line = 1;
00727   src = get_token(src, token, path, line);
00728   for (;;) {
00729     // EOF is ok after '}', trailing ',' is also allowed.
00730     if (!token.type && (state == 0 || state == 4))
00731       break;
00732 
00733     // Check expected token
00734     const char expect[] = "{\",},";
00735     if (token.type != expect[state]) {
00736       if (token.type != '?')
00737         pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]);
00738       ok = false;
00739       // Skip to next entry
00740       while (token.type && token.type != '{')
00741         src = get_token(src, token, path, line);
00742       state = 0;
00743       if (token.type)
00744         continue;
00745       break;
00746     }
00747 
00748     // Interpret parser state
00749     switch (state) {
00750       case 0: // ... ^{...}
00751         state = 1; field = 0;
00752         break;
00753       case 1: // {... ^"..." ...}
00754         switch (field) {
00755           case 1: case 2:
00756             if (!token.value.empty()) {
00757               regular_expression regex;
00758               if (!regex.compile(token.value.c_str(), REG_EXTENDED)) {
00759                 pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg());
00760                 ok = false;
00761               }
00762             }
00763             else if (field == 1) {
00764               pout("%s(%d): Missing regular expression for drive model\n", path, token.line);
00765               ok = false;
00766             }
00767             break;
00768           case 4:
00769             if (!token.value.empty()) {
00770               if (!is_usb_modelfamily(values[0].c_str())) {
00771                 ata_vendor_attr_defs defs; firmwarebug_defs fix;
00772                 if (!parse_presets(token.value.c_str(), defs, fix)) {
00773                   pout("%s(%d): Syntax error in preset option string\n", path, token.line);
00774                   ok = false;
00775                 }
00776               }
00777               else {
00778                 std::string type;
00779                 if (!parse_usb_type(token.value.c_str(), type)) {
00780                   pout("%s(%d): Syntax error in USB type string\n", path, token.line);
00781                   ok = false;
00782                 }
00783               }
00784             }
00785             break;
00786         }
00787         values[field] = token.value;
00788         state = (++field < 5 ? 2 : 3);
00789         break;
00790       case 2: // {... "..."^, ...}
00791         state = 1;
00792         break;
00793       case 3: // {...^}, ...
00794         {
00795           drive_settings entry;
00796           entry.modelfamily    = values[0].c_str();
00797           entry.modelregexp    = values[1].c_str();
00798           entry.firmwareregexp = values[2].c_str();
00799           entry.warningmsg     = values[3].c_str();
00800           entry.presets        = values[4].c_str();
00801           db.push_back(entry);
00802         }
00803         state = 4;
00804         break;
00805       case 4: // {...}^, ...
00806         state = 0;
00807         break;
00808       default:
00809         pout("Bad state %d\n", state);
00810         return false;
00811     }
00812     src = get_token(src, token, path, line);
00813   }
00814   return ok;
00815 }
00816 
00817 // Read drive database from file.
00818 bool read_drive_database(const char * path)
00819 {
00820   stdio_file f(path, "r"
00821 #ifdef __CYGWIN__ // Allow files with '\r\n'.
00822                       "t"
00823 #endif
00824                          );
00825   if (!f) {
00826     pout("%s: cannot open drive database file\n", path);
00827     return false;
00828   }
00829 
00830   return parse_drive_database(parse_ptr(f), knowndrives, path);
00831 }
00832 
00833 // Get path for additional database file
00834 const char * get_drivedb_path_add()
00835 {
00836 #ifndef _WIN32
00837   return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h";
00838 #else
00839   static std::string path = get_exe_dir() + "/drivedb-add.h";
00840   return path.c_str();
00841 #endif
00842 }
00843 
00844 #ifdef SMARTMONTOOLS_DRIVEDBDIR
00845 
00846 // Get path for default database file
00847 const char * get_drivedb_path_default()
00848 {
00849 #ifndef _WIN32
00850   return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h";
00851 #else
00852   static std::string path = get_exe_dir() + "/drivedb.h";
00853   return path.c_str();
00854 #endif
00855 }
00856 
00857 #endif
00858 
00859 // Read drive databases from standard places.
00860 bool read_default_drive_databases()
00861 {
00862   // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h
00863   const char * db1 = get_drivedb_path_add();
00864   if (!access(db1, 0)) {
00865     if (!read_drive_database(db1))
00866       return false;
00867   }
00868 
00869 #ifdef SMARTMONTOOLS_DRIVEDBDIR
00870   // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h
00871   const char * db2 = get_drivedb_path_default();
00872   if (!access(db2, 0)) {
00873     if (!read_drive_database(db2))
00874       return false;
00875   }
00876   else
00877 #endif
00878   {
00879     // Append builtin table.
00880     knowndrives.append(builtin_knowndrives,
00881       sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0]));
00882   }
00883 
00884   return true;
00885 }