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