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-11 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, write to the Free
17 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "knowndrives.h"
32 #include <io.h> // access()
37 const char * knowndrives_cpp_cvsid
= "$Id: knowndrives.cpp 3447 2011-10-14 20:32:00Z chrfranke $"
40 #define MODEL_STRING_LENGTH 40
41 #define FIRMWARE_STRING_LENGTH 8
42 #define TABLEPRINTWIDTH 19
45 // Builtin table of known drives.
46 // Used as a default if not read from
47 // "/usr/{,/local}share/smartmontools/drivedb.h"
48 // or any other file specified by '-B' option,
49 // see read_default_drive_databases() below.
50 // The drive_settings structure is described in drivedb.h.
51 const drive_settings builtin_knowndrives
[] = {
56 /// Drive database class. Stores custom entries read from file.
57 /// Provides transparent access to concatenation of custom and
66 /// Get total number of entries.
68 { return m_custom_tab
.size() + m_builtin_size
; }
70 /// Get number of custom entries.
71 unsigned custom_size() const
72 { return m_custom_tab
.size(); }
75 const drive_settings
& operator[](unsigned i
);
77 /// Append new custom entry.
78 void push_back(const drive_settings
& src
);
80 /// Append builtin table.
81 void append(const drive_settings
* builtin_tab
, unsigned builtin_size
)
82 { m_builtin_tab
= builtin_tab
; m_builtin_size
= builtin_size
; }
85 const drive_settings
* m_builtin_tab
;
86 unsigned m_builtin_size
;
88 std::vector
<drive_settings
> m_custom_tab
;
89 std::vector
<char *> m_custom_strings
;
91 const char * copy_string(const char * str
);
93 drive_database(const drive_database
&);
94 void operator=(const drive_database
&);
97 drive_database::drive_database()
98 : m_builtin_tab(0), m_builtin_size(0)
102 drive_database::~drive_database()
104 for (unsigned i
= 0; i
< m_custom_strings
.size(); i
++)
105 delete [] m_custom_strings
[i
];
108 const drive_settings
& drive_database::operator[](unsigned i
)
110 return (i
< m_custom_tab
.size() ? m_custom_tab
[i
]
111 : m_builtin_tab
[i
- m_custom_tab
.size()] );
114 void drive_database::push_back(const drive_settings
& src
)
117 dest
.modelfamily
= copy_string(src
.modelfamily
);
118 dest
.modelregexp
= copy_string(src
.modelregexp
);
119 dest
.firmwareregexp
= copy_string(src
.firmwareregexp
);
120 dest
.warningmsg
= copy_string(src
.warningmsg
);
121 dest
.presets
= copy_string(src
.presets
);
122 m_custom_tab
.push_back(dest
);
125 const char * drive_database::copy_string(const char * src
)
127 char * dest
= new char[strlen(src
)+1];
129 m_custom_strings
.push_back(dest
);
132 delete [] dest
; throw;
134 return strcpy(dest
, src
);
138 /// The drive database.
139 static drive_database knowndrives
;
142 // Return true if modelfamily string describes entry for USB ID
143 static bool is_usb_modelfamily(const char * modelfamily
)
145 return !strncmp(modelfamily
, "USB:", 4);
148 // Return true if entry for USB ID
149 static inline bool is_usb_entry(const drive_settings
* dbentry
)
151 return is_usb_modelfamily(dbentry
->modelfamily
);
154 // Compile regular expression, print message on failure.
155 static bool compile(regular_expression
& regex
, const char *pattern
)
157 if (!regex
.compile(pattern
, REG_EXTENDED
)) {
158 pout("Internal error: unable to compile regular expression \"%s\": %s\n"
159 "Please inform smartmontools developers at " PACKAGE_BUGREPORT
"\n",
160 pattern
, regex
.get_errmsg());
166 // Compile & match a regular expression.
167 static bool match(const char * pattern
, const char * str
)
169 regular_expression regex
;
170 if (!compile(regex
, pattern
))
172 return regex
.full_match(str
);
175 // Searches knowndrives[] for a drive with the given model number and firmware
176 // string. If either the drive's model or firmware strings are not set by the
177 // manufacturer then values of NULL may be used. Returns the entry of the
178 // first match in knowndrives[] or 0 if no match if found.
179 static const drive_settings
* lookup_drive(const char * model
, const char * firmware
)
186 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
188 if (is_usb_entry(&knowndrives
[i
]))
191 // Check whether model matches the regular expression in knowndrives[i].
192 if (!match(knowndrives
[i
].modelregexp
, model
))
195 // Model matches, now check firmware. "" matches always.
196 if (!( !*knowndrives
[i
].firmwareregexp
197 || match(knowndrives
[i
].firmwareregexp
, firmware
)))
201 return &knowndrives
[i
];
209 // Parse drive or USB options in preset string, return false on error.
210 static bool parse_db_presets(const char * presets
, ata_vendor_attr_defs
* defs
,
211 unsigned char * fix_firmwarebug
, std::string
* type
)
213 for (int i
= 0; ; ) {
214 i
+= strspn(presets
+i
, " \t");
217 char opt
, arg
[80+1+13]; int len
= -1;
218 if (!(sscanf(presets
+i
, "-%c %80[^ ]%n", &opt
, arg
, &len
) >= 2 && len
> 0))
220 if (opt
== 'v' && defs
) {
221 // Parse "-v N,format[,name]"
222 if (!parse_attribute_def(arg
, *defs
, PRIOR_DATABASE
))
225 else if (opt
== 'F' && fix_firmwarebug
) {
227 if (!strcmp(arg
, "samsung"))
229 else if (!strcmp(arg
, "samsung2"))
231 else if (!strcmp(arg
, "samsung3"))
235 // Set only if not set by user
236 if (*fix_firmwarebug
== FIX_NOTSPECIFIED
)
237 *fix_firmwarebug
= fix
;
239 else if (opt
== 'd' && type
) {
240 // TODO: Check valid types
251 // Parse '-v' and '-F' options in preset string, return false on error.
252 static inline bool parse_presets(const char * presets
,
253 ata_vendor_attr_defs
& defs
,
254 unsigned char & fix_firmwarebug
)
256 return parse_db_presets(presets
, &defs
, &fix_firmwarebug
, 0);
259 // Parse '-d' option in preset string, return false on error.
260 static inline bool parse_usb_type(const char * presets
, std::string
& type
)
262 return parse_db_presets(presets
, 0, 0, &type
);
265 // Parse "USB: [DEVICE] ; [BRIDGE]" string
266 static void parse_usb_names(const char * names
, usb_dev_info
& info
)
268 int n1
= -1, n2
= -1, n3
= -1;
269 sscanf(names
, "USB: %n%*[^;]%n; %n", &n1
, &n2
, &n3
);
270 if (0 < n1
&& n1
< n2
)
271 info
.usb_device
.assign(names
+n1
, n2
-n1
);
273 sscanf(names
, "USB: ; %n", &n3
);
275 info
.usb_bridge
= names
+n3
;
278 // Search drivedb for USB device with vendor:product ID.
279 int lookup_usb_device(int vendor_id
, int product_id
, int bcd_device
,
280 usb_dev_info
& info
, usb_dev_info
& info2
)
282 // Format strings to match
283 char usb_id_str
[16], bcd_dev_str
[16];
284 snprintf(usb_id_str
, sizeof(usb_id_str
), "0x%04x:0x%04x", vendor_id
, product_id
);
286 snprintf(bcd_dev_str
, sizeof(bcd_dev_str
), "0x%04x", bcd_device
);
291 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
292 const drive_settings
& dbentry
= knowndrives
[i
];
294 // Skip drive entries
295 if (!is_usb_entry(&dbentry
))
298 // Check whether USB vendor:product ID matches
299 if (!match(dbentry
.modelregexp
, usb_id_str
))
304 if (!parse_usb_type(dbentry
.presets
, d
.usb_type
))
305 return 0; // Syntax error
306 parse_usb_names(dbentry
.modelfamily
, d
);
308 // If two entries with same vendor:product ID have different
309 // types, use bcd_device (if provided by OS) to select entry.
310 if ( *dbentry
.firmwareregexp
&& *bcd_dev_str
311 && match(dbentry
.firmwareregexp
, bcd_dev_str
)) {
312 // Exact match including bcd_device
317 // First match without bcd_device
320 else if (info
.usb_type
!= d
.usb_type
) {
321 // Another possible match with different type
322 info2
= d
; found
= 2;
326 // Stop search at first matching entry with empty bcd_device
327 if (!*dbentry
.firmwareregexp
)
334 // Shows one entry of knowndrives[], returns #errors.
335 static int showonepreset(const drive_settings
* dbentry
)
339 && dbentry
->modelfamily
340 && dbentry
->modelregexp
&& *dbentry
->modelregexp
341 && dbentry
->firmwareregexp
342 && dbentry
->warningmsg
343 && dbentry
->presets
)) {
344 pout("Invalid drive database entry. Please report\n"
345 "this error to smartmontools developers at " PACKAGE_BUGREPORT
".\n");
349 bool usb
= is_usb_entry(dbentry
);
351 // print and check model and firmware regular expressions
353 regular_expression regex
;
354 pout("%-*s %s\n", TABLEPRINTWIDTH
, (!usb
? "MODEL REGEXP:" : "USB Vendor:Product:"),
355 dbentry
->modelregexp
);
356 if (!compile(regex
, dbentry
->modelregexp
))
359 pout("%-*s %s\n", TABLEPRINTWIDTH
, (!usb
? "FIRMWARE REGEXP:" : "USB bcdDevice:"),
360 *dbentry
->firmwareregexp
? dbentry
->firmwareregexp
: ".*"); // preserve old output (TODO: Change)
361 if (*dbentry
->firmwareregexp
&& !compile(regex
, dbentry
->firmwareregexp
))
365 pout("%-*s %s\n", TABLEPRINTWIDTH
, "MODEL FAMILY:", dbentry
->modelfamily
);
367 // if there are any presets, then show them
368 unsigned char fix_firmwarebug
= 0;
369 bool first_preset
= true;
370 if (*dbentry
->presets
) {
371 ata_vendor_attr_defs defs
;
372 if (!parse_presets(dbentry
->presets
, defs
, fix_firmwarebug
)) {
373 pout("Syntax error in preset option string \"%s\"\n", dbentry
->presets
);
376 for (int i
= 0; i
< MAX_ATTRIBUTE_NUM
; i
++) {
377 if (defs
[i
].priority
!= PRIOR_DEFAULT
) {
378 std::string name
= ata_get_smart_attr_name(i
, defs
);
379 // Use leading zeros instead of spaces so that everything lines up.
380 pout("%-*s %03d %s\n", TABLEPRINTWIDTH
, first_preset
? "ATTRIBUTE OPTIONS:" : "",
382 // Check max name length suitable for smartctl -A output
383 const unsigned maxlen
= 23;
384 if (name
.size() > maxlen
) {
385 pout("%*s\n", TABLEPRINTWIDTH
+6+maxlen
, "Error: Attribute name too long ------^");
388 first_preset
= false;
393 pout("%-*s %s\n", TABLEPRINTWIDTH
, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required.");
395 // describe firmwarefix
396 if (fix_firmwarebug
) {
397 const char * fixdesc
;
398 switch (fix_firmwarebug
) {
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
= "UNKNOWN"; errcnt
++;
412 pout("%-*s %s\n", TABLEPRINTWIDTH
, "OTHER PRESETS:", fixdesc
);
417 usb_dev_info info
; parse_usb_names(dbentry
->modelfamily
, info
);
418 pout("%-*s %s\n", TABLEPRINTWIDTH
, "USB Device:",
419 (!info
.usb_device
.empty() ? info
.usb_device
.c_str() : "[unknown]"));
420 pout("%-*s %s\n", TABLEPRINTWIDTH
, "USB Bridge:",
421 (!info
.usb_bridge
.empty() ? info
.usb_bridge
.c_str() : "[unknown]"));
423 if (*dbentry
->presets
&& !parse_usb_type(dbentry
->presets
, info
.usb_type
)) {
424 pout("Syntax error in USB type string \"%s\"\n", dbentry
->presets
);
427 pout("%-*s %s\n", TABLEPRINTWIDTH
, "USB Type",
428 (!info
.usb_type
.empty() ? info
.usb_type
.c_str() : "[unsupported]"));
431 // Print any special warnings
432 if (*dbentry
->warningmsg
)
433 pout("%-*s %s\n", TABLEPRINTWIDTH
, "WARNINGS:", dbentry
->warningmsg
);
437 // Shows all presets for drives in knowndrives[].
438 // Returns #syntax errors.
441 // loop over all entries in the knowndrives[] table, printing them
442 // out in a nice format
444 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
445 errcnt
+= showonepreset(&knowndrives
[i
]);
449 pout("Total number of entries :%5u\n"
450 "Entries read from file(s):%5u\n\n",
451 knowndrives
.size(), knowndrives
.custom_size());
453 pout("For information about adding a drive to the database see the FAQ on the\n");
454 pout("smartmontools home page: " PACKAGE_HOMEPAGE
"\n");
457 pout("\nFound %d syntax error(s) in database.\n"
458 "Please inform smartmontools developers at " PACKAGE_BUGREPORT
"\n", errcnt
);
462 // Shows all matching presets for a drive in knowndrives[].
463 // Returns # matching entries.
464 int showmatchingpresets(const char *model
, const char *firmware
)
467 const char * firmwaremsg
= (firmware
? firmware
: "(any)");
469 for (unsigned i
= 0; i
< knowndrives
.size(); i
++) {
470 if (!match(knowndrives
[i
].modelregexp
, model
))
472 if ( firmware
&& *knowndrives
[i
].firmwareregexp
473 && !match(knowndrives
[i
].firmwareregexp
, firmware
))
477 pout("Drive found in smartmontools Database. Drive identity strings:\n"
480 "match smartmontools Drive Database entry:\n",
481 TABLEPRINTWIDTH
, "MODEL:", model
, TABLEPRINTWIDTH
, "FIRMWARE:", firmwaremsg
);
483 pout("and match these additional entries:\n");
484 showonepreset(&knowndrives
[i
]);
488 pout("No presets are defined for this drive. Its identity strings:\n"
491 "do not match any of the known regular expressions.\n",
496 // Shows the presets (if any) that are available for the given drive.
497 void show_presets(const ata_identify_device
* drive
)
499 char model
[MODEL_STRING_LENGTH
+1], firmware
[FIRMWARE_STRING_LENGTH
+1];
501 // get the drive's model/firmware strings
502 ata_format_id_string(model
, drive
->model
, sizeof(model
)-1);
503 ata_format_id_string(firmware
, drive
->fw_rev
, sizeof(firmware
)-1);
505 // and search to see if they match values in the table
506 const drive_settings
* dbentry
= lookup_drive(model
, firmware
);
509 pout("No presets are defined for this drive. Its identity strings:\n"
512 "do not match any of the known regular expressions.\n"
513 "Use -P showall to list all known regular expressions.\n",
518 // We found a matching drive. Print out all information about it.
519 pout("Drive found in smartmontools Database. Drive identity strings:\n"
522 "match smartmontools Drive Database entry:\n",
523 TABLEPRINTWIDTH
, "MODEL:", model
, TABLEPRINTWIDTH
, "FIRMWARE:", firmware
);
524 showonepreset(dbentry
);
527 // Searches drive database and sets preset vendor attribute
528 // options in defs and fix_firmwarebug.
529 // Values that have already been set will not be changed.
530 // Returns pointer to database entry or nullptr if none found
531 const drive_settings
* lookup_drive_apply_presets(
532 const ata_identify_device
* drive
, ata_vendor_attr_defs
& defs
,
533 unsigned char & fix_firmwarebug
)
535 // get the drive's model/firmware strings
536 char model
[MODEL_STRING_LENGTH
+1], firmware
[FIRMWARE_STRING_LENGTH
+1];
537 ata_format_id_string(model
, drive
->model
, sizeof(model
)-1);
538 ata_format_id_string(firmware
, drive
->fw_rev
, sizeof(firmware
)-1);
540 // Look up the drive in knowndrives[].
541 const drive_settings
* dbentry
= lookup_drive(model
, firmware
);
545 if (*dbentry
->presets
) {
547 if (!parse_presets(dbentry
->presets
, defs
, fix_firmwarebug
))
548 pout("Syntax error in preset option string \"%s\"\n", dbentry
->presets
);
554 /////////////////////////////////////////////////////////////////////////////
555 // Parser for drive database files
557 // Abstract pointer to read file input.
558 // Operations supported: c = *p; c = p[1]; ++p;
562 explicit stdin_iterator(FILE * f
)
563 : m_f(f
) { get(); get(); }
565 stdin_iterator
& operator++()
566 { get(); return *this; }
568 char operator*() const
571 char operator[](int i
) const
585 void stdin_iterator::get()
589 m_next
= (ch
!= EOF
? ch
: 0);
592 void stdin_iterator::fail() const
594 throw std::runtime_error("stdin_iterator: wrong usage");
598 // Use above as parser input 'pointer'. Can easily be changed later
599 // to e.g. 'const char *' if above is too slow.
600 typedef stdin_iterator parse_ptr
;
602 // Skip whitespace and comments.
603 static parse_ptr
skip_white(parse_ptr src
, const char * path
, int & line
)
605 for ( ; ; ++src
) switch (*src
) {
618 while (*src
&& *src
!= '\n')
624 // skip '/* comment */'
628 pout("%s(%d): Missing '*/'\n", path
, line
);
631 char c
= *src
; ++src
;
634 else if (c
== '*' && *src
== '/')
648 // Info about a token.
655 token_info() : type(0), line(0) { }
659 static parse_ptr
get_token(parse_ptr src
, token_info
& token
, const char * path
, int & line
)
661 src
= skip_white(src
, path
, line
);
663 case '{': case '}': case ',':
665 token
.type
= *src
; token
.line
= line
;
671 token
.type
= '"'; token
.line
= line
;
674 for (++src
; *src
!= '"'; ++src
) {
676 if (!c
|| c
== '\n' || (c
== '\\' && !src
[1])) {
677 pout("%s(%d): Missing terminating '\"'\n", path
, line
);
678 token
.type
= '?'; token
.line
= line
;
684 case 'n' : c
= '\n'; break;
685 case '\n': ++line
; break;
686 case '\\': case '"': break;
688 pout("%s(%d): Unknown escape sequence '\\%c'\n", path
, line
, c
);
689 token
.type
= '?'; token
.line
= line
;
695 // Lookahead to detect string constant concatentation
696 src
= skip_white(++src
, path
, line
);
697 } while (*src
== '"');
702 token
.type
= 0; token
.line
= line
;
706 pout("%s(%d): Syntax error, invalid char '%c'\n", path
, line
, *src
);
707 token
.type
= '?'; token
.line
= line
;
708 while (*src
&& *src
!= '\n')
716 // Parse drive database from abstract input pointer.
717 static bool parse_drive_database(parse_ptr src
, drive_database
& db
, const char * path
)
719 int state
= 0, field
= 0;
720 std::string values
[5];
723 token_info token
; int line
= 1;
724 src
= get_token(src
, token
, path
, line
);
726 // EOF is ok after '}', trailing ',' is also allowed.
727 if (!token
.type
&& (state
== 0 || state
== 4))
730 // Check expected token
731 const char expect
[] = "{\",},";
732 if (token
.type
!= expect
[state
]) {
733 if (token
.type
!= '?')
734 pout("%s(%d): Syntax error, '%c' expected\n", path
, token
.line
, expect
[state
]);
736 // Skip to next entry
737 while (token
.type
&& token
.type
!= '{')
738 src
= get_token(src
, token
, path
, line
);
745 // Interpret parser state
747 case 0: // ... ^{...}
748 state
= 1; field
= 0;
750 case 1: // {... ^"..." ...}
753 if (!token
.value
.empty()) {
754 regular_expression regex
;
755 if (!regex
.compile(token
.value
.c_str(), REG_EXTENDED
)) {
756 pout("%s(%d): Error in regular expression: %s\n", path
, token
.line
, regex
.get_errmsg());
760 else if (field
== 1) {
761 pout("%s(%d): Missing regular expression for drive model\n", path
, token
.line
);
766 if (!token
.value
.empty()) {
767 if (!is_usb_modelfamily(values
[0].c_str())) {
768 ata_vendor_attr_defs defs
; unsigned char fix
= 0;
769 if (!parse_presets(token
.value
.c_str(), defs
, fix
)) {
770 pout("%s(%d): Syntax error in preset option string\n", path
, token
.line
);
776 if (!parse_usb_type(token
.value
.c_str(), type
)) {
777 pout("%s(%d): Syntax error in USB type string\n", path
, token
.line
);
784 values
[field
] = token
.value
;
785 state
= (++field
< 5 ? 2 : 3);
787 case 2: // {... "..."^, ...}
790 case 3: // {...^}, ...
792 drive_settings entry
;
793 entry
.modelfamily
= values
[0].c_str();
794 entry
.modelregexp
= values
[1].c_str();
795 entry
.firmwareregexp
= values
[2].c_str();
796 entry
.warningmsg
= values
[3].c_str();
797 entry
.presets
= values
[4].c_str();
802 case 4: // {...}^, ...
806 pout("Bad state %d\n", state
);
809 src
= get_token(src
, token
, path
, line
);
814 // Read drive database from file.
815 bool read_drive_database(const char * path
)
817 stdio_file
f(path
, "r"
818 #ifdef __CYGWIN__ // Allow files with '\r\n'.
823 pout("%s: cannot open drive database file\n", path
);
827 return parse_drive_database(parse_ptr(f
), knowndrives
, path
);
830 // Get path for additional database file
831 const char * get_drivedb_path_add()
834 return SMARTMONTOOLS_SYSCONFDIR
"/smart_drivedb.h";
836 static std::string path
= get_exe_dir() + "/drivedb-add.h";
841 #ifdef SMARTMONTOOLS_DRIVEDBDIR
843 // Get path for default database file
844 const char * get_drivedb_path_default()
847 return SMARTMONTOOLS_DRIVEDBDIR
"/drivedb.h";
849 static std::string path
= get_exe_dir() + "/drivedb.h";
856 // Read drive databases from standard places.
857 bool read_default_drive_databases()
859 // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h
860 const char * db1
= get_drivedb_path_add();
861 if (!access(db1
, 0)) {
862 if (!read_drive_database(db1
))
866 #ifdef SMARTMONTOOLS_DRIVEDBDIR
867 // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h
868 const char * db2
= get_drivedb_path_default();
869 if (!access(db2
, 0)) {
870 if (!read_drive_database(db2
))
876 // Append builtin table.
877 knowndrives
.append(builtin_knowndrives
,
878 sizeof(builtin_knowndrives
)/sizeof(builtin_knowndrives
[0]));