diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:14:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:14:45 +0000 |
commit | 43e8530e93493bb978c446a2023134bdd4277e50 (patch) | |
tree | e8c0d3c0c394b17381f48fb2d288f166b4f22440 /knowndrives.cpp | |
parent | Initial commit. (diff) | |
download | smartmontools-upstream.tar.xz smartmontools-upstream.zip |
Adding upstream version 7.4.upstream/7.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | knowndrives.cpp | 996 |
1 files changed, 996 insertions, 0 deletions
diff --git a/knowndrives.cpp b/knowndrives.cpp new file mode 100644 index 0000000..3e399cf --- /dev/null +++ b/knowndrives.cpp @@ -0,0 +1,996 @@ +/* + * knowndrives.cpp + * + * Home page of code is: https://www.smartmontools.org + * + * Copyright (C) 2003-11 Philip Williams, Bruce Allen + * Copyright (C) 2008-21 Christian Franke + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" + +#include <stdio.h> +#include "atacmds.h" +#include "knowndrives.h" +#include "utility.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _WIN32 +#include <io.h> // access() +#endif + +#include <stdexcept> + +const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 5376 2022-05-01 12:49:30Z chrfranke $" + 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" +}; + +const unsigned builtin_knowndrives_size = + sizeof(builtin_knowndrives) / sizeof(builtin_knowndrives[0]); + +/// 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<drive_settings> m_custom_tab; + std::vector<char *> 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; + + +enum dbentry_type { + DBENTRY_VERSION, + DBENTRY_ATA_DEFAULT, + DBENTRY_ATA, + DBENTRY_USB +}; + +// Return type of entry +static dbentry_type get_modelfamily_type(const char * modelfamily) +{ + if (modelfamily[0] == 'V' && str_starts_with(modelfamily, "VERSION:")) + return DBENTRY_VERSION; + else if (modelfamily[0] == 'D' && !strcmp(modelfamily, "DEFAULT")) + return DBENTRY_ATA_DEFAULT; + else if (modelfamily[0] == 'U' && str_starts_with(modelfamily, "USB:")) + return DBENTRY_USB; + else + return DBENTRY_ATA; +} + +static inline dbentry_type get_dbentry_type(const drive_settings * dbentry) +{ + return get_modelfamily_type(dbentry->modelfamily); +} + +// Extract "BRANCH/REV" from "VERSION: ..." string. +static void parse_version(std::string & dbversion, const char * verstr) +{ + static const regular_expression regex( + "^VERSION: ([0-9]+\\.[0-9]+)(/([0-9]+) | \\$[^0-9]* ([0-9]+) )" + // (1 )( (3 ) (4 ) ) + ); + const int nmatch = 1+4; + regular_expression::match_range match[nmatch]; + if (!regex.execute(verstr, nmatch, match)) + return; + dbversion.assign(verstr + match[1].rm_so, match[1].rm_eo - match[1].rm_so); + int i = (match[3].rm_so >= 0 ? 3 : 4); // "BRANCH/REV" or "BRANCH ... SVN-REV" + dbversion += '/'; + dbversion.append(verstr + match[i].rm_so, match[i].rm_eo - match[i].rm_so); +} + +// Compile regular expression, print message on failure. +static bool compile(regular_expression & regex, const char *pattern) +{ + if (!regex.compile(pattern)) { + 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, + std::string * dbversion = nullptr) +{ + if (!model) + model = ""; + if (!firmware) + firmware = ""; + + for (unsigned i = 0; i < knowndrives.size(); i++) { + dbentry_type t = get_dbentry_type(&knowndrives[i]); + // Get version if requested + if (t == DBENTRY_VERSION) { + if (dbversion) + parse_version(*dbversion, knowndrives[i].modelfamily); + continue; + } + + // Skip DEFAULT and USB entries + if (t != DBENTRY_ATA) + 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[,HDD|SSD]]" + if (!parse_attribute_def(arg, *defs, (firmwarebugs ? PRIOR_DATABASE : PRIOR_DEFAULT))) + 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' options in default preset string, return false on error. +static inline bool parse_default_presets(const char * presets, + ata_vendor_attr_defs & defs) +{ + return parse_db_presets(presets, &defs, 0, 0); +} + +// 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 (get_dbentry_type(&dbentry) != DBENTRY_USB) + 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; + } + + dbentry_type type = get_dbentry_type(dbentry); + bool usb = (type == DBENTRY_USB); + + // 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 (type == DBENTRY_ATA_DEFAULT) { + if (!parse_default_presets(dbentry->presets, defs)) { + pout("Syntax error in DEFAULT option string \"%s\"\n", dbentry->presets); + errcnt++; + } + } + else { + 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 || !defs[i].name.empty()) { + 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_URL "\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, std::string & dbversion) +{ + // 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, &dbversion); + 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), m_next(0) { 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 concatenation + 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())) { + 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()) { + // Syntax check + switch (get_modelfamily_type(values[0].c_str())) { + case DBENTRY_ATA_DEFAULT: { + ata_vendor_attr_defs defs; + if (!parse_default_presets(token.value.c_str(), defs)) { + pout("%s(%d): Syntax error in DEFAULT option string\n", path, token.line); + ok = false; + } + } break; + default: { // DBENTRY_ATA + 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; + } + } break; + case DBENTRY_USB: { + 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; + } + } + 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. +static 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, builtin_knowndrives_size); + } + + return true; +} + +static ata_vendor_attr_defs default_attr_defs; + +// Initialize default_attr_defs. +static bool init_default_attr_defs() +{ + // Lookup default entry + const drive_settings * entry = 0; + for (unsigned i = 0; i < knowndrives.size(); i++) { + if (get_dbentry_type(&knowndrives[i]) != DBENTRY_ATA_DEFAULT) + continue; + entry = &knowndrives[i]; + break; + } + + if (!entry) { + // Fall back to builtin database + for (unsigned i = 0; i < builtin_knowndrives_size; i++) { + if (get_dbentry_type(&builtin_knowndrives[i]) != DBENTRY_ATA_DEFAULT) + continue; + entry = &builtin_knowndrives[i]; + break; + } + + if (!entry) + throw std::logic_error("DEFAULT entry missing in builtin drive database"); + + pout("Warning: DEFAULT entry missing in drive database file(s)\n"); + } + + if (!parse_default_presets(entry->presets, default_attr_defs)) { + pout("Syntax error in DEFAULT drive database entry\n"); + return false; + } + + return true; +} + +// Init default db entry and optionally read drive databases from standard places. +bool init_drive_database(bool use_default_db) +{ + if (use_default_db && !read_default_drive_databases()) + return false; + + return init_default_attr_defs(); +} + +// Get vendor attribute options from default db entry. +const ata_vendor_attr_defs & get_default_attr_defs() +{ + return default_attr_defs; +} |