File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / smartmontools / knowndrives.cpp
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 16:32:16 2012 UTC (12 years, 4 months ago) by misho
Branches: smartmontools, elwix, MAIN
CVS tags: v5_43, v5_42, HEAD
smartmontools

    1: /*
    2:  * knowndrives.cpp
    3:  *
    4:  * Home page of code is: http://smartmontools.sourceforge.net
    5:  * Address of support mailing list: smartmontools-support@lists.sourceforge.net
    6:  *
    7:  * Copyright (C) 2003-11 Philip Williams, Bruce Allen
    8:  * Copyright (C) 2008-11 Christian Franke <smartmontools-support@lists.sourceforge.net>
    9:  *
   10:  * This program is free software; you can redistribute it and/or modify
   11:  * it under the terms of the GNU General Public License as published by
   12:  * the Free Software Foundation; either version 2, or (at your option)
   13:  * any later version.
   14:  *
   15:  * You should have received a copy of the GNU General Public License
   16:  * (for example COPYING); if not, write to the Free
   17:  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   18:  *
   19:  */
   20: 
   21: #include "config.h"
   22: #include "int64.h"
   23: #include <stdio.h>
   24: #include "atacmds.h"
   25: #include "knowndrives.h"
   26: #include "utility.h"
   27: 
   28: #ifdef HAVE_UNISTD_H
   29: #include <unistd.h>
   30: #endif
   31: #ifdef _WIN32
   32: #include <io.h> // access()
   33: #endif
   34: 
   35: #include <stdexcept>
   36: 
   37: const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp,v 1.1.1.1 2012/02/21 16:32:16 misho Exp $"
   38:                                      KNOWNDRIVES_H_CVSID;
   39: 
   40: #define MODEL_STRING_LENGTH                         40
   41: #define FIRMWARE_STRING_LENGTH                       8
   42: #define TABLEPRINTWIDTH                             19
   43: 
   44: 
   45: // Builtin table of known drives.
   46: // Used as a default if not read from
   47: // "/usr/{,/local}share/smartmontools/drivedb.h"
   48: // or any other file specified by '-B' option,
   49: // see read_default_drive_databases() below.
   50: // The drive_settings structure is described in drivedb.h.
   51: const drive_settings builtin_knowndrives[] = {
   52: #include "drivedb.h"
   53: };
   54: 
   55: 
   56: /// Drive database class. Stores custom entries read from file.
   57: /// Provides transparent access to concatenation of custom and
   58: /// default table.
   59: class drive_database
   60: {
   61: public:
   62:   drive_database();
   63: 
   64:   ~drive_database();
   65: 
   66:   /// Get total number of entries.
   67:   unsigned size() const
   68:     { return m_custom_tab.size() + m_builtin_size; }
   69: 
   70:   /// Get number of custom entries.
   71:   unsigned custom_size() const
   72:     { return m_custom_tab.size(); }
   73: 
   74:   /// Array access.
   75:   const drive_settings & operator[](unsigned i);
   76: 
   77:   /// Append new custom entry.
   78:   void push_back(const drive_settings & src);
   79: 
   80:   /// Append builtin table.
   81:   void append(const drive_settings * builtin_tab, unsigned builtin_size)
   82:     { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; }
   83: 
   84: private:
   85:   const drive_settings * m_builtin_tab;
   86:   unsigned m_builtin_size;
   87: 
   88:   std::vector<drive_settings> m_custom_tab;
   89:   std::vector<char *> m_custom_strings;
   90: 
   91:   const char * copy_string(const char * str);
   92: 
   93:   drive_database(const drive_database &);
   94:   void operator=(const drive_database &);
   95: };
   96: 
   97: drive_database::drive_database()
   98: : m_builtin_tab(0), m_builtin_size(0)
   99: {
  100: }
  101: 
  102: drive_database::~drive_database()
  103: {
  104:   for (unsigned i = 0; i < m_custom_strings.size(); i++)
  105:     delete [] m_custom_strings[i];
  106: }
  107: 
  108: const drive_settings & drive_database::operator[](unsigned i)
  109: {
  110:   return (i < m_custom_tab.size() ? m_custom_tab[i]
  111:           : m_builtin_tab[i - m_custom_tab.size()] );
  112: }
  113: 
  114: void drive_database::push_back(const drive_settings & src)
  115: {
  116:   drive_settings dest;
  117:   dest.modelfamily    = copy_string(src.modelfamily);
  118:   dest.modelregexp    = copy_string(src.modelregexp);
  119:   dest.firmwareregexp = copy_string(src.firmwareregexp);
  120:   dest.warningmsg     = copy_string(src.warningmsg);
  121:   dest.presets        = copy_string(src.presets);
  122:   m_custom_tab.push_back(dest);
  123: }
  124: 
  125: const char * drive_database::copy_string(const char * src)
  126: {
  127:   char * dest = new char[strlen(src)+1];
  128:   try {
  129:     m_custom_strings.push_back(dest);
  130:   }
  131:   catch (...) {
  132:     delete [] dest; throw;
  133:   }
  134:   return strcpy(dest, src);
  135: }
  136: 
  137: 
  138: /// The drive database.
  139: static drive_database knowndrives;
  140: 
  141: 
  142: // Return true if modelfamily string describes entry for USB ID
  143: static bool is_usb_modelfamily(const char * modelfamily)
  144: {
  145:   return !strncmp(modelfamily, "USB:", 4);
  146: }
  147: 
  148: // Return true if entry for USB ID
  149: static inline bool is_usb_entry(const drive_settings * dbentry)
  150: {
  151:   return is_usb_modelfamily(dbentry->modelfamily);
  152: }
  153: 
  154: // Compile regular expression, print message on failure.
  155: static bool compile(regular_expression & regex, const char *pattern)
  156: {
  157:   if (!regex.compile(pattern, REG_EXTENDED)) {
  158:     pout("Internal error: unable to compile regular expression \"%s\": %s\n"
  159:          "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n",
  160:       pattern, regex.get_errmsg());
  161:     return false;
  162:   }
  163:   return true;
  164: }
  165: 
  166: // Compile & match a regular expression.
  167: static bool match(const char * pattern, const char * str)
  168: {
  169:   regular_expression regex;
  170:   if (!compile(regex, pattern))
  171:     return false;
  172:   return regex.full_match(str);
  173: }
  174: 
  175: // Searches knowndrives[] for a drive with the given model number and firmware
  176: // string.  If either the drive's model or firmware strings are not set by the
  177: // manufacturer then values of NULL may be used.  Returns the entry of the
  178: // first match in knowndrives[] or 0 if no match if found.
  179: static const drive_settings * lookup_drive(const char * model, const char * firmware)
  180: {
  181:   if (!model)
  182:     model = "";
  183:   if (!firmware)
  184:     firmware = "";
  185: 
  186:   for (unsigned i = 0; i < knowndrives.size(); i++) {
  187:     // Skip USB entries
  188:     if (is_usb_entry(&knowndrives[i]))
  189:       continue;
  190: 
  191:     // Check whether model matches the regular expression in knowndrives[i].
  192:     if (!match(knowndrives[i].modelregexp, model))
  193:       continue;
  194: 
  195:     // Model matches, now check firmware. "" matches always.
  196:     if (!(  !*knowndrives[i].firmwareregexp
  197:           || match(knowndrives[i].firmwareregexp, firmware)))
  198:       continue;
  199: 
  200:     // Found
  201:     return &knowndrives[i];
  202:   }
  203: 
  204:   // Not found
  205:   return 0;
  206: }
  207: 
  208: 
  209: // Parse drive or USB options in preset string, return false on error.
  210: static bool parse_db_presets(const char * presets, ata_vendor_attr_defs * defs,
  211:                              unsigned char * fix_firmwarebug, std::string * type)
  212: {
  213:   for (int i = 0; ; ) {
  214:     i += strspn(presets+i, " \t");
  215:     if (!presets[i])
  216:       break;
  217:     char opt, arg[80+1+13]; int len = -1;
  218:     if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0))
  219:       return false;
  220:     if (opt == 'v' && defs) {
  221:       // Parse "-v N,format[,name]"
  222:       if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE))
  223:         return false;
  224:     }
  225:     else if (opt == 'F' && fix_firmwarebug) {
  226:       unsigned char fix;
  227:       if (!strcmp(arg, "samsung"))
  228:         fix = FIX_SAMSUNG;
  229:       else if (!strcmp(arg, "samsung2"))
  230:         fix = FIX_SAMSUNG2;
  231:       else if (!strcmp(arg, "samsung3"))
  232:         fix = FIX_SAMSUNG3;
  233:       else
  234:         return false;
  235:       // Set only if not set by user
  236:       if (*fix_firmwarebug == FIX_NOTSPECIFIED)
  237:         *fix_firmwarebug = fix;
  238:     }
  239:     else if (opt == 'd' && type) {
  240:         // TODO: Check valid types
  241:         *type = arg;
  242:     }
  243:     else
  244:       return false;
  245: 
  246:     i += len;
  247:   }
  248:   return true;
  249: }
  250: 
  251: // Parse '-v' and '-F' options in preset string, return false on error.
  252: static inline bool parse_presets(const char * presets,
  253:                                  ata_vendor_attr_defs & defs,
  254:                                  unsigned char & fix_firmwarebug)
  255: {
  256:   return parse_db_presets(presets, &defs, &fix_firmwarebug, 0);
  257: }
  258: 
  259: // Parse '-d' option in preset string, return false on error.
  260: static inline bool parse_usb_type(const char * presets, std::string & type)
  261: {
  262:   return parse_db_presets(presets, 0, 0, &type);
  263: }
  264: 
  265: // Parse "USB: [DEVICE] ; [BRIDGE]" string
  266: static void parse_usb_names(const char * names, usb_dev_info & info)
  267: {
  268:   int n1 = -1, n2 = -1, n3 = -1;
  269:   sscanf(names, "USB: %n%*[^;]%n; %n", &n1, &n2, &n3);
  270:   if (0 < n1 && n1 < n2)
  271:     info.usb_device.assign(names+n1, n2-n1);
  272:   else
  273:     sscanf(names, "USB: ; %n", &n3);
  274:   if (0 < n3)
  275:     info.usb_bridge = names+n3;
  276: }
  277: 
  278: // Search drivedb for USB device with vendor:product ID.
  279: int lookup_usb_device(int vendor_id, int product_id, int bcd_device,
  280:                       usb_dev_info & info, usb_dev_info & info2)
  281: {
  282:   // Format strings to match
  283:   char usb_id_str[16], bcd_dev_str[16];
  284:   snprintf(usb_id_str, sizeof(usb_id_str), "0x%04x:0x%04x", vendor_id, product_id);
  285:   if (bcd_device >= 0)
  286:     snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device);
  287:   else
  288:     bcd_dev_str[0] = 0;
  289: 
  290:   int found = 0;
  291:   for (unsigned i = 0; i < knowndrives.size(); i++) {
  292:     const drive_settings & dbentry = knowndrives[i];
  293: 
  294:     // Skip drive entries
  295:     if (!is_usb_entry(&dbentry))
  296:       continue;
  297: 
  298:     // Check whether USB vendor:product ID matches
  299:     if (!match(dbentry.modelregexp, usb_id_str))
  300:       continue;
  301: 
  302:     // Parse '-d type'
  303:     usb_dev_info d;
  304:     if (!parse_usb_type(dbentry.presets, d.usb_type))
  305:       return 0; // Syntax error
  306:     parse_usb_names(dbentry.modelfamily, d);
  307: 
  308:     // If two entries with same vendor:product ID have different
  309:     // types, use bcd_device (if provided by OS) to select entry.
  310:     if (  *dbentry.firmwareregexp && *bcd_dev_str
  311:         && match(dbentry.firmwareregexp, bcd_dev_str)) {
  312:       // Exact match including bcd_device
  313:       info = d; found = 1;
  314:       break;
  315:     }
  316:     else if (!found) {
  317:       // First match without bcd_device
  318:       info = d; found = 1;
  319:     }
  320:     else if (info.usb_type != d.usb_type) {
  321:       // Another possible match with different type
  322:       info2 = d; found = 2;
  323:       break;
  324:     }
  325: 
  326:     // Stop search at first matching entry with empty bcd_device
  327:     if (!*dbentry.firmwareregexp)
  328:       break;
  329:   }
  330: 
  331:   return found;
  332: }
  333: 
  334: // Shows one entry of knowndrives[], returns #errors.
  335: static int showonepreset(const drive_settings * dbentry)
  336: {
  337:   // Basic error check
  338:   if (!(   dbentry
  339:         && dbentry->modelfamily
  340:         && dbentry->modelregexp && *dbentry->modelregexp
  341:         && dbentry->firmwareregexp
  342:         && dbentry->warningmsg
  343:         && dbentry->presets                             )) {
  344:     pout("Invalid drive database entry. Please report\n"
  345:          "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n");
  346:     return 1;
  347:   }
  348: 
  349:   bool usb = is_usb_entry(dbentry);
  350: 
  351:   // print and check model and firmware regular expressions
  352:   int errcnt = 0;
  353:   regular_expression regex;
  354:   pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"),
  355:        dbentry->modelregexp);
  356:   if (!compile(regex, dbentry->modelregexp))
  357:     errcnt++;
  358: 
  359:   pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"),
  360:        *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change)
  361:   if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp))
  362:     errcnt++;
  363: 
  364:   if (!usb) {
  365:     pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily);
  366: 
  367:     // if there are any presets, then show them
  368:     unsigned char fix_firmwarebug = 0;
  369:     bool first_preset = true;
  370:     if (*dbentry->presets) {
  371:       ata_vendor_attr_defs defs;
  372:       if (!parse_presets(dbentry->presets, defs, fix_firmwarebug)) {
  373:         pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
  374:         errcnt++;
  375:       }
  376:       for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) {
  377:         if (defs[i].priority != PRIOR_DEFAULT) {
  378:           std::string name = ata_get_smart_attr_name(i, defs);
  379:           // Use leading zeros instead of spaces so that everything lines up.
  380:           pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "",
  381:                i, name.c_str());
  382:           // Check max name length suitable for smartctl -A output
  383:           const unsigned maxlen = 23;
  384:           if (name.size() > maxlen) {
  385:             pout("%*s\n", TABLEPRINTWIDTH+6+maxlen, "Error: Attribute name too long ------^");
  386:             errcnt++;
  387:           }
  388:           first_preset = false;
  389:         }
  390:       }
  391:     }
  392:     if (first_preset)
  393:       pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
  394: 
  395:     // describe firmwarefix
  396:     if (fix_firmwarebug) {
  397:       const char * fixdesc;
  398:       switch (fix_firmwarebug) {
  399:         case FIX_SAMSUNG:
  400:           fixdesc = "Fixes byte order in some SMART data (same as -F samsung)";
  401:           break;
  402:         case FIX_SAMSUNG2:
  403:           fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)";
  404:           break;
  405:         case FIX_SAMSUNG3:
  406:           fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)";
  407:           break;
  408:         default:
  409:           fixdesc = "UNKNOWN"; errcnt++;
  410:           break;
  411:       }
  412:       pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc);
  413:     }
  414:   }
  415:   else {
  416:     // Print USB info
  417:     usb_dev_info info; parse_usb_names(dbentry->modelfamily, info);
  418:     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Device:",
  419:       (!info.usb_device.empty() ? info.usb_device.c_str() : "[unknown]"));
  420:     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Bridge:",
  421:       (!info.usb_bridge.empty() ? info.usb_bridge.c_str() : "[unknown]"));
  422: 
  423:     if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) {
  424:       pout("Syntax error in USB type string \"%s\"\n", dbentry->presets);
  425:       errcnt++;
  426:     }
  427:     pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type",
  428:       (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]"));
  429:   }
  430: 
  431:   // Print any special warnings
  432:   if (*dbentry->warningmsg)
  433:     pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg);
  434:   return errcnt;
  435: }
  436: 
  437: // Shows all presets for drives in knowndrives[].
  438: // Returns #syntax errors.
  439: int showallpresets()
  440: {
  441:   // loop over all entries in the knowndrives[] table, printing them
  442:   // out in a nice format
  443:   int errcnt = 0;
  444:   for (unsigned i = 0; i < knowndrives.size(); i++) {
  445:     errcnt += showonepreset(&knowndrives[i]);
  446:     pout("\n");
  447:   }
  448: 
  449:   pout("Total number of entries  :%5u\n"
  450:        "Entries read from file(s):%5u\n\n",
  451:     knowndrives.size(), knowndrives.custom_size());
  452: 
  453:   pout("For information about adding a drive to the database see the FAQ on the\n");
  454:   pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n");
  455: 
  456:   if (errcnt > 0)
  457:     pout("\nFound %d syntax error(s) in database.\n"
  458:          "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt);
  459:   return errcnt;
  460: }
  461: 
  462: // Shows all matching presets for a drive in knowndrives[].
  463: // Returns # matching entries.
  464: int showmatchingpresets(const char *model, const char *firmware)
  465: {
  466:   int cnt = 0;
  467:   const char * firmwaremsg = (firmware ? firmware : "(any)");
  468: 
  469:   for (unsigned i = 0; i < knowndrives.size(); i++) {
  470:     if (!match(knowndrives[i].modelregexp, model))
  471:       continue;
  472:     if (   firmware && *knowndrives[i].firmwareregexp
  473:         && !match(knowndrives[i].firmwareregexp, firmware))
  474:         continue;
  475:     // Found
  476:     if (++cnt == 1)
  477:       pout("Drive found in smartmontools Database.  Drive identity strings:\n"
  478:            "%-*s %s\n"
  479:            "%-*s %s\n"
  480:            "match smartmontools Drive Database entry:\n",
  481:            TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg);
  482:     else if (cnt == 2)
  483:       pout("and match these additional entries:\n");
  484:     showonepreset(&knowndrives[i]);
  485:     pout("\n");
  486:   }
  487:   if (cnt == 0)
  488:     pout("No presets are defined for this drive.  Its identity strings:\n"
  489:          "MODEL:    %s\n"
  490:          "FIRMWARE: %s\n"
  491:          "do not match any of the known regular expressions.\n",
  492:          model, firmwaremsg);
  493:   return cnt;
  494: }
  495: 
  496: // Shows the presets (if any) that are available for the given drive.
  497: void show_presets(const ata_identify_device * drive)
  498: {
  499:   char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
  500: 
  501:   // get the drive's model/firmware strings
  502:   ata_format_id_string(model, drive->model, sizeof(model)-1);
  503:   ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
  504: 
  505:   // and search to see if they match values in the table
  506:   const drive_settings * dbentry = lookup_drive(model, firmware);
  507:   if (!dbentry) {
  508:     // no matches found
  509:     pout("No presets are defined for this drive.  Its identity strings:\n"
  510:          "MODEL:    %s\n"
  511:          "FIRMWARE: %s\n"
  512:          "do not match any of the known regular expressions.\n"
  513:          "Use -P showall to list all known regular expressions.\n",
  514:          model, firmware);
  515:     return;
  516:   }
  517:   
  518:   // We found a matching drive.  Print out all information about it.
  519:   pout("Drive found in smartmontools Database.  Drive identity strings:\n"
  520:        "%-*s %s\n"
  521:        "%-*s %s\n"
  522:        "match smartmontools Drive Database entry:\n",
  523:        TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware);
  524:   showonepreset(dbentry);
  525: }
  526: 
  527: // Searches drive database and sets preset vendor attribute
  528: // options in defs and fix_firmwarebug.
  529: // Values that have already been set will not be changed.
  530: // Returns pointer to database entry or nullptr if none found
  531: const drive_settings * lookup_drive_apply_presets(
  532:   const ata_identify_device * drive, ata_vendor_attr_defs & defs,
  533:   unsigned char & fix_firmwarebug)
  534: {
  535:   // get the drive's model/firmware strings
  536:   char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1];
  537:   ata_format_id_string(model, drive->model, sizeof(model)-1);
  538:   ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1);
  539: 
  540:   // Look up the drive in knowndrives[].
  541:   const drive_settings * dbentry = lookup_drive(model, firmware);
  542:   if (!dbentry)
  543:     return 0;
  544: 
  545:   if (*dbentry->presets) {
  546:     // Apply presets
  547:     if (!parse_presets(dbentry->presets, defs, fix_firmwarebug))
  548:       pout("Syntax error in preset option string \"%s\"\n", dbentry->presets);
  549:   }
  550:   return dbentry;
  551: }
  552: 
  553: 
  554: /////////////////////////////////////////////////////////////////////////////
  555: // Parser for drive database files
  556: 
  557: // Abstract pointer to read file input.
  558: // Operations supported: c = *p; c = p[1]; ++p;
  559: class stdin_iterator
  560: {
  561: public:
  562:   explicit stdin_iterator(FILE * f)
  563:     : m_f(f) { get(); get(); }
  564: 
  565:   stdin_iterator & operator++()
  566:     { get(); return *this; }
  567: 
  568:   char operator*() const
  569:     { return m_c; }
  570: 
  571:   char operator[](int i) const
  572:     {
  573:       if (i != 1)
  574:         fail();
  575:       return m_next;
  576:     }
  577: 
  578: private:
  579:   FILE * m_f;
  580:   char m_c, m_next;
  581:   void get();
  582:   void fail() const;
  583: };
  584: 
  585: void stdin_iterator::get()
  586: {
  587:   m_c = m_next;
  588:   int ch = getc(m_f);
  589:   m_next = (ch != EOF ? ch : 0);
  590: }
  591: 
  592: void stdin_iterator::fail() const
  593: {
  594:   throw std::runtime_error("stdin_iterator: wrong usage");
  595: }
  596: 
  597: 
  598: // Use above as parser input 'pointer'. Can easily be changed later
  599: // to e.g. 'const char *' if above is too slow.
  600: typedef stdin_iterator parse_ptr;
  601: 
  602: // Skip whitespace and comments.
  603: static parse_ptr skip_white(parse_ptr src, const char * path, int & line)
  604: {
  605:   for ( ; ; ++src) switch (*src) {
  606:     case ' ': case '\t':
  607:       continue;
  608: 
  609:     case '\n':
  610:       ++line;
  611:       continue;
  612: 
  613:     case '/':
  614:       switch (src[1]) {
  615:         case '/':
  616:           // skip '// comment'
  617:           ++src; ++src;
  618:           while (*src && *src != '\n')
  619:             ++src;
  620:           if (*src)
  621:             ++line;
  622:           break;
  623:         case '*':
  624:           // skip '/* comment */'
  625:           ++src; ++src;
  626:           for (;;) {
  627:             if (!*src) {
  628:               pout("%s(%d): Missing '*/'\n", path, line);
  629:               return src;
  630:             }
  631:             char c = *src; ++src;
  632:             if (c == '\n')
  633:               ++line;
  634:             else if (c == '*' && *src == '/')
  635:               break;
  636:           }
  637:           break;
  638:         default:
  639:           return src;
  640:       }
  641:       continue;
  642: 
  643:     default:
  644:       return src;
  645:   }
  646: }
  647: 
  648: // Info about a token.
  649: struct token_info
  650: {
  651:   char type;
  652:   int line;
  653:   std::string value;
  654: 
  655:   token_info() : type(0), line(0) { }
  656: };
  657: 
  658: // Get next token.
  659: static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line)
  660: {
  661:   src = skip_white(src, path, line);
  662:   switch (*src) {
  663:     case '{': case '}': case ',':
  664:       // Simple token
  665:       token.type = *src; token.line = line;
  666:       ++src;
  667:       break;
  668: 
  669:     case '"':
  670:       // String constant
  671:       token.type = '"'; token.line = line;
  672:       token.value = "";
  673:       do {
  674:         for (++src; *src != '"'; ++src) {
  675:           char c = *src;
  676:           if (!c || c == '\n' || (c == '\\' && !src[1])) {
  677:             pout("%s(%d): Missing terminating '\"'\n", path, line);
  678:             token.type = '?'; token.line = line;
  679:             return src;
  680:           }
  681:           if (c == '\\') {
  682:             c = *++src;
  683:             switch (c) {
  684:               case 'n' : c = '\n'; break;
  685:               case '\n': ++line; break;
  686:               case '\\': case '"': break;
  687:               default:
  688:                 pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c);
  689:                 token.type = '?'; token.line = line;
  690:                 continue;
  691:             }
  692:           }
  693:           token.value += c;
  694:         }
  695:         // Lookahead to detect string constant concatentation
  696:         src = skip_white(++src, path, line);
  697:       } while (*src == '"');
  698:       break;
  699: 
  700:     case 0:
  701:       // EOF
  702:       token.type = 0; token.line = line;
  703:       break;
  704: 
  705:     default:
  706:       pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src);
  707:       token.type = '?'; token.line = line;
  708:       while (*src && *src != '\n')
  709:         ++src;
  710:       break;
  711:   }
  712: 
  713:   return src;
  714: }
  715: 
  716: // Parse drive database from abstract input pointer.
  717: static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path)
  718: {
  719:   int state = 0, field = 0;
  720:   std::string values[5];
  721:   bool ok = true;
  722: 
  723:   token_info token; int line = 1;
  724:   src = get_token(src, token, path, line);
  725:   for (;;) {
  726:     // EOF is ok after '}', trailing ',' is also allowed.
  727:     if (!token.type && (state == 0 || state == 4))
  728:       break;
  729: 
  730:     // Check expected token
  731:     const char expect[] = "{\",},";
  732:     if (token.type != expect[state]) {
  733:       if (token.type != '?')
  734:         pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]);
  735:       ok = false;
  736:       // Skip to next entry
  737:       while (token.type && token.type != '{')
  738:         src = get_token(src, token, path, line);
  739:       state = 0;
  740:       if (token.type)
  741:         continue;
  742:       break;
  743:     }
  744: 
  745:     // Interpret parser state
  746:     switch (state) {
  747:       case 0: // ... ^{...}
  748:         state = 1; field = 0;
  749:         break;
  750:       case 1: // {... ^"..." ...}
  751:         switch (field) {
  752:           case 1: case 2:
  753:             if (!token.value.empty()) {
  754:               regular_expression regex;
  755:               if (!regex.compile(token.value.c_str(), REG_EXTENDED)) {
  756:                 pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg());
  757:                 ok = false;
  758:               }
  759:             }
  760:             else if (field == 1) {
  761:               pout("%s(%d): Missing regular expression for drive model\n", path, token.line);
  762:               ok = false;
  763:             }
  764:             break;
  765:           case 4:
  766:             if (!token.value.empty()) {
  767:               if (!is_usb_modelfamily(values[0].c_str())) {
  768:                 ata_vendor_attr_defs defs; unsigned char fix = 0;
  769:                 if (!parse_presets(token.value.c_str(), defs, fix)) {
  770:                   pout("%s(%d): Syntax error in preset option string\n", path, token.line);
  771:                   ok = false;
  772:                 }
  773:               }
  774:               else {
  775:                 std::string type;
  776:                 if (!parse_usb_type(token.value.c_str(), type)) {
  777:                   pout("%s(%d): Syntax error in USB type string\n", path, token.line);
  778:                   ok = false;
  779:                 }
  780:               }
  781:             }
  782:             break;
  783:         }
  784:         values[field] = token.value;
  785:         state = (++field < 5 ? 2 : 3);
  786:         break;
  787:       case 2: // {... "..."^, ...}
  788:         state = 1;
  789:         break;
  790:       case 3: // {...^}, ...
  791:         {
  792:           drive_settings entry;
  793:           entry.modelfamily    = values[0].c_str();
  794:           entry.modelregexp    = values[1].c_str();
  795:           entry.firmwareregexp = values[2].c_str();
  796:           entry.warningmsg     = values[3].c_str();
  797:           entry.presets        = values[4].c_str();
  798:           db.push_back(entry);
  799:         }
  800:         state = 4;
  801:         break;
  802:       case 4: // {...}^, ...
  803:         state = 0;
  804:         break;
  805:       default:
  806:         pout("Bad state %d\n", state);
  807:         return false;
  808:     }
  809:     src = get_token(src, token, path, line);
  810:   }
  811:   return ok;
  812: }
  813: 
  814: // Read drive database from file.
  815: bool read_drive_database(const char * path)
  816: {
  817:   stdio_file f(path, "r"
  818: #ifdef __CYGWIN__ // Allow files with '\r\n'.
  819:                       "t"
  820: #endif
  821:                          );
  822:   if (!f) {
  823:     pout("%s: cannot open drive database file\n", path);
  824:     return false;
  825:   }
  826: 
  827:   return parse_drive_database(parse_ptr(f), knowndrives, path);
  828: }
  829: 
  830: // Get path for additional database file
  831: const char * get_drivedb_path_add()
  832: {
  833: #ifndef _WIN32
  834:   return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h";
  835: #else
  836:   static std::string path = get_exe_dir() + "/drivedb-add.h";
  837:   return path.c_str();
  838: #endif
  839: }
  840: 
  841: #ifdef SMARTMONTOOLS_DRIVEDBDIR
  842: 
  843: // Get path for default database file
  844: const char * get_drivedb_path_default()
  845: {
  846: #ifndef _WIN32
  847:   return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h";
  848: #else
  849:   static std::string path = get_exe_dir() + "/drivedb.h";
  850:   return path.c_str();
  851: #endif
  852: }
  853: 
  854: #endif
  855: 
  856: // Read drive databases from standard places.
  857: bool read_default_drive_databases()
  858: {
  859:   // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h
  860:   const char * db1 = get_drivedb_path_add();
  861:   if (!access(db1, 0)) {
  862:     if (!read_drive_database(db1))
  863:       return false;
  864:   }
  865: 
  866: #ifdef SMARTMONTOOLS_DRIVEDBDIR
  867:   // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h
  868:   const char * db2 = get_drivedb_path_default();
  869:   if (!access(db2, 0)) {
  870:     if (!read_drive_database(db2))
  871:       return false;
  872:   }
  873:   else
  874: #endif
  875:   {
  876:     // Append builtin table.
  877:     knowndrives.append(builtin_knowndrives,
  878:       sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0]));
  879:   }
  880: 
  881:   return true;
  882: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>