File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / smartmontools / knowndrives.cpp
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Jul 22 01:17:35 2013 UTC (10 years, 10 months ago) by misho
Branches: smartmontools, elwix, MAIN
CVS tags: v6_2, v6_1p0, v6_1, HEAD
6.1

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

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