]>
Commit | Line | Data |
---|---|---|
832b75ed | 1 | /* |
4d59bff9 | 2 | * knowndrives.cpp |
832b75ed GG |
3 | * |
4 | * Home page of code is: http://smartmontools.sourceforge.net | |
5 | * Address of support mailing list: smartmontools-support@lists.sourceforge.net | |
6 | * | |
cfbba5b9 | 7 | * Copyright (C) 2003-11 Philip Williams, Bruce Allen |
ee38a438 | 8 | * Copyright (C) 2008-12 Christian Franke <smartmontools-support@lists.sourceforge.net> |
832b75ed GG |
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 | |
ee38a438 | 16 | * (for example COPYING); If not, see <http://www.gnu.org/licenses/>. |
832b75ed GG |
17 | * |
18 | */ | |
19 | ||
20 | #include "config.h" | |
21 | #include "int64.h" | |
22 | #include <stdio.h> | |
23 | #include "atacmds.h" | |
832b75ed | 24 | #include "knowndrives.h" |
2127e193 | 25 | #include "utility.h" |
832b75ed | 26 | |
2127e193 GI |
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 | ||
ee38a438 | 36 | const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3719 2012-12-03 21:19:33Z chrfranke $" |
a23d5117 | 37 | KNOWNDRIVES_H_CVSID; |
832b75ed GG |
38 | |
39 | #define MODEL_STRING_LENGTH 40 | |
40 | #define FIRMWARE_STRING_LENGTH 8 | |
41 | #define TABLEPRINTWIDTH 19 | |
42 | ||
832b75ed | 43 | |
a23d5117 GI |
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" | |
832b75ed GG |
52 | }; |
53 | ||
2127e193 GI |
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 | { | |
ee38a438 GI |
126 | size_t len = strlen(src); |
127 | char * dest = new char[len+1]; | |
128 | memcpy(dest, src, len+1); | |
2127e193 GI |
129 | try { |
130 | m_custom_strings.push_back(dest); | |
131 | } | |
132 | catch (...) { | |
133 | delete [] dest; throw; | |
134 | } | |
ee38a438 | 135 | return dest; |
2127e193 GI |
136 | } |
137 | ||
138 | ||
139 | /// The drive database. | |
140 | static drive_database knowndrives; | |
141 | ||
142 | ||
e9583e0c GI |
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 | ||
2127e193 GI |
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 | ||
832b75ed GG |
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 | |
2127e193 GI |
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. | |
cfbba5b9 | 180 | static const drive_settings * lookup_drive(const char * model, const char * firmware) |
832b75ed | 181 | { |
2127e193 GI |
182 | if (!model) |
183 | model = ""; | |
184 | if (!firmware) | |
185 | firmware = ""; | |
832b75ed | 186 | |
2127e193 | 187 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
e9583e0c GI |
188 | // Skip USB entries |
189 | if (is_usb_entry(&knowndrives[i])) | |
190 | continue; | |
191 | ||
2127e193 GI |
192 | // Check whether model matches the regular expression in knowndrives[i]. |
193 | if (!match(knowndrives[i].modelregexp, model)) | |
194 | continue; | |
832b75ed | 195 | |
2127e193 GI |
196 | // Model matches, now check firmware. "" matches always. |
197 | if (!( !*knowndrives[i].firmwareregexp | |
198 | || match(knowndrives[i].firmwareregexp, firmware))) | |
199 | continue; | |
832b75ed | 200 | |
2127e193 GI |
201 | // Found |
202 | return &knowndrives[i]; | |
832b75ed GG |
203 | } |
204 | ||
2127e193 GI |
205 | // Not found |
206 | return 0; | |
832b75ed GG |
207 | } |
208 | ||
e9583e0c GI |
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, | |
ee38a438 | 212 | firmwarebug_defs * firmwarebugs, std::string * type) |
2127e193 GI |
213 | { |
214 | for (int i = 0; ; ) { | |
215 | i += strspn(presets+i, " \t"); | |
216 | if (!presets[i]) | |
217 | break; | |
a7e8ffec GI |
218 | char opt, arg[80+1+13]; int len = -1; |
219 | if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) | |
2127e193 | 220 | return false; |
e9583e0c | 221 | if (opt == 'v' && defs) { |
bed94269 | 222 | // Parse "-v N,format[,name]" |
e9583e0c | 223 | if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE)) |
2127e193 | 224 | return false; |
2127e193 | 225 | } |
ee38a438 GI |
226 | else if (opt == 'F' && firmwarebugs) { |
227 | firmwarebug_defs bug; | |
228 | if (!parse_firmwarebug_def(arg, bug)) | |
2127e193 | 229 | return false; |
ee38a438 GI |
230 | // Don't set if user specified '-F none'. |
231 | if (!firmwarebugs->is_set(BUG_NONE)) | |
232 | firmwarebugs->set(bug); | |
e9583e0c GI |
233 | } |
234 | else if (opt == 'd' && type) { | |
235 | // TODO: Check valid types | |
236 | *type = arg; | |
2127e193 GI |
237 | } |
238 | else | |
239 | return false; | |
832b75ed | 240 | |
2127e193 GI |
241 | i += len; |
242 | } | |
243 | return true; | |
244 | } | |
245 | ||
e9583e0c GI |
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, | |
ee38a438 | 249 | firmwarebug_defs & firmwarebugs) |
e9583e0c | 250 | { |
ee38a438 | 251 | return parse_db_presets(presets, &defs, &firmwarebugs, 0); |
e9583e0c GI |
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; | |
e9583e0c GI |
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. | |
cfbba5b9 GI |
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 | |
e9583e0c | 313 | info = d; found = 1; |
e9583e0c | 314 | } |
cfbba5b9 GI |
315 | else if (info.usb_type != d.usb_type) { |
316 | // Another possible match with different type | |
e9583e0c GI |
317 | info2 = d; found = 2; |
318 | break; | |
319 | } | |
cfbba5b9 GI |
320 | |
321 | // Stop search at first matching entry with empty bcd_device | |
322 | if (!*dbentry.firmwareregexp) | |
323 | break; | |
e9583e0c GI |
324 | } |
325 | ||
326 | return found; | |
327 | } | |
328 | ||
2127e193 GI |
329 | // Shows one entry of knowndrives[], returns #errors. |
330 | static int showonepreset(const drive_settings * dbentry) | |
331 | { | |
832b75ed | 332 | // Basic error check |
2127e193 GI |
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" | |
832b75ed | 340 | "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); |
2127e193 | 341 | return 1; |
832b75ed | 342 | } |
e9583e0c GI |
343 | |
344 | bool usb = is_usb_entry(dbentry); | |
345 | ||
2127e193 GI |
346 | // print and check model and firmware regular expressions |
347 | int errcnt = 0; | |
348 | regular_expression regex; | |
e9583e0c GI |
349 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"), |
350 | dbentry->modelregexp); | |
2127e193 GI |
351 | if (!compile(regex, dbentry->modelregexp)) |
352 | errcnt++; | |
832b75ed | 353 | |
e9583e0c GI |
354 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"), |
355 | *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) | |
2127e193 GI |
356 | if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) |
357 | errcnt++; | |
358 | ||
e9583e0c GI |
359 | if (!usb) { |
360 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); | |
361 | ||
362 | // if there are any presets, then show them | |
ee38a438 | 363 | firmwarebug_defs firmwarebugs; |
e9583e0c GI |
364 | bool first_preset = true; |
365 | if (*dbentry->presets) { | |
366 | ata_vendor_attr_defs defs; | |
ee38a438 | 367 | if (!parse_presets(dbentry->presets, defs, firmwarebugs)) { |
e9583e0c GI |
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) { | |
d008864d | 373 | std::string name = ata_get_smart_attr_name(i, defs); |
e9583e0c GI |
374 | // Use leading zeros instead of spaces so that everything lines up. |
375 | pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", | |
d008864d GI |
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 | } | |
e9583e0c GI |
383 | first_preset = false; |
384 | } | |
385 | } | |
2127e193 | 386 | } |
e9583e0c GI |
387 | if (first_preset) |
388 | pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); | |
389 | ||
390 | // describe firmwarefix | |
ee38a438 GI |
391 | for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) { |
392 | if (!firmwarebugs.is_set((firmwarebug_t)b)) | |
393 | continue; | |
e9583e0c | 394 | const char * fixdesc; |
ee38a438 GI |
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: | |
e9583e0c GI |
400 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; |
401 | break; | |
ee38a438 | 402 | case BUG_SAMSUNG2: |
e9583e0c GI |
403 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; |
404 | break; | |
ee38a438 | 405 | case BUG_SAMSUNG3: |
e9583e0c GI |
406 | fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; |
407 | break; | |
ee38a438 GI |
408 | case BUG_XERRORLBA: |
409 | fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)"; | |
410 | break; | |
e9583e0c GI |
411 | default: |
412 | fixdesc = "UNKNOWN"; errcnt++; | |
413 | break; | |
2127e193 | 414 | } |
e9583e0c | 415 | pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); |
2127e193 | 416 | } |
832b75ed | 417 | } |
e9583e0c GI |
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++; | |
2127e193 | 429 | } |
e9583e0c GI |
430 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type", |
431 | (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]")); | |
832b75ed | 432 | } |
2127e193 | 433 | |
832b75ed | 434 | // Print any special warnings |
2127e193 GI |
435 | if (*dbentry->warningmsg) |
436 | pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); | |
437 | return errcnt; | |
832b75ed GG |
438 | } |
439 | ||
440 | // Shows all presets for drives in knowndrives[]. | |
2127e193 GI |
441 | // Returns #syntax errors. |
442 | int showallpresets() | |
443 | { | |
832b75ed GG |
444 | // loop over all entries in the knowndrives[] table, printing them |
445 | // out in a nice format | |
2127e193 GI |
446 | int errcnt = 0; |
447 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
448 | errcnt += showonepreset(&knowndrives[i]); | |
832b75ed GG |
449 | pout("\n"); |
450 | } | |
451 | ||
2127e193 GI |
452 | pout("Total number of entries :%5u\n" |
453 | "Entries read from file(s):%5u\n\n", | |
454 | knowndrives.size(), knowndrives.custom_size()); | |
455 | ||
832b75ed GG |
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"); | |
2127e193 GI |
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; | |
832b75ed GG |
463 | } |
464 | ||
465 | // Shows all matching presets for a drive in knowndrives[]. | |
466 | // Returns # matching entries. | |
2127e193 GI |
467 | int showmatchingpresets(const char *model, const char *firmware) |
468 | { | |
832b75ed GG |
469 | int cnt = 0; |
470 | const char * firmwaremsg = (firmware ? firmware : "(any)"); | |
832b75ed | 471 | |
2127e193 GI |
472 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
473 | if (!match(knowndrives[i].modelregexp, model)) | |
832b75ed | 474 | continue; |
2127e193 GI |
475 | if ( firmware && *knowndrives[i].firmwareregexp |
476 | && !match(knowndrives[i].firmwareregexp, firmware)) | |
832b75ed | 477 | continue; |
2127e193 | 478 | // Found |
832b75ed GG |
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 | } | |
832b75ed GG |
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. | |
cfbba5b9 | 500 | void show_presets(const ata_identify_device * drive) |
2127e193 | 501 | { |
832b75ed GG |
502 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
503 | ||
504 | // get the drive's model/firmware strings | |
cfbba5b9 GI |
505 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
506 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
507 | ||
832b75ed | 508 | // and search to see if they match values in the table |
2127e193 GI |
509 | const drive_settings * dbentry = lookup_drive(model, firmware); |
510 | if (!dbentry) { | |
832b75ed GG |
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); | |
2127e193 | 527 | showonepreset(dbentry); |
832b75ed GG |
528 | } |
529 | ||
cfbba5b9 | 530 | // Searches drive database and sets preset vendor attribute |
ee38a438 | 531 | // options in defs and firmwarebugs. |
cfbba5b9 GI |
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, | |
ee38a438 | 536 | firmwarebug_defs & firmwarebugs) |
2127e193 | 537 | { |
832b75ed | 538 | // get the drive's model/firmware strings |
2127e193 | 539 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
cfbba5b9 GI |
540 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
541 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
542 | ||
832b75ed | 543 | // Look up the drive in knowndrives[]. |
2127e193 GI |
544 | const drive_settings * dbentry = lookup_drive(model, firmware); |
545 | if (!dbentry) | |
cfbba5b9 | 546 | return 0; |
2127e193 GI |
547 | |
548 | if (*dbentry->presets) { | |
549 | // Apply presets | |
ee38a438 | 550 | if (!parse_presets(dbentry->presets, defs, firmwarebugs)) |
2127e193 GI |
551 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); |
552 | } | |
cfbba5b9 | 553 | return dbentry; |
2127e193 GI |
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; | |
832b75ed | 643 | } |
2127e193 GI |
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; | |
832b75ed | 746 | } |
2127e193 GI |
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()) { | |
e9583e0c | 770 | if (!is_usb_modelfamily(values[0].c_str())) { |
ee38a438 | 771 | ata_vendor_attr_defs defs; firmwarebug_defs fix; |
e9583e0c GI |
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 | } | |
2127e193 GI |
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); | |
832b75ed | 813 | } |
2127e193 GI |
814 | return ok; |
815 | } | |
816 | ||
817 | // Read drive database from file. | |
818 | bool read_drive_database(const char * path) | |
819 | { | |
a23d5117 GI |
820 | stdio_file f(path, "r" |
821 | #ifdef __CYGWIN__ // Allow files with '\r\n'. | |
822 | "t" | |
823 | #endif | |
824 | ); | |
2127e193 GI |
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 | ||
e9583e0c GI |
833 | // Get path for additional database file |
834 | const char * get_drivedb_path_add() | |
2127e193 GI |
835 | { |
836 | #ifndef _WIN32 | |
e9583e0c GI |
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"; | |
2127e193 | 851 | #else |
e9583e0c GI |
852 | static std::string path = get_exe_dir() + "/drivedb.h"; |
853 | return path.c_str(); | |
854 | #endif | |
855 | } | |
856 | ||
2127e193 | 857 | #endif |
e9583e0c GI |
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(); | |
2127e193 GI |
864 | if (!access(db1, 0)) { |
865 | if (!read_drive_database(db1)) | |
866 | return false; | |
867 | } | |
868 | ||
869 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
e9583e0c GI |
870 | // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h |
871 | const char * db2 = get_drivedb_path_default(); | |
2127e193 GI |
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; | |
832b75ed | 885 | } |