]>
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 GI |
7 | * Copyright (C) 2003-11 Philip Williams, Bruce Allen |
8 | * Copyright (C) 2008-11 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 | |
16 | * (for example COPYING); if not, write to the Free | |
17 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
18 | * | |
19 | */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "int64.h" | |
23 | #include <stdio.h> | |
24 | #include "atacmds.h" | |
832b75ed | 25 | #include "knowndrives.h" |
2127e193 | 26 | #include "utility.h" |
832b75ed | 27 | |
2127e193 GI |
28 | #ifdef HAVE_UNISTD_H |
29 | #include <unistd.h> | |
30 | #endif | |
31 | #ifdef _WIN32 | |
32 | #include <io.h> // access() | |
33 | #endif | |
34 | ||
35 | #include <stdexcept> | |
36 | ||
d008864d | 37 | const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3447 2011-10-14 20:32:00Z chrfranke $" |
a23d5117 | 38 | KNOWNDRIVES_H_CVSID; |
832b75ed GG |
39 | |
40 | #define MODEL_STRING_LENGTH 40 | |
41 | #define FIRMWARE_STRING_LENGTH 8 | |
42 | #define TABLEPRINTWIDTH 19 | |
43 | ||
832b75ed | 44 | |
a23d5117 GI |
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[] = { | |
52 | #include "drivedb.h" | |
832b75ed GG |
53 | }; |
54 | ||
2127e193 GI |
55 | |
56 | /// Drive database class. Stores custom entries read from file. | |
57 | /// Provides transparent access to concatenation of custom and | |
58 | /// default table. | |
59 | class drive_database | |
60 | { | |
61 | public: | |
62 | drive_database(); | |
63 | ||
64 | ~drive_database(); | |
65 | ||
66 | /// Get total number of entries. | |
67 | unsigned size() const | |
68 | { return m_custom_tab.size() + m_builtin_size; } | |
69 | ||
70 | /// Get number of custom entries. | |
71 | unsigned custom_size() const | |
72 | { return m_custom_tab.size(); } | |
73 | ||
74 | /// Array access. | |
75 | const drive_settings & operator[](unsigned i); | |
76 | ||
77 | /// Append new custom entry. | |
78 | void push_back(const drive_settings & src); | |
79 | ||
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; } | |
83 | ||
84 | private: | |
85 | const drive_settings * m_builtin_tab; | |
86 | unsigned m_builtin_size; | |
87 | ||
88 | std::vector<drive_settings> m_custom_tab; | |
89 | std::vector<char *> m_custom_strings; | |
90 | ||
91 | const char * copy_string(const char * str); | |
92 | ||
93 | drive_database(const drive_database &); | |
94 | void operator=(const drive_database &); | |
95 | }; | |
96 | ||
97 | drive_database::drive_database() | |
98 | : m_builtin_tab(0), m_builtin_size(0) | |
99 | { | |
100 | } | |
101 | ||
102 | drive_database::~drive_database() | |
103 | { | |
104 | for (unsigned i = 0; i < m_custom_strings.size(); i++) | |
105 | delete [] m_custom_strings[i]; | |
106 | } | |
107 | ||
108 | const drive_settings & drive_database::operator[](unsigned i) | |
109 | { | |
110 | return (i < m_custom_tab.size() ? m_custom_tab[i] | |
111 | : m_builtin_tab[i - m_custom_tab.size()] ); | |
112 | } | |
113 | ||
114 | void drive_database::push_back(const drive_settings & src) | |
115 | { | |
116 | drive_settings dest; | |
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); | |
123 | } | |
124 | ||
125 | const char * drive_database::copy_string(const char * src) | |
126 | { | |
127 | char * dest = new char[strlen(src)+1]; | |
128 | try { | |
129 | m_custom_strings.push_back(dest); | |
130 | } | |
131 | catch (...) { | |
132 | delete [] dest; throw; | |
133 | } | |
134 | return strcpy(dest, src); | |
135 | } | |
136 | ||
137 | ||
138 | /// The drive database. | |
139 | static drive_database knowndrives; | |
140 | ||
141 | ||
e9583e0c GI |
142 | // Return true if modelfamily string describes entry for USB ID |
143 | static bool is_usb_modelfamily(const char * modelfamily) | |
144 | { | |
145 | return !strncmp(modelfamily, "USB:", 4); | |
146 | } | |
147 | ||
148 | // Return true if entry for USB ID | |
149 | static inline bool is_usb_entry(const drive_settings * dbentry) | |
150 | { | |
151 | return is_usb_modelfamily(dbentry->modelfamily); | |
152 | } | |
153 | ||
2127e193 GI |
154 | // Compile regular expression, print message on failure. |
155 | static bool compile(regular_expression & regex, const char *pattern) | |
156 | { | |
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()); | |
161 | return false; | |
162 | } | |
163 | return true; | |
164 | } | |
165 | ||
166 | // Compile & match a regular expression. | |
167 | static bool match(const char * pattern, const char * str) | |
168 | { | |
169 | regular_expression regex; | |
170 | if (!compile(regex, pattern)) | |
171 | return false; | |
172 | return regex.full_match(str); | |
173 | } | |
174 | ||
832b75ed GG |
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 | |
2127e193 GI |
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. | |
cfbba5b9 | 179 | static const drive_settings * lookup_drive(const char * model, const char * firmware) |
832b75ed | 180 | { |
2127e193 GI |
181 | if (!model) |
182 | model = ""; | |
183 | if (!firmware) | |
184 | firmware = ""; | |
832b75ed | 185 | |
2127e193 | 186 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
e9583e0c GI |
187 | // Skip USB entries |
188 | if (is_usb_entry(&knowndrives[i])) | |
189 | continue; | |
190 | ||
2127e193 GI |
191 | // Check whether model matches the regular expression in knowndrives[i]. |
192 | if (!match(knowndrives[i].modelregexp, model)) | |
193 | continue; | |
832b75ed | 194 | |
2127e193 GI |
195 | // Model matches, now check firmware. "" matches always. |
196 | if (!( !*knowndrives[i].firmwareregexp | |
197 | || match(knowndrives[i].firmwareregexp, firmware))) | |
198 | continue; | |
832b75ed | 199 | |
2127e193 GI |
200 | // Found |
201 | return &knowndrives[i]; | |
832b75ed GG |
202 | } |
203 | ||
2127e193 GI |
204 | // Not found |
205 | return 0; | |
832b75ed GG |
206 | } |
207 | ||
e9583e0c GI |
208 | |
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) | |
2127e193 GI |
212 | { |
213 | for (int i = 0; ; ) { | |
214 | i += strspn(presets+i, " \t"); | |
215 | if (!presets[i]) | |
216 | break; | |
a7e8ffec GI |
217 | char opt, arg[80+1+13]; int len = -1; |
218 | if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) | |
2127e193 | 219 | return false; |
e9583e0c | 220 | if (opt == 'v' && defs) { |
bed94269 | 221 | // Parse "-v N,format[,name]" |
e9583e0c | 222 | if (!parse_attribute_def(arg, *defs, PRIOR_DATABASE)) |
2127e193 | 223 | return false; |
2127e193 | 224 | } |
e9583e0c | 225 | else if (opt == 'F' && fix_firmwarebug) { |
2127e193 GI |
226 | unsigned char fix; |
227 | if (!strcmp(arg, "samsung")) | |
228 | fix = FIX_SAMSUNG; | |
229 | else if (!strcmp(arg, "samsung2")) | |
230 | fix = FIX_SAMSUNG2; | |
231 | else if (!strcmp(arg, "samsung3")) | |
232 | fix = FIX_SAMSUNG3; | |
233 | else | |
234 | return false; | |
235 | // Set only if not set by user | |
e9583e0c GI |
236 | if (*fix_firmwarebug == FIX_NOTSPECIFIED) |
237 | *fix_firmwarebug = fix; | |
238 | } | |
239 | else if (opt == 'd' && type) { | |
240 | // TODO: Check valid types | |
241 | *type = arg; | |
2127e193 GI |
242 | } |
243 | else | |
244 | return false; | |
832b75ed | 245 | |
2127e193 GI |
246 | i += len; |
247 | } | |
248 | return true; | |
249 | } | |
250 | ||
e9583e0c GI |
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) | |
255 | { | |
256 | return parse_db_presets(presets, &defs, &fix_firmwarebug, 0); | |
257 | } | |
258 | ||
259 | // Parse '-d' option in preset string, return false on error. | |
260 | static inline bool parse_usb_type(const char * presets, std::string & type) | |
261 | { | |
262 | return parse_db_presets(presets, 0, 0, &type); | |
263 | } | |
264 | ||
265 | // Parse "USB: [DEVICE] ; [BRIDGE]" string | |
266 | static void parse_usb_names(const char * names, usb_dev_info & info) | |
267 | { | |
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); | |
272 | else | |
273 | sscanf(names, "USB: ; %n", &n3); | |
274 | if (0 < n3) | |
275 | info.usb_bridge = names+n3; | |
276 | } | |
277 | ||
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) | |
281 | { | |
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); | |
285 | if (bcd_device >= 0) | |
286 | snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device); | |
287 | else | |
288 | bcd_dev_str[0] = 0; | |
289 | ||
290 | int found = 0; | |
e9583e0c GI |
291 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
292 | const drive_settings & dbentry = knowndrives[i]; | |
293 | ||
294 | // Skip drive entries | |
295 | if (!is_usb_entry(&dbentry)) | |
296 | continue; | |
297 | ||
298 | // Check whether USB vendor:product ID matches | |
299 | if (!match(dbentry.modelregexp, usb_id_str)) | |
300 | continue; | |
301 | ||
302 | // Parse '-d type' | |
303 | usb_dev_info d; | |
304 | if (!parse_usb_type(dbentry.presets, d.usb_type)) | |
305 | return 0; // Syntax error | |
306 | parse_usb_names(dbentry.modelfamily, d); | |
307 | ||
308 | // If two entries with same vendor:product ID have different | |
309 | // types, use bcd_device (if provided by OS) to select entry. | |
cfbba5b9 GI |
310 | if ( *dbentry.firmwareregexp && *bcd_dev_str |
311 | && match(dbentry.firmwareregexp, bcd_dev_str)) { | |
312 | // Exact match including bcd_device | |
313 | info = d; found = 1; | |
314 | break; | |
315 | } | |
316 | else if (!found) { | |
317 | // First match without bcd_device | |
e9583e0c | 318 | info = d; found = 1; |
e9583e0c | 319 | } |
cfbba5b9 GI |
320 | else if (info.usb_type != d.usb_type) { |
321 | // Another possible match with different type | |
e9583e0c GI |
322 | info2 = d; found = 2; |
323 | break; | |
324 | } | |
cfbba5b9 GI |
325 | |
326 | // Stop search at first matching entry with empty bcd_device | |
327 | if (!*dbentry.firmwareregexp) | |
328 | break; | |
e9583e0c GI |
329 | } |
330 | ||
331 | return found; | |
332 | } | |
333 | ||
2127e193 GI |
334 | // Shows one entry of knowndrives[], returns #errors. |
335 | static int showonepreset(const drive_settings * dbentry) | |
336 | { | |
832b75ed | 337 | // Basic error check |
2127e193 GI |
338 | if (!( 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" | |
832b75ed | 345 | "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); |
2127e193 | 346 | return 1; |
832b75ed | 347 | } |
e9583e0c GI |
348 | |
349 | bool usb = is_usb_entry(dbentry); | |
350 | ||
2127e193 GI |
351 | // print and check model and firmware regular expressions |
352 | int errcnt = 0; | |
353 | regular_expression regex; | |
e9583e0c GI |
354 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"), |
355 | dbentry->modelregexp); | |
2127e193 GI |
356 | if (!compile(regex, dbentry->modelregexp)) |
357 | errcnt++; | |
832b75ed | 358 | |
e9583e0c GI |
359 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"), |
360 | *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) | |
2127e193 GI |
361 | if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) |
362 | errcnt++; | |
363 | ||
e9583e0c GI |
364 | if (!usb) { |
365 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); | |
366 | ||
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); | |
374 | errcnt++; | |
375 | } | |
376 | for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) { | |
377 | if (defs[i].priority != PRIOR_DEFAULT) { | |
d008864d | 378 | std::string name = ata_get_smart_attr_name(i, defs); |
e9583e0c GI |
379 | // Use leading zeros instead of spaces so that everything lines up. |
380 | pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", | |
d008864d GI |
381 | i, name.c_str()); |
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 ------^"); | |
386 | errcnt++; | |
387 | } | |
e9583e0c GI |
388 | first_preset = false; |
389 | } | |
390 | } | |
2127e193 | 391 | } |
e9583e0c GI |
392 | if (first_preset) |
393 | pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); | |
394 | ||
395 | // describe firmwarefix | |
396 | if (fix_firmwarebug) { | |
397 | const char * fixdesc; | |
398 | switch (fix_firmwarebug) { | |
399 | case FIX_SAMSUNG: | |
400 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; | |
401 | break; | |
402 | case FIX_SAMSUNG2: | |
403 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; | |
404 | break; | |
405 | case FIX_SAMSUNG3: | |
406 | fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; | |
407 | break; | |
408 | default: | |
409 | fixdesc = "UNKNOWN"; errcnt++; | |
410 | break; | |
2127e193 | 411 | } |
e9583e0c | 412 | pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); |
2127e193 | 413 | } |
832b75ed | 414 | } |
e9583e0c GI |
415 | else { |
416 | // Print USB info | |
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]")); | |
422 | ||
423 | if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) { | |
424 | pout("Syntax error in USB type string \"%s\"\n", dbentry->presets); | |
425 | errcnt++; | |
2127e193 | 426 | } |
e9583e0c GI |
427 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type", |
428 | (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]")); | |
832b75ed | 429 | } |
2127e193 | 430 | |
832b75ed | 431 | // Print any special warnings |
2127e193 GI |
432 | if (*dbentry->warningmsg) |
433 | pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); | |
434 | return errcnt; | |
832b75ed GG |
435 | } |
436 | ||
437 | // Shows all presets for drives in knowndrives[]. | |
2127e193 GI |
438 | // Returns #syntax errors. |
439 | int showallpresets() | |
440 | { | |
832b75ed GG |
441 | // loop over all entries in the knowndrives[] table, printing them |
442 | // out in a nice format | |
2127e193 GI |
443 | int errcnt = 0; |
444 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
445 | errcnt += showonepreset(&knowndrives[i]); | |
832b75ed GG |
446 | pout("\n"); |
447 | } | |
448 | ||
2127e193 GI |
449 | pout("Total number of entries :%5u\n" |
450 | "Entries read from file(s):%5u\n\n", | |
451 | knowndrives.size(), knowndrives.custom_size()); | |
452 | ||
832b75ed GG |
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"); | |
2127e193 GI |
455 | |
456 | if (errcnt > 0) | |
457 | pout("\nFound %d syntax error(s) in database.\n" | |
458 | "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt); | |
459 | return errcnt; | |
832b75ed GG |
460 | } |
461 | ||
462 | // Shows all matching presets for a drive in knowndrives[]. | |
463 | // Returns # matching entries. | |
2127e193 GI |
464 | int showmatchingpresets(const char *model, const char *firmware) |
465 | { | |
832b75ed GG |
466 | int cnt = 0; |
467 | const char * firmwaremsg = (firmware ? firmware : "(any)"); | |
832b75ed | 468 | |
2127e193 GI |
469 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
470 | if (!match(knowndrives[i].modelregexp, model)) | |
832b75ed | 471 | continue; |
2127e193 GI |
472 | if ( firmware && *knowndrives[i].firmwareregexp |
473 | && !match(knowndrives[i].firmwareregexp, firmware)) | |
832b75ed | 474 | continue; |
2127e193 | 475 | // Found |
832b75ed GG |
476 | if (++cnt == 1) |
477 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
478 | "%-*s %s\n" | |
479 | "%-*s %s\n" | |
480 | "match smartmontools Drive Database entry:\n", | |
481 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg); | |
482 | else if (cnt == 2) | |
483 | pout("and match these additional entries:\n"); | |
484 | showonepreset(&knowndrives[i]); | |
485 | pout("\n"); | |
486 | } | |
832b75ed GG |
487 | if (cnt == 0) |
488 | pout("No presets are defined for this drive. Its identity strings:\n" | |
489 | "MODEL: %s\n" | |
490 | "FIRMWARE: %s\n" | |
491 | "do not match any of the known regular expressions.\n", | |
492 | model, firmwaremsg); | |
493 | return cnt; | |
494 | } | |
495 | ||
496 | // Shows the presets (if any) that are available for the given drive. | |
cfbba5b9 | 497 | void show_presets(const ata_identify_device * drive) |
2127e193 | 498 | { |
832b75ed GG |
499 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
500 | ||
501 | // get the drive's model/firmware strings | |
cfbba5b9 GI |
502 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
503 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
504 | ||
832b75ed | 505 | // and search to see if they match values in the table |
2127e193 GI |
506 | const drive_settings * dbentry = lookup_drive(model, firmware); |
507 | if (!dbentry) { | |
832b75ed GG |
508 | // no matches found |
509 | pout("No presets are defined for this drive. Its identity strings:\n" | |
510 | "MODEL: %s\n" | |
511 | "FIRMWARE: %s\n" | |
512 | "do not match any of the known regular expressions.\n" | |
513 | "Use -P showall to list all known regular expressions.\n", | |
514 | model, firmware); | |
515 | return; | |
516 | } | |
517 | ||
518 | // We found a matching drive. Print out all information about it. | |
519 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
520 | "%-*s %s\n" | |
521 | "%-*s %s\n" | |
522 | "match smartmontools Drive Database entry:\n", | |
523 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware); | |
2127e193 | 524 | showonepreset(dbentry); |
832b75ed GG |
525 | } |
526 | ||
cfbba5b9 GI |
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) | |
2127e193 | 534 | { |
832b75ed | 535 | // get the drive's model/firmware strings |
2127e193 | 536 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
cfbba5b9 GI |
537 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
538 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
539 | ||
832b75ed | 540 | // Look up the drive in knowndrives[]. |
2127e193 GI |
541 | const drive_settings * dbentry = lookup_drive(model, firmware); |
542 | if (!dbentry) | |
cfbba5b9 | 543 | return 0; |
2127e193 GI |
544 | |
545 | if (*dbentry->presets) { | |
546 | // Apply presets | |
bed94269 | 547 | if (!parse_presets(dbentry->presets, defs, fix_firmwarebug)) |
2127e193 GI |
548 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); |
549 | } | |
cfbba5b9 | 550 | return dbentry; |
2127e193 GI |
551 | } |
552 | ||
553 | ||
554 | ///////////////////////////////////////////////////////////////////////////// | |
555 | // Parser for drive database files | |
556 | ||
557 | // Abstract pointer to read file input. | |
558 | // Operations supported: c = *p; c = p[1]; ++p; | |
559 | class stdin_iterator | |
560 | { | |
561 | public: | |
562 | explicit stdin_iterator(FILE * f) | |
563 | : m_f(f) { get(); get(); } | |
564 | ||
565 | stdin_iterator & operator++() | |
566 | { get(); return *this; } | |
567 | ||
568 | char operator*() const | |
569 | { return m_c; } | |
570 | ||
571 | char operator[](int i) const | |
572 | { | |
573 | if (i != 1) | |
574 | fail(); | |
575 | return m_next; | |
576 | } | |
577 | ||
578 | private: | |
579 | FILE * m_f; | |
580 | char m_c, m_next; | |
581 | void get(); | |
582 | void fail() const; | |
583 | }; | |
584 | ||
585 | void stdin_iterator::get() | |
586 | { | |
587 | m_c = m_next; | |
588 | int ch = getc(m_f); | |
589 | m_next = (ch != EOF ? ch : 0); | |
590 | } | |
591 | ||
592 | void stdin_iterator::fail() const | |
593 | { | |
594 | throw std::runtime_error("stdin_iterator: wrong usage"); | |
595 | } | |
596 | ||
597 | ||
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; | |
601 | ||
602 | // Skip whitespace and comments. | |
603 | static parse_ptr skip_white(parse_ptr src, const char * path, int & line) | |
604 | { | |
605 | for ( ; ; ++src) switch (*src) { | |
606 | case ' ': case '\t': | |
607 | continue; | |
608 | ||
609 | case '\n': | |
610 | ++line; | |
611 | continue; | |
612 | ||
613 | case '/': | |
614 | switch (src[1]) { | |
615 | case '/': | |
616 | // skip '// comment' | |
617 | ++src; ++src; | |
618 | while (*src && *src != '\n') | |
619 | ++src; | |
620 | if (*src) | |
621 | ++line; | |
622 | break; | |
623 | case '*': | |
624 | // skip '/* comment */' | |
625 | ++src; ++src; | |
626 | for (;;) { | |
627 | if (!*src) { | |
628 | pout("%s(%d): Missing '*/'\n", path, line); | |
629 | return src; | |
630 | } | |
631 | char c = *src; ++src; | |
632 | if (c == '\n') | |
633 | ++line; | |
634 | else if (c == '*' && *src == '/') | |
635 | break; | |
636 | } | |
637 | break; | |
638 | default: | |
639 | return src; | |
832b75ed | 640 | } |
2127e193 GI |
641 | continue; |
642 | ||
643 | default: | |
644 | return src; | |
645 | } | |
646 | } | |
647 | ||
648 | // Info about a token. | |
649 | struct token_info | |
650 | { | |
651 | char type; | |
652 | int line; | |
653 | std::string value; | |
654 | ||
655 | token_info() : type(0), line(0) { } | |
656 | }; | |
657 | ||
658 | // Get next token. | |
659 | static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line) | |
660 | { | |
661 | src = skip_white(src, path, line); | |
662 | switch (*src) { | |
663 | case '{': case '}': case ',': | |
664 | // Simple token | |
665 | token.type = *src; token.line = line; | |
666 | ++src; | |
667 | break; | |
668 | ||
669 | case '"': | |
670 | // String constant | |
671 | token.type = '"'; token.line = line; | |
672 | token.value = ""; | |
673 | do { | |
674 | for (++src; *src != '"'; ++src) { | |
675 | char c = *src; | |
676 | if (!c || c == '\n' || (c == '\\' && !src[1])) { | |
677 | pout("%s(%d): Missing terminating '\"'\n", path, line); | |
678 | token.type = '?'; token.line = line; | |
679 | return src; | |
680 | } | |
681 | if (c == '\\') { | |
682 | c = *++src; | |
683 | switch (c) { | |
684 | case 'n' : c = '\n'; break; | |
685 | case '\n': ++line; break; | |
686 | case '\\': case '"': break; | |
687 | default: | |
688 | pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c); | |
689 | token.type = '?'; token.line = line; | |
690 | continue; | |
691 | } | |
692 | } | |
693 | token.value += c; | |
694 | } | |
695 | // Lookahead to detect string constant concatentation | |
696 | src = skip_white(++src, path, line); | |
697 | } while (*src == '"'); | |
698 | break; | |
699 | ||
700 | case 0: | |
701 | // EOF | |
702 | token.type = 0; token.line = line; | |
703 | break; | |
704 | ||
705 | default: | |
706 | pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src); | |
707 | token.type = '?'; token.line = line; | |
708 | while (*src && *src != '\n') | |
709 | ++src; | |
710 | break; | |
711 | } | |
712 | ||
713 | return src; | |
714 | } | |
715 | ||
716 | // Parse drive database from abstract input pointer. | |
717 | static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path) | |
718 | { | |
719 | int state = 0, field = 0; | |
720 | std::string values[5]; | |
721 | bool ok = true; | |
722 | ||
723 | token_info token; int line = 1; | |
724 | src = get_token(src, token, path, line); | |
725 | for (;;) { | |
726 | // EOF is ok after '}', trailing ',' is also allowed. | |
727 | if (!token.type && (state == 0 || state == 4)) | |
728 | break; | |
729 | ||
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]); | |
735 | ok = false; | |
736 | // Skip to next entry | |
737 | while (token.type && token.type != '{') | |
738 | src = get_token(src, token, path, line); | |
739 | state = 0; | |
740 | if (token.type) | |
741 | continue; | |
742 | break; | |
832b75ed | 743 | } |
2127e193 GI |
744 | |
745 | // Interpret parser state | |
746 | switch (state) { | |
747 | case 0: // ... ^{...} | |
748 | state = 1; field = 0; | |
749 | break; | |
750 | case 1: // {... ^"..." ...} | |
751 | switch (field) { | |
752 | case 1: case 2: | |
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()); | |
757 | ok = false; | |
758 | } | |
759 | } | |
760 | else if (field == 1) { | |
761 | pout("%s(%d): Missing regular expression for drive model\n", path, token.line); | |
762 | ok = false; | |
763 | } | |
764 | break; | |
765 | case 4: | |
766 | if (!token.value.empty()) { | |
e9583e0c GI |
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); | |
771 | ok = false; | |
772 | } | |
773 | } | |
774 | else { | |
775 | std::string type; | |
776 | if (!parse_usb_type(token.value.c_str(), type)) { | |
777 | pout("%s(%d): Syntax error in USB type string\n", path, token.line); | |
778 | ok = false; | |
779 | } | |
2127e193 GI |
780 | } |
781 | } | |
782 | break; | |
783 | } | |
784 | values[field] = token.value; | |
785 | state = (++field < 5 ? 2 : 3); | |
786 | break; | |
787 | case 2: // {... "..."^, ...} | |
788 | state = 1; | |
789 | break; | |
790 | case 3: // {...^}, ... | |
791 | { | |
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(); | |
798 | db.push_back(entry); | |
799 | } | |
800 | state = 4; | |
801 | break; | |
802 | case 4: // {...}^, ... | |
803 | state = 0; | |
804 | break; | |
805 | default: | |
806 | pout("Bad state %d\n", state); | |
807 | return false; | |
808 | } | |
809 | src = get_token(src, token, path, line); | |
832b75ed | 810 | } |
2127e193 GI |
811 | return ok; |
812 | } | |
813 | ||
814 | // Read drive database from file. | |
815 | bool read_drive_database(const char * path) | |
816 | { | |
a23d5117 GI |
817 | stdio_file f(path, "r" |
818 | #ifdef __CYGWIN__ // Allow files with '\r\n'. | |
819 | "t" | |
820 | #endif | |
821 | ); | |
2127e193 GI |
822 | if (!f) { |
823 | pout("%s: cannot open drive database file\n", path); | |
824 | return false; | |
825 | } | |
826 | ||
827 | return parse_drive_database(parse_ptr(f), knowndrives, path); | |
828 | } | |
829 | ||
e9583e0c GI |
830 | // Get path for additional database file |
831 | const char * get_drivedb_path_add() | |
2127e193 GI |
832 | { |
833 | #ifndef _WIN32 | |
e9583e0c GI |
834 | return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h"; |
835 | #else | |
836 | static std::string path = get_exe_dir() + "/drivedb-add.h"; | |
837 | return path.c_str(); | |
838 | #endif | |
839 | } | |
840 | ||
841 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
842 | ||
843 | // Get path for default database file | |
844 | const char * get_drivedb_path_default() | |
845 | { | |
846 | #ifndef _WIN32 | |
847 | return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h"; | |
2127e193 | 848 | #else |
e9583e0c GI |
849 | static std::string path = get_exe_dir() + "/drivedb.h"; |
850 | return path.c_str(); | |
851 | #endif | |
852 | } | |
853 | ||
2127e193 | 854 | #endif |
e9583e0c GI |
855 | |
856 | // Read drive databases from standard places. | |
857 | bool read_default_drive_databases() | |
858 | { | |
859 | // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h | |
860 | const char * db1 = get_drivedb_path_add(); | |
2127e193 GI |
861 | if (!access(db1, 0)) { |
862 | if (!read_drive_database(db1)) | |
863 | return false; | |
864 | } | |
865 | ||
866 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
e9583e0c GI |
867 | // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h |
868 | const char * db2 = get_drivedb_path_default(); | |
2127e193 GI |
869 | if (!access(db2, 0)) { |
870 | if (!read_drive_database(db2)) | |
871 | return false; | |
872 | } | |
873 | else | |
874 | #endif | |
875 | { | |
876 | // Append builtin table. | |
877 | knowndrives.append(builtin_knowndrives, | |
878 | sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0])); | |
879 | } | |
880 | ||
881 | return true; | |
832b75ed | 882 | } |