]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * knowndrives.cpp | |
3 | * | |
4 | * Home page of code is: http://www.smartmontools.org | |
5 | * | |
6 | * Copyright (C) 2003-11 Philip Williams, Bruce Allen | |
7 | * Copyright (C) 2008-18 Christian Franke | |
8 | * | |
9 | * SPDX-License-Identifier: GPL-2.0-or-later | |
10 | */ | |
11 | ||
12 | #include "config.h" | |
13 | ||
14 | #include <stdio.h> | |
15 | #include "atacmds.h" | |
16 | #include "knowndrives.h" | |
17 | #include "utility.h" | |
18 | ||
19 | #ifdef HAVE_UNISTD_H | |
20 | #include <unistd.h> | |
21 | #endif | |
22 | #ifdef _WIN32 | |
23 | #include <io.h> // access() | |
24 | #endif | |
25 | ||
26 | #include <stdexcept> | |
27 | ||
28 | const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 4842 2018-12-02 16:07:26Z chrfranke $" | |
29 | KNOWNDRIVES_H_CVSID; | |
30 | ||
31 | #define MODEL_STRING_LENGTH 40 | |
32 | #define FIRMWARE_STRING_LENGTH 8 | |
33 | #define TABLEPRINTWIDTH 19 | |
34 | ||
35 | ||
36 | // Builtin table of known drives. | |
37 | // Used as a default if not read from | |
38 | // "/usr/{,/local}share/smartmontools/drivedb.h" | |
39 | // or any other file specified by '-B' option, | |
40 | // see read_default_drive_databases() below. | |
41 | // The drive_settings structure is described in drivedb.h. | |
42 | const drive_settings builtin_knowndrives[] = { | |
43 | #include "drivedb.h" | |
44 | }; | |
45 | ||
46 | const unsigned builtin_knowndrives_size = | |
47 | sizeof(builtin_knowndrives) / sizeof(builtin_knowndrives[0]); | |
48 | ||
49 | /// Drive database class. Stores custom entries read from file. | |
50 | /// Provides transparent access to concatenation of custom and | |
51 | /// default table. | |
52 | class drive_database | |
53 | { | |
54 | public: | |
55 | drive_database(); | |
56 | ||
57 | ~drive_database(); | |
58 | ||
59 | /// Get total number of entries. | |
60 | unsigned size() const | |
61 | { return m_custom_tab.size() + m_builtin_size; } | |
62 | ||
63 | /// Get number of custom entries. | |
64 | unsigned custom_size() const | |
65 | { return m_custom_tab.size(); } | |
66 | ||
67 | /// Array access. | |
68 | const drive_settings & operator[](unsigned i); | |
69 | ||
70 | /// Append new custom entry. | |
71 | void push_back(const drive_settings & src); | |
72 | ||
73 | /// Append builtin table. | |
74 | void append(const drive_settings * builtin_tab, unsigned builtin_size) | |
75 | { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; } | |
76 | ||
77 | private: | |
78 | const drive_settings * m_builtin_tab; | |
79 | unsigned m_builtin_size; | |
80 | ||
81 | std::vector<drive_settings> m_custom_tab; | |
82 | std::vector<char *> m_custom_strings; | |
83 | ||
84 | const char * copy_string(const char * str); | |
85 | ||
86 | drive_database(const drive_database &); | |
87 | void operator=(const drive_database &); | |
88 | }; | |
89 | ||
90 | drive_database::drive_database() | |
91 | : m_builtin_tab(0), m_builtin_size(0) | |
92 | { | |
93 | } | |
94 | ||
95 | drive_database::~drive_database() | |
96 | { | |
97 | for (unsigned i = 0; i < m_custom_strings.size(); i++) | |
98 | delete [] m_custom_strings[i]; | |
99 | } | |
100 | ||
101 | const drive_settings & drive_database::operator[](unsigned i) | |
102 | { | |
103 | return (i < m_custom_tab.size() ? m_custom_tab[i] | |
104 | : m_builtin_tab[i - m_custom_tab.size()] ); | |
105 | } | |
106 | ||
107 | void drive_database::push_back(const drive_settings & src) | |
108 | { | |
109 | drive_settings dest; | |
110 | dest.modelfamily = copy_string(src.modelfamily); | |
111 | dest.modelregexp = copy_string(src.modelregexp); | |
112 | dest.firmwareregexp = copy_string(src.firmwareregexp); | |
113 | dest.warningmsg = copy_string(src.warningmsg); | |
114 | dest.presets = copy_string(src.presets); | |
115 | m_custom_tab.push_back(dest); | |
116 | } | |
117 | ||
118 | const char * drive_database::copy_string(const char * src) | |
119 | { | |
120 | size_t len = strlen(src); | |
121 | char * dest = new char[len+1]; | |
122 | memcpy(dest, src, len+1); | |
123 | try { | |
124 | m_custom_strings.push_back(dest); | |
125 | } | |
126 | catch (...) { | |
127 | delete [] dest; throw; | |
128 | } | |
129 | return dest; | |
130 | } | |
131 | ||
132 | ||
133 | /// The drive database. | |
134 | static drive_database knowndrives; | |
135 | ||
136 | ||
137 | enum dbentry_type { | |
138 | DBENTRY_ATA_DEFAULT, | |
139 | DBENTRY_ATA, | |
140 | DBENTRY_USB | |
141 | }; | |
142 | ||
143 | // Return type of entry | |
144 | static dbentry_type get_modelfamily_type(const char * modelfamily) | |
145 | { | |
146 | if (modelfamily[0] == 'D' && !strcmp(modelfamily, "DEFAULT")) | |
147 | return DBENTRY_ATA_DEFAULT; | |
148 | else if(modelfamily[0] == 'U' && str_starts_with(modelfamily, "USB:")) | |
149 | return DBENTRY_USB; | |
150 | else | |
151 | return DBENTRY_ATA; | |
152 | } | |
153 | ||
154 | static inline dbentry_type get_dbentry_type(const drive_settings * dbentry) | |
155 | { | |
156 | return get_modelfamily_type(dbentry->modelfamily); | |
157 | } | |
158 | ||
159 | // Compile regular expression, print message on failure. | |
160 | static bool compile(regular_expression & regex, const char *pattern) | |
161 | { | |
162 | if (!regex.compile(pattern)) { | |
163 | pout("Internal error: unable to compile regular expression \"%s\": %s\n" | |
164 | "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", | |
165 | pattern, regex.get_errmsg()); | |
166 | return false; | |
167 | } | |
168 | return true; | |
169 | } | |
170 | ||
171 | // Compile & match a regular expression. | |
172 | static bool match(const char * pattern, const char * str) | |
173 | { | |
174 | regular_expression regex; | |
175 | if (!compile(regex, pattern)) | |
176 | return false; | |
177 | return regex.full_match(str); | |
178 | } | |
179 | ||
180 | // Searches knowndrives[] for a drive with the given model number and firmware | |
181 | // string. If either the drive's model or firmware strings are not set by the | |
182 | // manufacturer then values of NULL may be used. Returns the entry of the | |
183 | // first match in knowndrives[] or 0 if no match if found. | |
184 | static const drive_settings * lookup_drive(const char * model, const char * firmware) | |
185 | { | |
186 | if (!model) | |
187 | model = ""; | |
188 | if (!firmware) | |
189 | firmware = ""; | |
190 | ||
191 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
192 | // Skip DEFAULT and USB entries | |
193 | if (get_dbentry_type(&knowndrives[i]) != DBENTRY_ATA) | |
194 | continue; | |
195 | ||
196 | // Check whether model matches the regular expression in knowndrives[i]. | |
197 | if (!match(knowndrives[i].modelregexp, model)) | |
198 | continue; | |
199 | ||
200 | // Model matches, now check firmware. "" matches always. | |
201 | if (!( !*knowndrives[i].firmwareregexp | |
202 | || match(knowndrives[i].firmwareregexp, firmware))) | |
203 | continue; | |
204 | ||
205 | // Found | |
206 | return &knowndrives[i]; | |
207 | } | |
208 | ||
209 | // Not found | |
210 | return 0; | |
211 | } | |
212 | ||
213 | ||
214 | // Parse drive or USB options in preset string, return false on error. | |
215 | static bool parse_db_presets(const char * presets, ata_vendor_attr_defs * defs, | |
216 | firmwarebug_defs * firmwarebugs, std::string * type) | |
217 | { | |
218 | for (int i = 0; ; ) { | |
219 | i += strspn(presets+i, " \t"); | |
220 | if (!presets[i]) | |
221 | break; | |
222 | char opt, arg[80+1+13]; int len = -1; | |
223 | if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) | |
224 | return false; | |
225 | if (opt == 'v' && defs) { | |
226 | // Parse "-v N,format[,name[,HDD|SSD]]" | |
227 | if (!parse_attribute_def(arg, *defs, (firmwarebugs ? PRIOR_DATABASE : PRIOR_DEFAULT))) | |
228 | return false; | |
229 | } | |
230 | else if (opt == 'F' && firmwarebugs) { | |
231 | firmwarebug_defs bug; | |
232 | if (!parse_firmwarebug_def(arg, bug)) | |
233 | return false; | |
234 | // Don't set if user specified '-F none'. | |
235 | if (!firmwarebugs->is_set(BUG_NONE)) | |
236 | firmwarebugs->set(bug); | |
237 | } | |
238 | else if (opt == 'd' && type) { | |
239 | // TODO: Check valid types | |
240 | *type = arg; | |
241 | } | |
242 | else | |
243 | return false; | |
244 | ||
245 | i += len; | |
246 | } | |
247 | return true; | |
248 | } | |
249 | ||
250 | // Parse '-v' options in default preset string, return false on error. | |
251 | static inline bool parse_default_presets(const char * presets, | |
252 | ata_vendor_attr_defs & defs) | |
253 | { | |
254 | return parse_db_presets(presets, &defs, 0, 0); | |
255 | } | |
256 | ||
257 | // Parse '-v' and '-F' options in preset string, return false on error. | |
258 | static inline bool parse_presets(const char * presets, | |
259 | ata_vendor_attr_defs & defs, | |
260 | firmwarebug_defs & firmwarebugs) | |
261 | { | |
262 | return parse_db_presets(presets, &defs, &firmwarebugs, 0); | |
263 | } | |
264 | ||
265 | // Parse '-d' option in preset string, return false on error. | |
266 | static inline bool parse_usb_type(const char * presets, std::string & type) | |
267 | { | |
268 | return parse_db_presets(presets, 0, 0, &type); | |
269 | } | |
270 | ||
271 | // Parse "USB: [DEVICE] ; [BRIDGE]" string | |
272 | static void parse_usb_names(const char * names, usb_dev_info & info) | |
273 | { | |
274 | int n1 = -1, n2 = -1, n3 = -1; | |
275 | sscanf(names, "USB: %n%*[^;]%n; %n", &n1, &n2, &n3); | |
276 | if (0 < n1 && n1 < n2) | |
277 | info.usb_device.assign(names+n1, n2-n1); | |
278 | else | |
279 | sscanf(names, "USB: ; %n", &n3); | |
280 | if (0 < n3) | |
281 | info.usb_bridge = names+n3; | |
282 | } | |
283 | ||
284 | // Search drivedb for USB device with vendor:product ID. | |
285 | int lookup_usb_device(int vendor_id, int product_id, int bcd_device, | |
286 | usb_dev_info & info, usb_dev_info & info2) | |
287 | { | |
288 | // Format strings to match | |
289 | char usb_id_str[16], bcd_dev_str[16]; | |
290 | snprintf(usb_id_str, sizeof(usb_id_str), "0x%04x:0x%04x", vendor_id, product_id); | |
291 | if (bcd_device >= 0) | |
292 | snprintf(bcd_dev_str, sizeof(bcd_dev_str), "0x%04x", bcd_device); | |
293 | else | |
294 | bcd_dev_str[0] = 0; | |
295 | ||
296 | int found = 0; | |
297 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
298 | const drive_settings & dbentry = knowndrives[i]; | |
299 | ||
300 | // Skip drive entries | |
301 | if (get_dbentry_type(&dbentry) != DBENTRY_USB) | |
302 | continue; | |
303 | ||
304 | // Check whether USB vendor:product ID matches | |
305 | if (!match(dbentry.modelregexp, usb_id_str)) | |
306 | continue; | |
307 | ||
308 | // Parse '-d type' | |
309 | usb_dev_info d; | |
310 | if (!parse_usb_type(dbentry.presets, d.usb_type)) | |
311 | return 0; // Syntax error | |
312 | parse_usb_names(dbentry.modelfamily, d); | |
313 | ||
314 | // If two entries with same vendor:product ID have different | |
315 | // types, use bcd_device (if provided by OS) to select entry. | |
316 | if ( *dbentry.firmwareregexp && *bcd_dev_str | |
317 | && match(dbentry.firmwareregexp, bcd_dev_str)) { | |
318 | // Exact match including bcd_device | |
319 | info = d; found = 1; | |
320 | break; | |
321 | } | |
322 | else if (!found) { | |
323 | // First match without bcd_device | |
324 | info = d; found = 1; | |
325 | } | |
326 | else if (info.usb_type != d.usb_type) { | |
327 | // Another possible match with different type | |
328 | info2 = d; found = 2; | |
329 | break; | |
330 | } | |
331 | ||
332 | // Stop search at first matching entry with empty bcd_device | |
333 | if (!*dbentry.firmwareregexp) | |
334 | break; | |
335 | } | |
336 | ||
337 | return found; | |
338 | } | |
339 | ||
340 | // Shows one entry of knowndrives[], returns #errors. | |
341 | static int showonepreset(const drive_settings * dbentry) | |
342 | { | |
343 | // Basic error check | |
344 | if (!( dbentry | |
345 | && dbentry->modelfamily | |
346 | && dbentry->modelregexp && *dbentry->modelregexp | |
347 | && dbentry->firmwareregexp | |
348 | && dbentry->warningmsg | |
349 | && dbentry->presets )) { | |
350 | pout("Invalid drive database entry. Please report\n" | |
351 | "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); | |
352 | return 1; | |
353 | } | |
354 | ||
355 | dbentry_type type = get_dbentry_type(dbentry); | |
356 | bool usb = (type == DBENTRY_USB); | |
357 | ||
358 | // print and check model and firmware regular expressions | |
359 | int errcnt = 0; | |
360 | regular_expression regex; | |
361 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"), | |
362 | dbentry->modelregexp); | |
363 | if (!compile(regex, dbentry->modelregexp)) | |
364 | errcnt++; | |
365 | ||
366 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"), | |
367 | *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) | |
368 | if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) | |
369 | errcnt++; | |
370 | ||
371 | if (!usb) { | |
372 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); | |
373 | ||
374 | // if there are any presets, then show them | |
375 | firmwarebug_defs firmwarebugs; | |
376 | bool first_preset = true; | |
377 | if (*dbentry->presets) { | |
378 | ata_vendor_attr_defs defs; | |
379 | if (type == DBENTRY_ATA_DEFAULT) { | |
380 | if (!parse_default_presets(dbentry->presets, defs)) { | |
381 | pout("Syntax error in DEFAULT option string \"%s\"\n", dbentry->presets); | |
382 | errcnt++; | |
383 | } | |
384 | } | |
385 | else { | |
386 | if (!parse_presets(dbentry->presets, defs, firmwarebugs)) { | |
387 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); | |
388 | errcnt++; | |
389 | } | |
390 | } | |
391 | ||
392 | for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) { | |
393 | if (defs[i].priority != PRIOR_DEFAULT || !defs[i].name.empty()) { | |
394 | std::string name = ata_get_smart_attr_name(i, defs); | |
395 | // Use leading zeros instead of spaces so that everything lines up. | |
396 | pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", | |
397 | i, name.c_str()); | |
398 | // Check max name length suitable for smartctl -A output | |
399 | const unsigned maxlen = 23; | |
400 | if (name.size() > maxlen) { | |
401 | pout("%*s\n", TABLEPRINTWIDTH+6+maxlen, "Error: Attribute name too long ------^"); | |
402 | errcnt++; | |
403 | } | |
404 | first_preset = false; | |
405 | } | |
406 | } | |
407 | } | |
408 | if (first_preset) | |
409 | pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); | |
410 | ||
411 | // describe firmwarefix | |
412 | for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) { | |
413 | if (!firmwarebugs.is_set((firmwarebug_t)b)) | |
414 | continue; | |
415 | const char * fixdesc; | |
416 | switch ((firmwarebug_t)b) { | |
417 | case BUG_NOLOGDIR: | |
418 | fixdesc = "Avoids reading GP/SMART Log Directories (same as -F nologdir)"; | |
419 | break; | |
420 | case BUG_SAMSUNG: | |
421 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; | |
422 | break; | |
423 | case BUG_SAMSUNG2: | |
424 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; | |
425 | break; | |
426 | case BUG_SAMSUNG3: | |
427 | fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; | |
428 | break; | |
429 | case BUG_XERRORLBA: | |
430 | fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)"; | |
431 | break; | |
432 | default: | |
433 | fixdesc = "UNKNOWN"; errcnt++; | |
434 | break; | |
435 | } | |
436 | pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); | |
437 | } | |
438 | } | |
439 | else { | |
440 | // Print USB info | |
441 | usb_dev_info info; parse_usb_names(dbentry->modelfamily, info); | |
442 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Device:", | |
443 | (!info.usb_device.empty() ? info.usb_device.c_str() : "[unknown]")); | |
444 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Bridge:", | |
445 | (!info.usb_bridge.empty() ? info.usb_bridge.c_str() : "[unknown]")); | |
446 | ||
447 | if (*dbentry->presets && !parse_usb_type(dbentry->presets, info.usb_type)) { | |
448 | pout("Syntax error in USB type string \"%s\"\n", dbentry->presets); | |
449 | errcnt++; | |
450 | } | |
451 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type", | |
452 | (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]")); | |
453 | } | |
454 | ||
455 | // Print any special warnings | |
456 | if (*dbentry->warningmsg) | |
457 | pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); | |
458 | return errcnt; | |
459 | } | |
460 | ||
461 | // Shows all presets for drives in knowndrives[]. | |
462 | // Returns #syntax errors. | |
463 | int showallpresets() | |
464 | { | |
465 | // loop over all entries in the knowndrives[] table, printing them | |
466 | // out in a nice format | |
467 | int errcnt = 0; | |
468 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
469 | errcnt += showonepreset(&knowndrives[i]); | |
470 | pout("\n"); | |
471 | } | |
472 | ||
473 | pout("Total number of entries :%5u\n" | |
474 | "Entries read from file(s):%5u\n\n", | |
475 | knowndrives.size(), knowndrives.custom_size()); | |
476 | ||
477 | pout("For information about adding a drive to the database see the FAQ on the\n"); | |
478 | pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n"); | |
479 | ||
480 | if (errcnt > 0) | |
481 | pout("\nFound %d syntax error(s) in database.\n" | |
482 | "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt); | |
483 | return errcnt; | |
484 | } | |
485 | ||
486 | // Shows all matching presets for a drive in knowndrives[]. | |
487 | // Returns # matching entries. | |
488 | int showmatchingpresets(const char *model, const char *firmware) | |
489 | { | |
490 | int cnt = 0; | |
491 | const char * firmwaremsg = (firmware ? firmware : "(any)"); | |
492 | ||
493 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
494 | if (!match(knowndrives[i].modelregexp, model)) | |
495 | continue; | |
496 | if ( firmware && *knowndrives[i].firmwareregexp | |
497 | && !match(knowndrives[i].firmwareregexp, firmware)) | |
498 | continue; | |
499 | // Found | |
500 | if (++cnt == 1) | |
501 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
502 | "%-*s %s\n" | |
503 | "%-*s %s\n" | |
504 | "match smartmontools Drive Database entry:\n", | |
505 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg); | |
506 | else if (cnt == 2) | |
507 | pout("and match these additional entries:\n"); | |
508 | showonepreset(&knowndrives[i]); | |
509 | pout("\n"); | |
510 | } | |
511 | if (cnt == 0) | |
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 | model, firmwaremsg); | |
517 | return cnt; | |
518 | } | |
519 | ||
520 | // Shows the presets (if any) that are available for the given drive. | |
521 | void show_presets(const ata_identify_device * drive) | |
522 | { | |
523 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; | |
524 | ||
525 | // get the drive's model/firmware strings | |
526 | ata_format_id_string(model, drive->model, sizeof(model)-1); | |
527 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
528 | ||
529 | // and search to see if they match values in the table | |
530 | const drive_settings * dbentry = lookup_drive(model, firmware); | |
531 | if (!dbentry) { | |
532 | // no matches found | |
533 | pout("No presets are defined for this drive. Its identity strings:\n" | |
534 | "MODEL: %s\n" | |
535 | "FIRMWARE: %s\n" | |
536 | "do not match any of the known regular expressions.\n" | |
537 | "Use -P showall to list all known regular expressions.\n", | |
538 | model, firmware); | |
539 | return; | |
540 | } | |
541 | ||
542 | // We found a matching drive. Print out all information about it. | |
543 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
544 | "%-*s %s\n" | |
545 | "%-*s %s\n" | |
546 | "match smartmontools Drive Database entry:\n", | |
547 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware); | |
548 | showonepreset(dbentry); | |
549 | } | |
550 | ||
551 | // Searches drive database and sets preset vendor attribute | |
552 | // options in defs and firmwarebugs. | |
553 | // Values that have already been set will not be changed. | |
554 | // Returns pointer to database entry or nullptr if none found | |
555 | const drive_settings * lookup_drive_apply_presets( | |
556 | const ata_identify_device * drive, ata_vendor_attr_defs & defs, | |
557 | firmwarebug_defs & firmwarebugs) | |
558 | { | |
559 | // get the drive's model/firmware strings | |
560 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; | |
561 | ata_format_id_string(model, drive->model, sizeof(model)-1); | |
562 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
563 | ||
564 | // Look up the drive in knowndrives[]. | |
565 | const drive_settings * dbentry = lookup_drive(model, firmware); | |
566 | if (!dbentry) | |
567 | return 0; | |
568 | ||
569 | if (*dbentry->presets) { | |
570 | // Apply presets | |
571 | if (!parse_presets(dbentry->presets, defs, firmwarebugs)) | |
572 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); | |
573 | } | |
574 | return dbentry; | |
575 | } | |
576 | ||
577 | ||
578 | ///////////////////////////////////////////////////////////////////////////// | |
579 | // Parser for drive database files | |
580 | ||
581 | // Abstract pointer to read file input. | |
582 | // Operations supported: c = *p; c = p[1]; ++p; | |
583 | class stdin_iterator | |
584 | { | |
585 | public: | |
586 | explicit stdin_iterator(FILE * f) | |
587 | : m_f(f), m_next(0) { get(); get(); } | |
588 | ||
589 | stdin_iterator & operator++() | |
590 | { get(); return *this; } | |
591 | ||
592 | char operator*() const | |
593 | { return m_c; } | |
594 | ||
595 | char operator[](int i) const | |
596 | { | |
597 | if (i != 1) | |
598 | fail(); | |
599 | return m_next; | |
600 | } | |
601 | ||
602 | private: | |
603 | FILE * m_f; | |
604 | char m_c, m_next; | |
605 | void get(); | |
606 | void fail() const; | |
607 | }; | |
608 | ||
609 | void stdin_iterator::get() | |
610 | { | |
611 | m_c = m_next; | |
612 | int ch = getc(m_f); | |
613 | m_next = (ch != EOF ? ch : 0); | |
614 | } | |
615 | ||
616 | void stdin_iterator::fail() const | |
617 | { | |
618 | throw std::runtime_error("stdin_iterator: wrong usage"); | |
619 | } | |
620 | ||
621 | ||
622 | // Use above as parser input 'pointer'. Can easily be changed later | |
623 | // to e.g. 'const char *' if above is too slow. | |
624 | typedef stdin_iterator parse_ptr; | |
625 | ||
626 | // Skip whitespace and comments. | |
627 | static parse_ptr skip_white(parse_ptr src, const char * path, int & line) | |
628 | { | |
629 | for ( ; ; ++src) switch (*src) { | |
630 | case ' ': case '\t': | |
631 | continue; | |
632 | ||
633 | case '\n': | |
634 | ++line; | |
635 | continue; | |
636 | ||
637 | case '/': | |
638 | switch (src[1]) { | |
639 | case '/': | |
640 | // skip '// comment' | |
641 | ++src; ++src; | |
642 | while (*src && *src != '\n') | |
643 | ++src; | |
644 | if (*src) | |
645 | ++line; | |
646 | break; | |
647 | case '*': | |
648 | // skip '/* comment */' | |
649 | ++src; ++src; | |
650 | for (;;) { | |
651 | if (!*src) { | |
652 | pout("%s(%d): Missing '*/'\n", path, line); | |
653 | return src; | |
654 | } | |
655 | char c = *src; ++src; | |
656 | if (c == '\n') | |
657 | ++line; | |
658 | else if (c == '*' && *src == '/') | |
659 | break; | |
660 | } | |
661 | break; | |
662 | default: | |
663 | return src; | |
664 | } | |
665 | continue; | |
666 | ||
667 | default: | |
668 | return src; | |
669 | } | |
670 | } | |
671 | ||
672 | // Info about a token. | |
673 | struct token_info | |
674 | { | |
675 | char type; | |
676 | int line; | |
677 | std::string value; | |
678 | ||
679 | token_info() : type(0), line(0) { } | |
680 | }; | |
681 | ||
682 | // Get next token. | |
683 | static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line) | |
684 | { | |
685 | src = skip_white(src, path, line); | |
686 | switch (*src) { | |
687 | case '{': case '}': case ',': | |
688 | // Simple token | |
689 | token.type = *src; token.line = line; | |
690 | ++src; | |
691 | break; | |
692 | ||
693 | case '"': | |
694 | // String constant | |
695 | token.type = '"'; token.line = line; | |
696 | token.value = ""; | |
697 | do { | |
698 | for (++src; *src != '"'; ++src) { | |
699 | char c = *src; | |
700 | if (!c || c == '\n' || (c == '\\' && !src[1])) { | |
701 | pout("%s(%d): Missing terminating '\"'\n", path, line); | |
702 | token.type = '?'; token.line = line; | |
703 | return src; | |
704 | } | |
705 | if (c == '\\') { | |
706 | c = *++src; | |
707 | switch (c) { | |
708 | case 'n' : c = '\n'; break; | |
709 | case '\n': ++line; break; | |
710 | case '\\': case '"': break; | |
711 | default: | |
712 | pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c); | |
713 | token.type = '?'; token.line = line; | |
714 | continue; | |
715 | } | |
716 | } | |
717 | token.value += c; | |
718 | } | |
719 | // Lookahead to detect string constant concatenation | |
720 | src = skip_white(++src, path, line); | |
721 | } while (*src == '"'); | |
722 | break; | |
723 | ||
724 | case 0: | |
725 | // EOF | |
726 | token.type = 0; token.line = line; | |
727 | break; | |
728 | ||
729 | default: | |
730 | pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src); | |
731 | token.type = '?'; token.line = line; | |
732 | while (*src && *src != '\n') | |
733 | ++src; | |
734 | break; | |
735 | } | |
736 | ||
737 | return src; | |
738 | } | |
739 | ||
740 | // Parse drive database from abstract input pointer. | |
741 | static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path) | |
742 | { | |
743 | int state = 0, field = 0; | |
744 | std::string values[5]; | |
745 | bool ok = true; | |
746 | ||
747 | token_info token; int line = 1; | |
748 | src = get_token(src, token, path, line); | |
749 | for (;;) { | |
750 | // EOF is ok after '}', trailing ',' is also allowed. | |
751 | if (!token.type && (state == 0 || state == 4)) | |
752 | break; | |
753 | ||
754 | // Check expected token | |
755 | const char expect[] = "{\",},"; | |
756 | if (token.type != expect[state]) { | |
757 | if (token.type != '?') | |
758 | pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]); | |
759 | ok = false; | |
760 | // Skip to next entry | |
761 | while (token.type && token.type != '{') | |
762 | src = get_token(src, token, path, line); | |
763 | state = 0; | |
764 | if (token.type) | |
765 | continue; | |
766 | break; | |
767 | } | |
768 | ||
769 | // Interpret parser state | |
770 | switch (state) { | |
771 | case 0: // ... ^{...} | |
772 | state = 1; field = 0; | |
773 | break; | |
774 | case 1: // {... ^"..." ...} | |
775 | switch (field) { | |
776 | case 1: case 2: | |
777 | if (!token.value.empty()) { | |
778 | regular_expression regex; | |
779 | if (!regex.compile(token.value.c_str())) { | |
780 | pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg()); | |
781 | ok = false; | |
782 | } | |
783 | } | |
784 | else if (field == 1) { | |
785 | pout("%s(%d): Missing regular expression for drive model\n", path, token.line); | |
786 | ok = false; | |
787 | } | |
788 | break; | |
789 | case 4: | |
790 | if (!token.value.empty()) { | |
791 | // Syntax check | |
792 | switch (get_modelfamily_type(values[0].c_str())) { | |
793 | case DBENTRY_ATA_DEFAULT: { | |
794 | ata_vendor_attr_defs defs; | |
795 | if (!parse_default_presets(token.value.c_str(), defs)) { | |
796 | pout("%s(%d): Syntax error in DEFAULT option string\n", path, token.line); | |
797 | ok = false; | |
798 | } | |
799 | } break; | |
800 | default: { // DBENTRY_ATA | |
801 | ata_vendor_attr_defs defs; firmwarebug_defs fix; | |
802 | if (!parse_presets(token.value.c_str(), defs, fix)) { | |
803 | pout("%s(%d): Syntax error in preset option string\n", path, token.line); | |
804 | ok = false; | |
805 | } | |
806 | } break; | |
807 | case DBENTRY_USB: { | |
808 | std::string type; | |
809 | if (!parse_usb_type(token.value.c_str(), type)) { | |
810 | pout("%s(%d): Syntax error in USB type string\n", path, token.line); | |
811 | ok = false; | |
812 | } | |
813 | } break; | |
814 | } | |
815 | } | |
816 | break; | |
817 | } | |
818 | values[field] = token.value; | |
819 | state = (++field < 5 ? 2 : 3); | |
820 | break; | |
821 | case 2: // {... "..."^, ...} | |
822 | state = 1; | |
823 | break; | |
824 | case 3: // {...^}, ... | |
825 | { | |
826 | drive_settings entry; | |
827 | entry.modelfamily = values[0].c_str(); | |
828 | entry.modelregexp = values[1].c_str(); | |
829 | entry.firmwareregexp = values[2].c_str(); | |
830 | entry.warningmsg = values[3].c_str(); | |
831 | entry.presets = values[4].c_str(); | |
832 | db.push_back(entry); | |
833 | } | |
834 | state = 4; | |
835 | break; | |
836 | case 4: // {...}^, ... | |
837 | state = 0; | |
838 | break; | |
839 | default: | |
840 | pout("Bad state %d\n", state); | |
841 | return false; | |
842 | } | |
843 | src = get_token(src, token, path, line); | |
844 | } | |
845 | return ok; | |
846 | } | |
847 | ||
848 | // Read drive database from file. | |
849 | bool read_drive_database(const char * path) | |
850 | { | |
851 | stdio_file f(path, "r" | |
852 | #ifdef __CYGWIN__ // Allow files with '\r\n'. | |
853 | "t" | |
854 | #endif | |
855 | ); | |
856 | if (!f) { | |
857 | pout("%s: cannot open drive database file\n", path); | |
858 | return false; | |
859 | } | |
860 | ||
861 | return parse_drive_database(parse_ptr(f), knowndrives, path); | |
862 | } | |
863 | ||
864 | // Get path for additional database file | |
865 | const char * get_drivedb_path_add() | |
866 | { | |
867 | #ifndef _WIN32 | |
868 | return SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h"; | |
869 | #else | |
870 | static std::string path = get_exe_dir() + "/drivedb-add.h"; | |
871 | return path.c_str(); | |
872 | #endif | |
873 | } | |
874 | ||
875 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
876 | ||
877 | // Get path for default database file | |
878 | const char * get_drivedb_path_default() | |
879 | { | |
880 | #ifndef _WIN32 | |
881 | return SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h"; | |
882 | #else | |
883 | static std::string path = get_exe_dir() + "/drivedb.h"; | |
884 | return path.c_str(); | |
885 | #endif | |
886 | } | |
887 | ||
888 | #endif | |
889 | ||
890 | // Read drive databases from standard places. | |
891 | static bool read_default_drive_databases() | |
892 | { | |
893 | // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h | |
894 | const char * db1 = get_drivedb_path_add(); | |
895 | if (!access(db1, 0)) { | |
896 | if (!read_drive_database(db1)) | |
897 | return false; | |
898 | } | |
899 | ||
900 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
901 | // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h | |
902 | const char * db2 = get_drivedb_path_default(); | |
903 | if (!access(db2, 0)) { | |
904 | if (!read_drive_database(db2)) | |
905 | return false; | |
906 | } | |
907 | else | |
908 | #endif | |
909 | { | |
910 | // Append builtin table. | |
911 | knowndrives.append(builtin_knowndrives, builtin_knowndrives_size); | |
912 | } | |
913 | ||
914 | return true; | |
915 | } | |
916 | ||
917 | static ata_vendor_attr_defs default_attr_defs; | |
918 | ||
919 | // Initialize default_attr_defs. | |
920 | static bool init_default_attr_defs() | |
921 | { | |
922 | // Lookup default entry | |
923 | const drive_settings * entry = 0; | |
924 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
925 | if (get_dbentry_type(&knowndrives[i]) != DBENTRY_ATA_DEFAULT) | |
926 | continue; | |
927 | entry = &knowndrives[i]; | |
928 | break; | |
929 | } | |
930 | ||
931 | if (!entry) { | |
932 | // Fall back to builtin database | |
933 | for (unsigned i = 0; i < builtin_knowndrives_size; i++) { | |
934 | if (get_dbentry_type(&builtin_knowndrives[i]) != DBENTRY_ATA_DEFAULT) | |
935 | continue; | |
936 | entry = &builtin_knowndrives[i]; | |
937 | break; | |
938 | } | |
939 | ||
940 | if (!entry) | |
941 | throw std::logic_error("DEFAULT entry missing in builtin drive database"); | |
942 | ||
943 | pout("Warning: DEFAULT entry missing in drive database file(s)\n"); | |
944 | } | |
945 | ||
946 | if (!parse_default_presets(entry->presets, default_attr_defs)) { | |
947 | pout("Syntax error in DEFAULT drive database entry\n"); | |
948 | return false; | |
949 | } | |
950 | ||
951 | return true; | |
952 | } | |
953 | ||
954 | // Init default db entry and optionally read drive databases from standard places. | |
955 | bool init_drive_database(bool use_default_db) | |
956 | { | |
957 | if (use_default_db && !read_default_drive_databases()) | |
958 | return false; | |
959 | ||
960 | return init_default_attr_defs(); | |
961 | } | |
962 | ||
963 | // Get vendor attribute options from default db entry. | |
964 | const ata_vendor_attr_defs & get_default_attr_defs() | |
965 | { | |
966 | return default_attr_defs; | |
967 | } |