4 * Home page of code is: http://smartmontools.sourceforge.net
5 * Address of support mailing list: smartmontools-support@lists.sourceforge.net
7 * Copyright (C) 2003-11 Philip Williams, Bruce Allen
8 * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net>
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)
15 * You should have received a copy of the GNU General Public License
16 * (for example COPYING); If not, see <http://www.gnu.org/licenses/>.
24 #include "knowndrives.h"
31 #include <io.h> // access()
36 const char * knowndrives_cpp_cvsid
= "$Id: knowndrives.cpp 3719 2012-12-03 21:19:33Z chrfranke $"
39 #define MODEL_STRING_LENGTH 40
40 #define FIRMWARE_STRING_LENGTH 8
41 #define TABLEPRINTWIDTH 19
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
[] = {
55 /// Drive database class. Stores custom entries read from file.
56 /// Provides transparent access to concatenation of custom and
65 /// Get total number of entries.
67 { return m_custom_tab
.size() + m_builtin_size
; }
69 /// Get number of custom entries.
70 unsigned custom_size() const
71 { return m_custom_tab
.size(); }
74 const drive_settings
& operator[](unsigned i
);
76 /// Append new custom entry.
77 void push_back(const drive_settings
& src
);
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
; }
84 const drive_settings
* m_builtin_tab
;
85 unsigned m_builtin_size
;
87 std::vector
<drive_settings
> m_custom_tab
;
88 std::vector
<char *> m_custom_strings
;
90 const char * copy_string(const char * str
);
92 drive_database(const drive_database
&);
93 void operator=(const drive_database
&);
96 drive_database::drive_database()
97 : m_builtin_tab(0), m_builtin_size(0)
101 drive_database::~drive_database()
103 for (unsigned i
= 0; i
< m_custom_strings
.size(); i
++)
104 delete [] m_custom_strings
[i
];
107 const drive_settings
& drive_database::operator[](unsigned i
)
109 return (i
< m_custom_tab
.size() ? m_custom_tab
[i
]
110 : m_builtin_tab
[i
- m_custom_tab
.size()] );
113 void drive_database::push_back(const drive_settings
& src
)
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
);
124 const char * drive_database::copy_string(const char * src
)
126 size_t len
= strlen(src
);
127 char * dest
= new char[len
+1];
128 memcpy(dest
, src
, len
+1);
130 m_custom_strings
.push_back(dest
);
133 delete [] dest
; throw;
139 /// The drive database.
140 static drive_database knowndrives
;
143 // Return true if modelfamily string describes entry for USB ID
144 static bool is_usb_modelfamily(const char * modelfamily
)
146 return !strncmp(modelfamily
, "USB:", 4);
149 // Return true if entry for USB ID
150 static inline bool is_usb_entry(const drive_settings
* dbentry
)
152 return is_usb_modelfamily(dbentry
->modelfamily
);
155 // Compile regular expression, print message on failure.
156 static bool compile(regular_expression
& regex
, const char *pattern
)
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());
167 // Compile & match a regular expression.
168 static bool match(const char * pattern
, const char * str
)
170 regular_expression regex
;
171 if (!compile(regex
, pattern
))
173 return regex
.full_match(str
);
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
)
187 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
189 if (is_usb_entry(&knowndrives
[i
]))
192 // Check whether model matches the regular expression in knowndrives[i].
193 if (!match(knowndrives
[i
].modelregexp
, model
))
196 // Model matches, now check firmware. "" matches always.
197 if (!( !*knowndrives
[i
].firmwareregexp
198 || match(knowndrives
[i
].firmwareregexp
, firmware
)))
202 return &knowndrives
[i
];
210 // Parse drive or USB options in preset string, return false on error.
211 static bool parse_db_presets(const char * presets
, ata_vendor_attr_defs
* defs
,
212 firmwarebug_defs
* firmwarebugs
, std::string
* type
)
214 for (int i
= 0; ; ) {
215 i
+= strspn(presets
+i
, " \t");
218 char opt
, arg
[80+1+13]; int len
= -1;
219 if (!(sscanf(presets
+i
, "-%c %80[^ ]%n", &opt
, arg
, &len
) >= 2 && len
> 0))
221 if (opt
== 'v' && defs
) {
222 // Parse "-v N,format[,name]"
223 if (!parse_attribute_def(arg
, *defs
, PRIOR_DATABASE
))
226 else if (opt
== 'F' && firmwarebugs
) {
227 firmwarebug_defs bug
;
228 if (!parse_firmwarebug_def(arg
, bug
))
230 // Don't set if user specified '-F none'.
231 if (!firmwarebugs
->is_set(BUG_NONE
))
232 firmwarebugs
->set(bug
);
234 else if (opt
== 'd' && type
) {
235 // TODO: Check valid types
246 // Parse '-v' and '-F' options in preset string, return false on error.
247 static inline bool parse_presets(const char * presets
,
248 ata_vendor_attr_defs
& defs
,
249 firmwarebug_defs
& firmwarebugs
)
251 return parse_db_presets(presets
, &defs
, &firmwarebugs
, 0);
254 // Parse '-d' option in preset string, return false on error.
255 static inline bool parse_usb_type(const char * presets
, std::string
& type
)
257 return parse_db_presets(presets
, 0, 0, &type
);
260 // Parse "USB: [DEVICE] ; [BRIDGE]" string
261 static void parse_usb_names(const char * names
, usb_dev_info
& info
)
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
);
268 sscanf(names
, "USB: ; %n", &n3
);
270 info
.usb_bridge
= names
+n3
;
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
)
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
);
281 snprintf(bcd_dev_str
, sizeof(bcd_dev_str
), "0x%04x", bcd_device
);
286 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
287 const drive_settings
& dbentry
= knowndrives
[i
];
289 // Skip drive entries
290 if (!is_usb_entry(&dbentry
))
293 // Check whether USB vendor:product ID matches
294 if (!match(dbentry
.modelregexp
, usb_id_str
))
299 if (!parse_usb_type(dbentry
.presets
, d
.usb_type
))
300 return 0; // Syntax error
301 parse_usb_names(dbentry
.modelfamily
, d
);
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
312 // First match without bcd_device
315 else if (info
.usb_type
!= d
.usb_type
) {
316 // Another possible match with different type
317 info2
= d
; found
= 2;
321 // Stop search at first matching entry with empty bcd_device
322 if (!*dbentry
.firmwareregexp
)
329 // Shows one entry of knowndrives[], returns #errors.
330 static int showonepreset(const drive_settings
* 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");
344 bool usb
= is_usb_entry(dbentry
);
346 // print and check model and firmware regular expressions
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
))
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
))
360 pout("%-*s %s\n", TABLEPRINTWIDTH
, "MODEL FAMILY:", dbentry
->modelfamily
);
362 // if there are any presets, then show them
363 firmwarebug_defs firmwarebugs
;
364 bool first_preset
= true;
365 if (*dbentry
->presets
) {
366 ata_vendor_attr_defs defs
;
367 if (!parse_presets(dbentry
->presets
, defs
, firmwarebugs
)) {
368 pout("Syntax error in preset option string \"%s\"\n", dbentry
->presets
);
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:" : "",
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 ------^");
383 first_preset
= false;
388 pout("%-*s %s\n", TABLEPRINTWIDTH
, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
390 // describe firmwarefix
391 for (int b
= BUG_NOLOGDIR
; b
<= BUG_XERRORLBA
; b
++) {
392 if (!firmwarebugs
.is_set((firmwarebug_t
)b
))
394 const char * fixdesc
;
395 switch ((firmwarebug_t
)b
) {
397 fixdesc
= "Avoids reading GP/SMART Log Directories (same as -F nologdir)";
400 fixdesc
= "Fixes byte order in some SMART data (same as -F samsung)";
403 fixdesc
= "Fixes byte order in some SMART data (same as -F samsung2)";
406 fixdesc
= "Fixes completed self-test reported as in progress (same as -F samsung3)";
409 fixdesc
= "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)";
412 fixdesc
= "UNKNOWN"; errcnt
++;
415 pout("%-*s %s\n", TABLEPRINTWIDTH
, "OTHER PRESETS:", fixdesc
);
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]"));
426 if (*dbentry
->presets
&& !parse_usb_type(dbentry
->presets
, info
.usb_type
)) {
427 pout("Syntax error in USB type string \"%s\"\n", dbentry
->presets
);
430 pout("%-*s %s\n", TABLEPRINTWIDTH
, "USB Type",
431 (!info
.usb_type
.empty() ? info
.usb_type
.c_str() : "[unsupported]"));
434 // Print any special warnings
435 if (*dbentry
->warningmsg
)
436 pout("%-*s %s\n", TABLEPRINTWIDTH
, "WARNINGS:", dbentry
->warningmsg
);
440 // Shows all presets for drives in knowndrives[].
441 // Returns #syntax errors.
444 // loop over all entries in the knowndrives[] table, printing them
445 // out in a nice format
447 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
448 errcnt
+= showonepreset(&knowndrives
[i
]);
452 pout("Total number of entries :%5u\n"
453 "Entries read from file(s):%5u\n\n",
454 knowndrives
.size(), knowndrives
.custom_size());
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");
460 pout("\nFound %d syntax error(s) in database.\n"
461 "Please inform smartmontools developers at " PACKAGE_BUGREPORT
"\n", errcnt
);
465 // Shows all matching presets for a drive in knowndrives[].
466 // Returns # matching entries.
467 int showmatchingpresets(const char *model
, const char *firmware
)
470 const char * firmwaremsg
= (firmware
? firmware
: "(any)");
472 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
473 if (!match(knowndrives
[i
].modelregexp
, model
))
475 if ( firmware
&& *knowndrives
[i
].firmwareregexp
476 && !match(knowndrives
[i
].firmwareregexp
, firmware
))
480 pout("Drive found in smartmontools Database. Drive identity strings:\n"
483 "match smartmontools Drive Database entry:\n",
484 TABLEPRINTWIDTH
, "MODEL:", model
, TABLEPRINTWIDTH
, "FIRMWARE:", firmwaremsg
);
486 pout("and match these additional entries:\n");
487 showonepreset(&knowndrives
[i
]);
491 pout("No presets are defined for this drive. Its identity strings:\n"
494 "do not match any of the known regular expressions.\n",
499 // Shows the presets (if any) that are available for the given drive.
500 void show_presets(const ata_identify_device
* drive
)
502 char model
[MODEL_STRING_LENGTH
+1], firmware
[FIRMWARE_STRING_LENGTH
+1];
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);
508 // and search to see if they match values in the table
509 const drive_settings
* dbentry
= lookup_drive(model
, firmware
);
512 pout("No presets are defined for this drive. Its identity strings:\n"
515 "do not match any of the known regular expressions.\n"
516 "Use -P showall to list all known regular expressions.\n",
521 // We found a matching drive. Print out all information about it.
522 pout("Drive found in smartmontools Database. Drive identity strings:\n"
525 "match smartmontools Drive Database entry:\n",
526 TABLEPRINTWIDTH
, "MODEL:", model
, TABLEPRINTWIDTH
, "FIRMWARE:", firmware
);
527 showonepreset(dbentry
);
530 // Searches drive database and sets preset vendor attribute
531 // options in defs and firmwarebugs.
532 // Values that have already been set will not be changed.
533 // Returns pointer to database entry or nullptr if none found
534 const drive_settings
* lookup_drive_apply_presets(
535 const ata_identify_device
* drive
, ata_vendor_attr_defs
& defs
,
536 firmwarebug_defs
& firmwarebugs
)
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);
543 // Look up the drive in knowndrives[].
544 const drive_settings
* dbentry
= lookup_drive(model
, firmware
);
548 if (*dbentry
->presets
) {
550 if (!parse_presets(dbentry
->presets
, defs
, firmwarebugs
))
551 pout("Syntax error in preset option string \"%s\"\n", dbentry
->presets
);
557 /////////////////////////////////////////////////////////////////////////////
558 // Parser for drive database files
560 // Abstract pointer to read file input.
561 // Operations supported: c = *p; c = p[1]; ++p;
565 explicit stdin_iterator(FILE * f
)
566 : m_f(f
) { get(); get(); }
568 stdin_iterator
& operator++()
569 { get(); return *this; }
571 char operator*() const
574 char operator[](int i
) const
588 void stdin_iterator::get()
592 m_next
= (ch
!= EOF
? ch
: 0);
595 void stdin_iterator::fail() const
597 throw std::runtime_error("stdin_iterator: wrong usage");
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
;
605 // Skip whitespace and comments.
606 static parse_ptr
skip_white(parse_ptr src
, const char * path
, int & line
)
608 for ( ; ; ++src
) switch (*src
) {
621 while (*src
&& *src
!= '\n')
627 // skip '/* comment */'
631 pout("%s(%d): Missing '*/'\n", path
, line
);
634 char c
= *src
; ++src
;
637 else if (c
== '*' && *src
== '/')
651 // Info about a token.
658 token_info() : type(0), line(0) { }
662 static parse_ptr
get_token(parse_ptr src
, token_info
& token
, const char * path
, int & line
)
664 src
= skip_white(src
, path
, line
);
666 case '{': case '}': case ',':
668 token
.type
= *src
; token
.line
= line
;
674 token
.type
= '"'; token
.line
= line
;
677 for (++src
; *src
!= '"'; ++src
) {
679 if (!c
|| c
== '\n' || (c
== '\\' && !src
[1])) {
680 pout("%s(%d): Missing terminating '\"'\n", path
, line
);
681 token
.type
= '?'; token
.line
= line
;
687 case 'n' : c
= '\n'; break;
688 case '\n': ++line
; break;
689 case '\\': case '"': break;
691 pout("%s(%d): Unknown escape sequence '\\%c'\n", path
, line
, c
);
692 token
.type
= '?'; token
.line
= line
;
698 // Lookahead to detect string constant concatentation
699 src
= skip_white(++src
, path
, line
);
700 } while (*src
== '"');
705 token
.type
= 0; token
.line
= line
;
709 pout("%s(%d): Syntax error, invalid char '%c'\n", path
, line
, *src
);
710 token
.type
= '?'; token
.line
= line
;
711 while (*src
&& *src
!= '\n')
719 // Parse drive database from abstract input pointer.
720 static bool parse_drive_database(parse_ptr src
, drive_database
& db
, const char * path
)
722 int state
= 0, field
= 0;
723 std::string values
[5];
726 token_info token
; int line
= 1;
727 src
= get_token(src
, token
, path
, line
);
729 // EOF is ok after '}', trailing ',' is also allowed.
730 if (!token
.type
&& (state
== 0 || state
== 4))
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
]);
739 // Skip to next entry
740 while (token
.type
&& token
.type
!= '{')
741 src
= get_token(src
, token
, path
, line
);
748 // Interpret parser state
750 case 0: // ... ^{...}
751 state
= 1; field
= 0;
753 case 1: // {... ^"..." ...}
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());
763 else if (field
== 1) {
764 pout("%s(%d): Missing regular expression for drive model\n", path
, token
.line
);
769 if (!token
.value
.empty()) {
770 if (!is_usb_modelfamily(values
[0].c_str())) {
771 ata_vendor_attr_defs defs
; firmwarebug_defs fix
;
772 if (!parse_presets(token
.value
.c_str(), defs
, fix
)) {
773 pout("%s(%d): Syntax error in preset option string\n", path
, token
.line
);
779 if (!parse_usb_type(token
.value
.c_str(), type
)) {
780 pout("%s(%d): Syntax error in USB type string\n", path
, token
.line
);
787 values
[field
] = token
.value
;
788 state
= (++field
< 5 ? 2 : 3);
790 case 2: // {... "..."^, ...}
793 case 3: // {...^}, ...
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();
805 case 4: // {...}^, ...
809 pout("Bad state %d\n", state
);
812 src
= get_token(src
, token
, path
, line
);
817 // Read drive database from file.
818 bool read_drive_database(const char * path
)
820 stdio_file
f(path
, "r"
821 #ifdef __CYGWIN__ // Allow files with '\r\n'.
826 pout("%s: cannot open drive database file\n", path
);
830 return parse_drive_database(parse_ptr(f
), knowndrives
, path
);
833 // Get path for additional database file
834 const char * get_drivedb_path_add()
837 return SMARTMONTOOLS_SYSCONFDIR
"/smart_drivedb.h";
839 static std::string path
= get_exe_dir() + "/drivedb-add.h";
844 #ifdef SMARTMONTOOLS_DRIVEDBDIR
846 // Get path for default database file
847 const char * get_drivedb_path_default()
850 return SMARTMONTOOLS_DRIVEDBDIR
"/drivedb.h";
852 static std::string path
= get_exe_dir() + "/drivedb.h";
859 // Read drive databases from standard places.
860 bool read_default_drive_databases()
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
))
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
))
879 // Append builtin table.
880 knowndrives
.append(builtin_knowndrives
,
881 sizeof(builtin_knowndrives
)/sizeof(builtin_knowndrives
[0]));