Annotation of embedaddon/smartmontools/knowndrives.cpp, revision 1.1.1.2

1.1       misho       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
1.1.1.2 ! misho       8:  * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
1.1       misho       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
1.1.1.2 ! misho      16:  * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
1.1       misho      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: 
1.1.1.2 ! misho      36: const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3719 2012-12-03 21:19:33Z chrfranke $"
1.1       misho      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: {
1.1.1.2 ! misho     126:   size_t len = strlen(src);
        !           127:   char * dest = new char[len+1];
        !           128:   memcpy(dest, src, len+1);
1.1       misho     129:   try {
                    130:     m_custom_strings.push_back(dest);
                    131:   }
                    132:   catch (...) {
                    133:     delete [] dest; throw;
                    134:   }
1.1.1.2 ! misho     135:   return dest;
1.1       misho     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,
1.1.1.2 ! misho     212:                              firmwarebug_defs * firmwarebugs, std::string * type)
1.1       misho     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:     }
1.1.1.2 ! misho     226:     else if (opt == 'F' && firmwarebugs) {
        !           227:       firmwarebug_defs bug;
        !           228:       if (!parse_firmwarebug_def(arg, bug))
1.1       misho     229:         return false;
1.1.1.2 ! misho     230:       // Don't set if user specified '-F none'.
        !           231:       if (!firmwarebugs->is_set(BUG_NONE))
        !           232:         firmwarebugs->set(bug);
1.1       misho     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,
1.1.1.2 ! misho     249:                                  firmwarebug_defs & firmwarebugs)
1.1       misho     250: {
1.1.1.2 ! misho     251:   return parse_db_presets(presets, &defs, &firmwarebugs, 0);
1.1       misho     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
1.1.1.2 ! misho     363:     firmwarebug_defs firmwarebugs;
1.1       misho     364:     bool first_preset = true;
                    365:     if (*dbentry->presets) {
                    366:       ata_vendor_attr_defs defs;
1.1.1.2 ! misho     367:       if (!parse_presets(dbentry->presets, defs, firmwarebugs)) {
1.1       misho     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
1.1.1.2 ! misho     391:     for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) {
        !           392:       if (!firmwarebugs.is_set((firmwarebug_t)b))
        !           393:         continue;
1.1       misho     394:       const char * fixdesc;
1.1.1.2 ! misho     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:
1.1       misho     400:           fixdesc = "Fixes byte order in some SMART data (same as -F samsung)";
                    401:           break;
1.1.1.2 ! misho     402:         case BUG_SAMSUNG2:
1.1       misho     403:           fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)";
                    404:           break;
1.1.1.2 ! misho     405:         case BUG_SAMSUNG3:
1.1       misho     406:           fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)";
                    407:           break;
1.1.1.2 ! misho     408:         case BUG_XERRORLBA:
        !           409:           fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)";
        !           410:           break;
1.1       misho     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
1.1.1.2 ! misho     531: // options in defs and firmwarebugs.
1.1       misho     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,
1.1.1.2 ! misho     536:   firmwarebug_defs & firmwarebugs)
1.1       misho     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
1.1.1.2 ! misho     550:     if (!parse_presets(dbentry->presets, defs, firmwarebugs))
1.1       misho     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())) {
1.1.1.2 ! misho     771:                 ata_vendor_attr_defs defs; firmwarebug_defs fix;
1.1       misho     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>