]>
Commit | Line | Data |
---|---|---|
832b75ed | 1 | /* |
4d59bff9 | 2 | * knowndrives.cpp |
832b75ed | 3 | * |
6b80b4d2 | 4 | * Home page of code is: http://www.smartmontools.org |
832b75ed | 5 | * |
cfbba5b9 | 6 | * Copyright (C) 2003-11 Philip Williams, Bruce Allen |
ff28b140 | 7 | * Copyright (C) 2008-18 Christian Franke |
832b75ed | 8 | * |
ff28b140 | 9 | * SPDX-License-Identifier: GPL-2.0-or-later |
832b75ed GG |
10 | */ |
11 | ||
12 | #include "config.h" | |
ff28b140 | 13 | |
832b75ed GG |
14 | #include <stdio.h> |
15 | #include "atacmds.h" | |
832b75ed | 16 | #include "knowndrives.h" |
2127e193 | 17 | #include "utility.h" |
832b75ed | 18 | |
2127e193 GI |
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 | ||
ff28b140 | 28 | const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 4842 2018-12-02 16:07:26Z chrfranke $" |
a23d5117 | 29 | KNOWNDRIVES_H_CVSID; |
832b75ed GG |
30 | |
31 | #define MODEL_STRING_LENGTH 40 | |
32 | #define FIRMWARE_STRING_LENGTH 8 | |
33 | #define TABLEPRINTWIDTH 19 | |
34 | ||
832b75ed | 35 | |
a23d5117 GI |
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" | |
832b75ed GG |
44 | }; |
45 | ||
6b80b4d2 JD |
46 | const unsigned builtin_knowndrives_size = |
47 | sizeof(builtin_knowndrives) / sizeof(builtin_knowndrives[0]); | |
2127e193 GI |
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 | { | |
ee38a438 GI |
120 | size_t len = strlen(src); |
121 | char * dest = new char[len+1]; | |
122 | memcpy(dest, src, len+1); | |
2127e193 GI |
123 | try { |
124 | m_custom_strings.push_back(dest); | |
125 | } | |
126 | catch (...) { | |
127 | delete [] dest; throw; | |
128 | } | |
ee38a438 | 129 | return dest; |
2127e193 GI |
130 | } |
131 | ||
132 | ||
133 | /// The drive database. | |
134 | static drive_database knowndrives; | |
135 | ||
136 | ||
6b80b4d2 JD |
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) | |
e9583e0c | 145 | { |
6b80b4d2 JD |
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; | |
e9583e0c GI |
152 | } |
153 | ||
6b80b4d2 | 154 | static inline dbentry_type get_dbentry_type(const drive_settings * dbentry) |
e9583e0c | 155 | { |
6b80b4d2 | 156 | return get_modelfamily_type(dbentry->modelfamily); |
e9583e0c GI |
157 | } |
158 | ||
2127e193 GI |
159 | // Compile regular expression, print message on failure. |
160 | static bool compile(regular_expression & regex, const char *pattern) | |
161 | { | |
ff28b140 | 162 | if (!regex.compile(pattern)) { |
2127e193 GI |
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 | ||
832b75ed GG |
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 | |
2127e193 GI |
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. | |
cfbba5b9 | 184 | static const drive_settings * lookup_drive(const char * model, const char * firmware) |
832b75ed | 185 | { |
2127e193 GI |
186 | if (!model) |
187 | model = ""; | |
188 | if (!firmware) | |
189 | firmware = ""; | |
832b75ed | 190 | |
2127e193 | 191 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
6b80b4d2 JD |
192 | // Skip DEFAULT and USB entries |
193 | if (get_dbentry_type(&knowndrives[i]) != DBENTRY_ATA) | |
e9583e0c GI |
194 | continue; |
195 | ||
2127e193 GI |
196 | // Check whether model matches the regular expression in knowndrives[i]. |
197 | if (!match(knowndrives[i].modelregexp, model)) | |
198 | continue; | |
832b75ed | 199 | |
2127e193 GI |
200 | // Model matches, now check firmware. "" matches always. |
201 | if (!( !*knowndrives[i].firmwareregexp | |
202 | || match(knowndrives[i].firmwareregexp, firmware))) | |
203 | continue; | |
832b75ed | 204 | |
2127e193 GI |
205 | // Found |
206 | return &knowndrives[i]; | |
832b75ed GG |
207 | } |
208 | ||
2127e193 GI |
209 | // Not found |
210 | return 0; | |
832b75ed GG |
211 | } |
212 | ||
e9583e0c GI |
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, | |
ee38a438 | 216 | firmwarebug_defs * firmwarebugs, std::string * type) |
2127e193 GI |
217 | { |
218 | for (int i = 0; ; ) { | |
219 | i += strspn(presets+i, " \t"); | |
220 | if (!presets[i]) | |
221 | break; | |
a7e8ffec GI |
222 | char opt, arg[80+1+13]; int len = -1; |
223 | if (!(sscanf(presets+i, "-%c %80[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) | |
2127e193 | 224 | return false; |
e9583e0c | 225 | if (opt == 'v' && defs) { |
6b80b4d2 JD |
226 | // Parse "-v N,format[,name[,HDD|SSD]]" |
227 | if (!parse_attribute_def(arg, *defs, (firmwarebugs ? PRIOR_DATABASE : PRIOR_DEFAULT))) | |
2127e193 | 228 | return false; |
2127e193 | 229 | } |
ee38a438 GI |
230 | else if (opt == 'F' && firmwarebugs) { |
231 | firmwarebug_defs bug; | |
232 | if (!parse_firmwarebug_def(arg, bug)) | |
2127e193 | 233 | return false; |
ee38a438 GI |
234 | // Don't set if user specified '-F none'. |
235 | if (!firmwarebugs->is_set(BUG_NONE)) | |
236 | firmwarebugs->set(bug); | |
e9583e0c GI |
237 | } |
238 | else if (opt == 'd' && type) { | |
239 | // TODO: Check valid types | |
240 | *type = arg; | |
2127e193 GI |
241 | } |
242 | else | |
243 | return false; | |
832b75ed | 244 | |
2127e193 GI |
245 | i += len; |
246 | } | |
247 | return true; | |
248 | } | |
249 | ||
6b80b4d2 JD |
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 | ||
e9583e0c GI |
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, | |
ee38a438 | 260 | firmwarebug_defs & firmwarebugs) |
e9583e0c | 261 | { |
ee38a438 | 262 | return parse_db_presets(presets, &defs, &firmwarebugs, 0); |
e9583e0c GI |
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; | |
e9583e0c GI |
297 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
298 | const drive_settings & dbentry = knowndrives[i]; | |
299 | ||
300 | // Skip drive entries | |
6b80b4d2 | 301 | if (get_dbentry_type(&dbentry) != DBENTRY_USB) |
e9583e0c GI |
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. | |
cfbba5b9 GI |
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 | |
e9583e0c | 324 | info = d; found = 1; |
e9583e0c | 325 | } |
cfbba5b9 GI |
326 | else if (info.usb_type != d.usb_type) { |
327 | // Another possible match with different type | |
e9583e0c GI |
328 | info2 = d; found = 2; |
329 | break; | |
330 | } | |
cfbba5b9 GI |
331 | |
332 | // Stop search at first matching entry with empty bcd_device | |
333 | if (!*dbentry.firmwareregexp) | |
334 | break; | |
e9583e0c GI |
335 | } |
336 | ||
337 | return found; | |
338 | } | |
339 | ||
2127e193 GI |
340 | // Shows one entry of knowndrives[], returns #errors. |
341 | static int showonepreset(const drive_settings * dbentry) | |
342 | { | |
832b75ed | 343 | // Basic error check |
2127e193 GI |
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" | |
832b75ed | 351 | "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); |
2127e193 | 352 | return 1; |
832b75ed | 353 | } |
e9583e0c | 354 | |
6b80b4d2 JD |
355 | dbentry_type type = get_dbentry_type(dbentry); |
356 | bool usb = (type == DBENTRY_USB); | |
e9583e0c | 357 | |
2127e193 GI |
358 | // print and check model and firmware regular expressions |
359 | int errcnt = 0; | |
360 | regular_expression regex; | |
e9583e0c GI |
361 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "MODEL REGEXP:" : "USB Vendor:Product:"), |
362 | dbentry->modelregexp); | |
2127e193 GI |
363 | if (!compile(regex, dbentry->modelregexp)) |
364 | errcnt++; | |
832b75ed | 365 | |
e9583e0c GI |
366 | pout("%-*s %s\n", TABLEPRINTWIDTH, (!usb ? "FIRMWARE REGEXP:" : "USB bcdDevice:"), |
367 | *dbentry->firmwareregexp ? dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) | |
2127e193 GI |
368 | if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) |
369 | errcnt++; | |
370 | ||
e9583e0c GI |
371 | if (!usb) { |
372 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); | |
373 | ||
374 | // if there are any presets, then show them | |
ee38a438 | 375 | firmwarebug_defs firmwarebugs; |
e9583e0c GI |
376 | bool first_preset = true; |
377 | if (*dbentry->presets) { | |
378 | ata_vendor_attr_defs defs; | |
6b80b4d2 JD |
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 | } | |
e9583e0c | 384 | } |
6b80b4d2 JD |
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 | ||
e9583e0c | 392 | for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) { |
6b80b4d2 | 393 | if (defs[i].priority != PRIOR_DEFAULT || !defs[i].name.empty()) { |
d008864d | 394 | std::string name = ata_get_smart_attr_name(i, defs); |
e9583e0c GI |
395 | // Use leading zeros instead of spaces so that everything lines up. |
396 | pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", | |
d008864d GI |
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 | } | |
e9583e0c GI |
404 | first_preset = false; |
405 | } | |
406 | } | |
2127e193 | 407 | } |
e9583e0c GI |
408 | if (first_preset) |
409 | pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); | |
410 | ||
411 | // describe firmwarefix | |
ee38a438 GI |
412 | for (int b = BUG_NOLOGDIR; b <= BUG_XERRORLBA; b++) { |
413 | if (!firmwarebugs.is_set((firmwarebug_t)b)) | |
414 | continue; | |
e9583e0c | 415 | const char * fixdesc; |
ee38a438 GI |
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: | |
e9583e0c GI |
421 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; |
422 | break; | |
ee38a438 | 423 | case BUG_SAMSUNG2: |
e9583e0c GI |
424 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; |
425 | break; | |
ee38a438 | 426 | case BUG_SAMSUNG3: |
e9583e0c GI |
427 | fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; |
428 | break; | |
ee38a438 GI |
429 | case BUG_XERRORLBA: |
430 | fixdesc = "Fixes LBA byte ordering in Ext. Comprehensive SMART error log (same as -F xerrorlba)"; | |
431 | break; | |
e9583e0c GI |
432 | default: |
433 | fixdesc = "UNKNOWN"; errcnt++; | |
434 | break; | |
2127e193 | 435 | } |
e9583e0c | 436 | pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); |
2127e193 | 437 | } |
832b75ed | 438 | } |
e9583e0c GI |
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++; | |
2127e193 | 450 | } |
e9583e0c GI |
451 | pout("%-*s %s\n", TABLEPRINTWIDTH, "USB Type", |
452 | (!info.usb_type.empty() ? info.usb_type.c_str() : "[unsupported]")); | |
832b75ed | 453 | } |
2127e193 | 454 | |
832b75ed | 455 | // Print any special warnings |
2127e193 GI |
456 | if (*dbentry->warningmsg) |
457 | pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); | |
458 | return errcnt; | |
832b75ed GG |
459 | } |
460 | ||
461 | // Shows all presets for drives in knowndrives[]. | |
2127e193 GI |
462 | // Returns #syntax errors. |
463 | int showallpresets() | |
464 | { | |
832b75ed GG |
465 | // loop over all entries in the knowndrives[] table, printing them |
466 | // out in a nice format | |
2127e193 GI |
467 | int errcnt = 0; |
468 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
469 | errcnt += showonepreset(&knowndrives[i]); | |
832b75ed GG |
470 | pout("\n"); |
471 | } | |
472 | ||
2127e193 GI |
473 | pout("Total number of entries :%5u\n" |
474 | "Entries read from file(s):%5u\n\n", | |
475 | knowndrives.size(), knowndrives.custom_size()); | |
476 | ||
832b75ed GG |
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"); | |
2127e193 GI |
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; | |
832b75ed GG |
484 | } |
485 | ||
486 | // Shows all matching presets for a drive in knowndrives[]. | |
487 | // Returns # matching entries. | |
2127e193 GI |
488 | int showmatchingpresets(const char *model, const char *firmware) |
489 | { | |
832b75ed GG |
490 | int cnt = 0; |
491 | const char * firmwaremsg = (firmware ? firmware : "(any)"); | |
832b75ed | 492 | |
2127e193 GI |
493 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
494 | if (!match(knowndrives[i].modelregexp, model)) | |
832b75ed | 495 | continue; |
2127e193 GI |
496 | if ( firmware && *knowndrives[i].firmwareregexp |
497 | && !match(knowndrives[i].firmwareregexp, firmware)) | |
832b75ed | 498 | continue; |
2127e193 | 499 | // Found |
832b75ed GG |
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 | } | |
832b75ed GG |
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. | |
cfbba5b9 | 521 | void show_presets(const ata_identify_device * drive) |
2127e193 | 522 | { |
832b75ed GG |
523 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
524 | ||
525 | // get the drive's model/firmware strings | |
cfbba5b9 GI |
526 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
527 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
528 | ||
832b75ed | 529 | // and search to see if they match values in the table |
2127e193 GI |
530 | const drive_settings * dbentry = lookup_drive(model, firmware); |
531 | if (!dbentry) { | |
832b75ed GG |
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); | |
2127e193 | 548 | showonepreset(dbentry); |
832b75ed GG |
549 | } |
550 | ||
cfbba5b9 | 551 | // Searches drive database and sets preset vendor attribute |
ee38a438 | 552 | // options in defs and firmwarebugs. |
cfbba5b9 GI |
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, | |
ee38a438 | 557 | firmwarebug_defs & firmwarebugs) |
2127e193 | 558 | { |
832b75ed | 559 | // get the drive's model/firmware strings |
2127e193 | 560 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
cfbba5b9 GI |
561 | ata_format_id_string(model, drive->model, sizeof(model)-1); |
562 | ata_format_id_string(firmware, drive->fw_rev, sizeof(firmware)-1); | |
563 | ||
832b75ed | 564 | // Look up the drive in knowndrives[]. |
2127e193 GI |
565 | const drive_settings * dbentry = lookup_drive(model, firmware); |
566 | if (!dbentry) | |
cfbba5b9 | 567 | return 0; |
2127e193 GI |
568 | |
569 | if (*dbentry->presets) { | |
570 | // Apply presets | |
ee38a438 | 571 | if (!parse_presets(dbentry->presets, defs, firmwarebugs)) |
2127e193 GI |
572 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); |
573 | } | |
cfbba5b9 | 574 | return dbentry; |
2127e193 GI |
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) | |
6b80b4d2 | 587 | : m_f(f), m_next(0) { get(); get(); } |
2127e193 GI |
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; | |
832b75ed | 664 | } |
2127e193 GI |
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 | } | |
ff28b140 | 719 | // Lookahead to detect string constant concatenation |
2127e193 GI |
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; | |
832b75ed | 767 | } |
2127e193 GI |
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; | |
ff28b140 | 779 | if (!regex.compile(token.value.c_str())) { |
2127e193 GI |
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()) { | |
6b80b4d2 JD |
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; | |
2127e193 GI |
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); | |
832b75ed | 844 | } |
2127e193 GI |
845 | return ok; |
846 | } | |
847 | ||
848 | // Read drive database from file. | |
849 | bool read_drive_database(const char * path) | |
850 | { | |
a23d5117 GI |
851 | stdio_file f(path, "r" |
852 | #ifdef __CYGWIN__ // Allow files with '\r\n'. | |
853 | "t" | |
854 | #endif | |
855 | ); | |
2127e193 GI |
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 | ||
e9583e0c GI |
864 | // Get path for additional database file |
865 | const char * get_drivedb_path_add() | |
2127e193 GI |
866 | { |
867 | #ifndef _WIN32 | |
e9583e0c GI |
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"; | |
2127e193 | 882 | #else |
e9583e0c GI |
883 | static std::string path = get_exe_dir() + "/drivedb.h"; |
884 | return path.c_str(); | |
885 | #endif | |
886 | } | |
887 | ||
2127e193 | 888 | #endif |
e9583e0c GI |
889 | |
890 | // Read drive databases from standard places. | |
6b80b4d2 | 891 | static bool read_default_drive_databases() |
e9583e0c GI |
892 | { |
893 | // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h | |
894 | const char * db1 = get_drivedb_path_add(); | |
2127e193 GI |
895 | if (!access(db1, 0)) { |
896 | if (!read_drive_database(db1)) | |
897 | return false; | |
898 | } | |
899 | ||
900 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
e9583e0c GI |
901 | // Read file from package: /usr/{,local/}share/smartmontools/drivedb.h |
902 | const char * db2 = get_drivedb_path_default(); | |
2127e193 GI |
903 | if (!access(db2, 0)) { |
904 | if (!read_drive_database(db2)) | |
905 | return false; | |
906 | } | |
907 | else | |
908 | #endif | |
909 | { | |
910 | // Append builtin table. | |
6b80b4d2 JD |
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; | |
2127e193 GI |
949 | } |
950 | ||
951 | return true; | |
832b75ed | 952 | } |
6b80b4d2 JD |
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 | } |