]> git.proxmox.com Git - mirror_smartmontools-debian.git/blame - knowndrives.cpp
import smartmontools 7.0
[mirror_smartmontools-debian.git] / knowndrives.cpp
CommitLineData
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 28const 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.
42const drive_settings builtin_knowndrives[] = {
43#include "drivedb.h"
832b75ed
GG
44};
45
6b80b4d2
JD
46const 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.
52class drive_database
53{
54public:
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
77private:
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
90drive_database::drive_database()
91: m_builtin_tab(0), m_builtin_size(0)
92{
93}
94
95drive_database::~drive_database()
96{
97 for (unsigned i = 0; i < m_custom_strings.size(); i++)
98 delete [] m_custom_strings[i];
99}
100
101const 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
107void 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
118const 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.
134static drive_database knowndrives;
135
136
6b80b4d2
JD
137enum dbentry_type {
138 DBENTRY_ATA_DEFAULT,
139 DBENTRY_ATA,
140 DBENTRY_USB
141};
142
143// Return type of entry
144static 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 154static 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.
160static 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.
172static 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 184static 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.
215static 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.
251static 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.
258static 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.
266static 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
272static 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.
285int 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.
341static 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.
463int 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
488int 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 521void 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
555const 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;
583class stdin_iterator
584{
585public:
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
602private:
603 FILE * m_f;
604 char m_c, m_next;
605 void get();
606 void fail() const;
607};
608
609void stdin_iterator::get()
610{
611 m_c = m_next;
612 int ch = getc(m_f);
613 m_next = (ch != EOF ? ch : 0);
614}
615
616void 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.
624typedef stdin_iterator parse_ptr;
625
626// Skip whitespace and comments.
627static 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.
673struct 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.
683static 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.
741static 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.
849bool 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
865const 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
878const 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 891static 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
917static ata_vendor_attr_defs default_attr_defs;
918
919// Initialize default_attr_defs.
920static 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.
955bool 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.
964const ata_vendor_attr_defs & get_default_attr_defs()
965{
966 return default_attr_defs;
967}