/* * knowndrives.cpp * * Home page of code is: http://smartmontools.sourceforge.net * Address of support mailing list: smartmontools-support@lists.sourceforge.net * * Copyright (C) 2003-11 Philip Williams, Bruce Allen * Copyright (C) 2008-12 Christian Franke * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * You should have received a copy of the GNU General Public License * (for example COPYING); If not, see . * */ #include "config.h" #include "int64.h" #include #include "atacmds.h" #include "knowndrives.h" #include "utility.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef _WIN32 #include // access() #endif #include const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp,v 1.1.1.2 2013/07/22 01:17:35 misho Exp $" KNOWNDRIVES_H_CVSID; #define MODEL_STRING_LENGTH 40 #define FIRMWARE_STRING_LENGTH 8 #define TABLEPRINTWIDTH 19 // Builtin table of known drives. // Used as a default if not read from // "/usr/{,/local}share/smartmontools/drivedb.h" // or any other file specified by '-B' option, // see read_default_drive_databases() below. // The drive_settings structure is described in drivedb.h. const drive_settings builtin_knowndrives[] = { #include "drivedb.h" }; /// Drive database class. Stores custom entries read from file. /// Provides transparent access to concatenation of custom and /// default table. class drive_database { public: drive_database(); ~drive_database(); /// Get total number of entries. unsigned size() const { return m_custom_tab.size() + m_builtin_size; } /// Get number of custom entries. unsigned custom_size() const { return m_custom_tab.size(); } /// Array access. const drive_settings & operator[](unsigned i); /// Append new custom entry. void push_back(const drive_settings & src); /// Append builtin table. void append(const drive_settings * builtin_tab, unsigned builtin_size) { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; } private: const drive_settings * m_builtin_tab; unsigned m_builtin_size; std::vector m_custom_tab; std::vector m_custom_strings; const char * copy_string(const char * str); drive_database(const drive_database &); void operator=(const drive_database &); }; drive_database::drive_database() : m_builtin_tab(0), m_builtin_size(0) { } drive_database::~drive_database() { for (unsigned i = 0; i < m_custom_strings.size(); i++) delete [] m_custom_strings[i]; } const drive_settings & drive_database::operator[](unsigned i) { return (i < m_custom_tab.size() ? m_custom_tab[i] : m_builtin_tab[i - m_custom_tab.size()] ); } void drive_database::push_back(const drive_settings & src) { drive_settings dest; dest.modelfamily = copy_string(src.modelfamily); dest.modelregexp = copy_string(src.modelregexp); dest.firmwareregexp = copy_string(src.firmwareregexp); dest.warningmsg = copy_string(src.warningmsg); dest.presets = copy_string(src.presets); m_custom_tab.push_back(dest); } const char * drive_database::copy_string(const char * src) { size_t len = strlen(src); char * dest = new char[len+1]; memcpy(dest, src, len+1); try { m_custom_strings.push_back(dest); } catch (...) { delete [] dest; throw; } return dest; } /// The drive database. static drive_database knowndrives; // Return true if modelfamily string describes entry for USB ID static bool is_usb_modelfamily(const char * modelfamily) { return !strncmp(modelfamily, "USB:", 4); } // Return true if entry for USB ID static inline bool is_usb_entry(const drive_settings * dbentry) { return is_usb_modelfamily(dbentry->modelfamily); } // Compile regular expression, print message on failure. static bool compile(regular_expression & regex, const char *pattern) { if (!regex.compile(pattern, REG_EXTENDED)) { pout("Internal error: unable to compile regular expression \"%s\": %s\n" "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", pattern, regex.get_errmsg()); return false; } return true; } // Compile & match a regular expression. static bool match(const char * pattern, const char * str) { regular_expression regex; if (!compile(regex, pattern)) return false; return regex.full_match(str); } // Searches knowndrives[] for a drive with the given model number and firmware // string. If either the drive's model or firmware strings are not set by the // manufacturer then values of NULL may be used. Returns the entry of the // first match in knowndrives[] or 0 if no match if found. static const drive_settings * lookup_drive(const char * model, const char * firmware) { if (!model) model = ""; if (!firmware) firmware = ""; for (unsigned i = 0; i < knowndrives.size(); i++) { // Skip USB entries if (is_usb_entry(&knowndrives[i])) continue; // Check whether model matches the regular expression in knowndrives[i]. if (!match(knowndrives[i].modelregexp, model)) continue; // Model matches, now check firmware. "" matches always. if (!( !*knowndrives[i].firmwareregexp || match(knowndrives[i].firmwareregexp, firmware))) continue; // Found return &knowndrives[i]; } // Not found return 0; } // Parse drive or USB options in preset string, return false on error. static bool parse_db_presets(const char * presets, ata_vendor_attr_defs * defs, firmwarebug_defs * firmwarebugs, std::string * type) { for (int i = 0; ; ) { i += strspn(presets+i, " \t"); if (!presets[i]) break; char opt, arg[80+1+13]; int len = -1; if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) return false; if (opt == 'v' && defs) { // Parse "-v N,format[,name]" if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE)) return false; } else if (opt == 'F' && firmwarebugs) { firmwarebug_defs bug; if (!parse_firmwarebug_def(arg, bug)) return false; // Don't set if user specified '-F none'. if (!firmwarebugs->is_set(BUG_NONE)) firmwarebugs->set(bug); } else if (opt == 'd' && type) { // TODO: Check valid types *type = arg; } else return false; i += len; } return true; } // Parse '-v' and '-F' options in preset string, return false on error. static inline bool parse_presets(const char * presets, ata_vendor_attr_defs & defs, firmwarebug_defs & firmwarebugs) { return parse_db_presets(presets, &defs, &firmwarebugs, 0); } // Parse '-d' option in preset string, return false on error. static inline bool parse_usb_type(const char * presets, std::string & type) { return parse_db_presets(presets, 0, 0, &type); } // Parse "USB: [DEVICE] ; [BRIDGE]" string static void parse_usb_names(const char * names, usb_dev_info & info) { int n1 = -1, n2 = -1, n3 = -1; sscanf(names, "USB: %n%*[^;]%n; %n", &n1, &n2, &n3); if (0 < n1 && n1 < n2) info.usb_device.assign(names+n1, n2-n1); else sscanf(names, "USB: ; %n", &n3); if (0 < n3) info.usb_bridge = names+n3; } // Search drivedb for USB device with vendor:product ID. int lookup_usb_device(int vendor_id, int product_id, int bcd_device, usb_dev_info & info, usb_dev_info & info2) { // Format strings to match char usb_id_str[16], bcd_dev_str[16]; snprintf(usb_id_str, sizeof(usb_id_str), "0x%04x:0x%04x", vendor_id, product_id); if (bcd_device >= 0) snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device); else bcd_dev_str[0] = 0; int found = 0; for (unsigned i = 0; i < knowndrives.size(); i++) { const drive_settings & dbentry = knowndrives[i]; // Skip drive entries if (!is_usb_entry(&dbentry)) continue; // Check whether USB vendor:product ID matches if (!match(dbentry.modelregexp, usb_id_str)) continue; // Parse '-d type' usb_dev_info d; if (!parse_usb_type(dbentry.presets, d.usb_type)) return 0; // Syntax error parse_usb_names(dbentry.modelfamily, d); // If two entries with same vendor:product ID have different // types, use bcd_device (if provided by OS) to select entry. if ( *dbentry.firmwareregexp && *bcd_dev_str && match(dbentry.firmwareregexp, bcd_dev_str)) { // Exact match including bcd_device info = d; found = 1; break; } else if (!found) { // First match without bcd_device info = d; found = 1; } else if (info.usb_type != d.usb_type) { // Another possible match with different type info2 = d; found = 2; break; } // Stop search at first matching entry with empty bcd_device if (!*dbentry.firmwareregexp) break; } return found; } // Shows one entry of knowndrives[], returns #errors. static int showonepreset(const drive_settings * dbentry) { // Basic error check if (!( dbentry && dbentry->modelfamily && dbentry->modelregexp && *dbentry->modelregexp && dbentry->firmwareregexp && dbentry->warningmsg && dbentry->presets )) { pout("Invalid drive database entry. Please report\n" "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); return 1; } bool usb = is_usb_entry(dbentry); // print and check model and firmware regular expressions int errcnt = 0; regular_expression regex; pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"), dbentry->modelregexp); if (!compile(regex, dbentry->modelregexp)) errcnt++; pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"), *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) errcnt++; if (!usb) { pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); // if there are any presets, then show them firmwarebug_defs firmwarebugs; bool first_preset = true; if (*dbentry->presets) { ata_vendor_attr_defs defs; if (!parse_presets(dbentry->presets, defs, firmwarebugs)) { pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); errcnt++; } for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) { if (defs[i].priority != PRIOR_DEFAULT) { std::string name = ata_get_smart_attr_name(i, defs); // Use leading zeros instead of spaces so that everything lines up. pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", i, name.c_str()); // Check max name length suitable for smartctl -A output const unsigned maxlen = 23; if (name.size() > maxlen) { pout("%*s\n", TABLEPRINTWIDTH+6+maxlen, "Error: Attribute name too long ------^"); errcnt++; } first_preset = false; } } } if (first_preset) pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); // describe firmwarefix for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) { if (!firmwarebugs.is_set((firmwarebug_t)b)) continue; const char * fixdesc; switch ((firmwarebug_t)b) { case BUG_NOLOGDIR: fixdesc = "Avoids reading GP/SMART Log Directories (same as -F nologdir)"; break; case BUG_SAMSUNG: fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; break; case BUG_SAMSUNG2: fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; break; case BUG_SAMSUNG3: fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; break; case BUG_XERRORLBA: fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)"; break; default: fixdesc = "UNKNOWN"; errcnt++; break; } pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); } } else { // Print USB info usb_dev_info info; parse_usb_names(dbentry->modelfamily, info); pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Device:", (!info.usb_device.empty() ? info.usb_device.c_str() : "[unknown]")); pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Bridge:", (!info.usb_bridge.empty() ? info.usb_bridge.c_str() : "[unknown]")); if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) { pout("Syntax error in USB type string \"%s\"\n", dbentry->presets); errcnt++; } pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type", (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]")); } // Print any special warnings if (*dbentry->warningmsg) pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); return errcnt; } // Shows all presets for drives in knowndrives[]. // Returns #syntax errors. int showallpresets() { // loop over all entries in the knowndrives[] table, printing them // out in a nice format int errcnt = 0; for (unsigned i = 0; i < knowndrives.size(); i++) { errcnt += showonepreset(&knowndrives[i]); pout("\n"); } pout("Total number of entries :%5u\n" "Entries read from file(s):%5u\n\n", knowndrives.size(), knowndrives.custom_size()); pout("For information about adding a drive to the database see the FAQ on the\n"); pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n"); if (errcnt > 0) pout("\nFound %d syntax error(s) in database.\n" "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt); return errcnt; } // Shows all matching presets for a drive in knowndrives[]. // Returns # matching entries. int showmatchingpresets(const char *model, const char *firmware) { int cnt = 0; const char * firmwaremsg = (firmware ? firmware : "(any)"); for (unsigned i = 0; i < knowndrives.size(); i++) { if (!match(knowndrives[i].modelregexp, model)) continue; if ( firmware && *knowndrives[i].firmwareregexp && !match(knowndrives[i].firmwareregexp, firmware)) continue; // Found if (++cnt == 1) pout("Drive found in smartmontools Database. Drive identity strings:\n" "%-*s %s\n" "%-*s %s\n" "match smartmontools Drive Database entry:\n", TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg); else if (cnt == 2) pout("and match these additional entries:\n"); showonepreset(&knowndrives[i]); pout("\n"); } if (cnt == 0) pout("No presets are defined for this drive. Its identity strings:\n" "MODEL: %s\n" "FIRMWARE: %s\n" "do not match any of the known regular expressions.\n", model, firmwaremsg); return cnt; } // Shows the presets (if any) that are available for the given drive. void show_presets(const ata_identify_device * drive) { char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; // get the drive's model/firmware strings ata_format_id_string(model, drive->model, sizeof(model)-1); ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); // and search to see if they match values in the table const drive_settings * dbentry = lookup_drive(model, firmware); if (!dbentry) { // no matches found pout("No presets are defined for this drive. Its identity strings:\n" "MODEL: %s\n" "FIRMWARE: %s\n" "do not match any of the known regular expressions.\n" "Use -P showall to list all known regular expressions.\n", model, firmware); return; } // We found a matching drive. Print out all information about it. pout("Drive found in smartmontools Database. Drive identity strings:\n" "%-*s %s\n" "%-*s %s\n" "match smartmontools Drive Database entry:\n", TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware); showonepreset(dbentry); } // Searches drive database and sets preset vendor attribute // options in defs and firmwarebugs. // Values that have already been set will not be changed. // Returns pointer to database entry or nullptr if none found const drive_settings * lookup_drive_apply_presets( const ata_identify_device * drive, ata_vendor_attr_defs & defs, firmwarebug_defs & firmwarebugs) { // get the drive's model/firmware strings char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; ata_format_id_string(model, drive->model, sizeof(model)-1); ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); // Look up the drive in knowndrives[]. const drive_settings * dbentry = lookup_drive(model, firmware); if (!dbentry) return 0; if (*dbentry->presets) { // Apply presets if (!parse_presets(dbentry->presets, defs, firmwarebugs)) pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); } return dbentry; } ///////////////////////////////////////////////////////////////////////////// // Parser for drive database files // Abstract pointer to read file input. // Operations supported: c = *p; c = p[1]; ++p; class stdin_iterator { public: explicit stdin_iterator(FILE * f) : m_f(f) { get(); get(); } stdin_iterator & operator++() { get(); return *this; } char operator*() const { return m_c; } char operator[](int i) const { if (i != 1) fail(); return m_next; } private: FILE * m_f; char m_c, m_next; void get(); void fail() const; }; void stdin_iterator::get() { m_c = m_next; int ch = getc(m_f); m_next = (ch != EOF ? ch : 0); } void stdin_iterator::fail() const { throw std::runtime_error("stdin_iterator: wrong usage"); } // Use above as parser input 'pointer'. Can easily be changed later // to e.g. 'const char *' if above is too slow. typedef stdin_iterator parse_ptr; // Skip whitespace and comments. static parse_ptr skip_white(parse_ptr src, const char * path, int & line) { for ( ; ; ++src) switch (*src) { case ' ': case '\t': continue; case '\n': ++line; continue; case '/': switch (src[1]) { case '/': // skip '// comment' ++src; ++src; while (*src && *src != '\n') ++src; if (*src) ++line; break; case '*': // skip '/* comment */' ++src; ++src; for (;;) { if (!*src) { pout("%s(%d): Missing '*/'\n", path, line); return src; } char c = *src; ++src; if (c == '\n') ++line; else if (c == '*' && *src == '/') break; } break; default: return src; } continue; default: return src; } } // Info about a token. struct token_info { char type; int line; std::string value; token_info() : type(0), line(0) { } }; // Get next token. static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line) { src = skip_white(src, path, line); switch (*src) { case '{': case '}': case ',': // Simple token token.type = *src; token.line = line; ++src; break; case '"': // String constant token.type = '"'; token.line = line; token.value = ""; do { for (++src; *src != '"'; ++src) { char c = *src; if (!c || c == '\n' || (c == '\\' && !src[1])) { pout("%s(%d): Missing terminating '\"'\n", path, line); token.type = '?'; token.line = line; return src; } if (c == '\\') { c = *++src; switch (c) { case 'n' : c = '\n'; break; case '\n': ++line; break; case '\\': case '"': break; default: pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c); token.type = '?'; token.line = line; continue; } } token.value += c; } // Lookahead to detect string constant concatentation src = skip_white(++src, path, line); } while (*src == '"'); break; case 0: // EOF token.type = 0; token.line = line; break; default: pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src); token.type = '?'; token.line = line; while (*src && *src != '\n') ++src; break; } return src; } // Parse drive database from abstract input pointer. static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path) { int state = 0, field = 0; std::string values[5]; bool ok = true; token_info token; int line = 1; src = get_token(src, token, path, line); for (;;) { // EOF is ok after '}', trailing ',' is also allowed. if (!token.type && (state == 0 || state == 4)) break; // Check expected token const char expect[] = "{\",},"; if (token.type != expect[state]) { if (token.type != '?') pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]); ok = false; // Skip to next entry while (token.type && token.type != '{') src = get_token(src, token, path, line); state = 0; if (token.type) continue; break; } // Interpret parser state switch (state) { case 0: // ... ^{...} state = 1; field = 0; break; case 1: // {... ^"..." ...} switch (field) { case 1: case 2: if (!token.value.empty()) { regular_expression regex; if (!regex.compile(token.value.c_str(), REG_EXTENDED)) { pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg()); ok = false; } } else if (field == 1) { pout("%s(%d): Missing regular expression for drive model\n", path, token.line); ok = false; } break; case 4: if (!token.value.empty()) { if (!is_usb_modelfamily(values[0].c_str())) { ata_vendor_attr_defs defs; firmwarebug_defs fix; if (!parse_presets(token.value.c_str(), defs, fix)) { pout("%s(%d): Syntax error in preset option string\n", path, token.line); ok = false; } } else { std::string type; if (!parse_usb_type(token.value.c_str(), type)) { pout("%s(%d): Syntax error in USB type string\n", path, token.line); ok = false; } } } break; } values[field] = token.value; state = (++field < 5 ? 2 : 3); break; case 2: // {... "..."^, ...} state = 1; break; case 3: // {...^}, ... { drive_settings entry; entry.modelfamily = values[0].c_str(); entry.modelregexp = values[1].c_str(); entry.firmwareregexp = values[2].c_str(); entry.warningmsg = values[3].c_str(); entry.presets = values[4].c_str(); db.push_back(entry); } state = 4; break; case 4: // {...}^, ... state = 0; break; default: pout("Bad state %d\n", state); return false; } src = get_token(src, token, path, line); } return ok; } // Read drive database from file. bool read_drive_database(const char * path) { stdio_file f(path, "r" #ifdef __CYGWIN__ // Allow files with '\r\n'. "t" #endif ); if (!f) { pout("%s: cannot open drive database file\n", path); return false; } return parse_drive_database(parse_ptr(f), knowndrives, path); } // Get path for additional database file const char * get_drivedb_path_add() { #ifndef _WIN32 return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h"; #else static std::string path = get_exe_dir() + "/drivedb-add.h"; return path.c_str(); #endif } #ifdef SMARTMONTOOLS_DRIVEDBDIR // Get path for default database file const char * get_drivedb_path_default() { #ifndef _WIN32 return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h"; #else static std::string path = get_exe_dir() + "/drivedb.h"; return path.c_str(); #endif } #endif // Read drive databases from standard places. bool read_default_drive_databases() { // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h const char * db1 = get_drivedb_path_add(); if (!access(db1, 0)) { if (!read_drive_database(db1)) return false; } #ifdef SMARTMONTOOLS_DRIVEDBDIR // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h const char * db2 = get_drivedb_path_default(); if (!access(db2, 0)) { if (!read_drive_database(db2)) return false; } else #endif { // Append builtin table. knowndrives.append(builtin_knowndrives, sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0])); } return true; }