]>
Commit | Line | Data |
---|---|---|
832b75ed | 1 | /* |
4d59bff9 | 2 | * knowndrives.cpp |
832b75ed GG |
3 | * |
4 | * Home page of code is: http://smartmontools.sourceforge.net | |
5 | * Address of support mailing list: smartmontools-support@lists.sourceforge.net | |
6 | * | |
2127e193 GI |
7 | * Copyright (C) 2003-9 Philip Williams, Bruce Allen |
8 | * Copyright (C) 2008-9 Christian Franke <smartmontools-support@lists.sourceforge.net> | |
832b75ed GG |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; either version 2, or (at your option) | |
13 | * any later version. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * (for example COPYING); if not, write to the Free | |
17 | * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
18 | * | |
19 | */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "int64.h" | |
23 | #include <stdio.h> | |
24 | #include "atacmds.h" | |
832b75ed GG |
25 | #include "extern.h" |
26 | #include "knowndrives.h" | |
2127e193 | 27 | #include "utility.h" |
832b75ed | 28 | |
2127e193 GI |
29 | #ifdef HAVE_UNISTD_H |
30 | #include <unistd.h> | |
31 | #endif | |
32 | #ifdef _WIN32 | |
33 | #include <io.h> // access() | |
34 | #endif | |
35 | ||
36 | #include <stdexcept> | |
37 | ||
a23d5117 GI |
38 | const char * knowndrives_cpp_cvsid = "$Id: knowndrives.cpp 3004 2009-12-19 19:39:12Z chrfranke $" |
39 | KNOWNDRIVES_H_CVSID; | |
832b75ed GG |
40 | |
41 | #define MODEL_STRING_LENGTH 40 | |
42 | #define FIRMWARE_STRING_LENGTH 8 | |
43 | #define TABLEPRINTWIDTH 19 | |
44 | ||
832b75ed | 45 | |
a23d5117 GI |
46 | // Builtin table of known drives. |
47 | // Used as a default if not read from | |
48 | // "/usr/{,/local}share/smartmontools/drivedb.h" | |
49 | // or any other file specified by '-B' option, | |
50 | // see read_default_drive_databases() below. | |
51 | // The drive_settings structure is described in drivedb.h. | |
52 | const drive_settings builtin_knowndrives[] = { | |
53 | #include "drivedb.h" | |
832b75ed GG |
54 | }; |
55 | ||
2127e193 GI |
56 | |
57 | /// Drive database class. Stores custom entries read from file. | |
58 | /// Provides transparent access to concatenation of custom and | |
59 | /// default table. | |
60 | class drive_database | |
61 | { | |
62 | public: | |
63 | drive_database(); | |
64 | ||
65 | ~drive_database(); | |
66 | ||
67 | /// Get total number of entries. | |
68 | unsigned size() const | |
69 | { return m_custom_tab.size() + m_builtin_size; } | |
70 | ||
71 | /// Get number of custom entries. | |
72 | unsigned custom_size() const | |
73 | { return m_custom_tab.size(); } | |
74 | ||
75 | /// Array access. | |
76 | const drive_settings & operator[](unsigned i); | |
77 | ||
78 | /// Append new custom entry. | |
79 | void push_back(const drive_settings & src); | |
80 | ||
81 | /// Append builtin table. | |
82 | void append(const drive_settings * builtin_tab, unsigned builtin_size) | |
83 | { m_builtin_tab = builtin_tab; m_builtin_size = builtin_size; } | |
84 | ||
85 | private: | |
86 | const drive_settings * m_builtin_tab; | |
87 | unsigned m_builtin_size; | |
88 | ||
89 | std::vector<drive_settings> m_custom_tab; | |
90 | std::vector<char *> m_custom_strings; | |
91 | ||
92 | const char * copy_string(const char * str); | |
93 | ||
94 | drive_database(const drive_database &); | |
95 | void operator=(const drive_database &); | |
96 | }; | |
97 | ||
98 | drive_database::drive_database() | |
99 | : m_builtin_tab(0), m_builtin_size(0) | |
100 | { | |
101 | } | |
102 | ||
103 | drive_database::~drive_database() | |
104 | { | |
105 | for (unsigned i = 0; i < m_custom_strings.size(); i++) | |
106 | delete [] m_custom_strings[i]; | |
107 | } | |
108 | ||
109 | const drive_settings & drive_database::operator[](unsigned i) | |
110 | { | |
111 | return (i < m_custom_tab.size() ? m_custom_tab[i] | |
112 | : m_builtin_tab[i - m_custom_tab.size()] ); | |
113 | } | |
114 | ||
115 | void drive_database::push_back(const drive_settings & src) | |
116 | { | |
117 | drive_settings dest; | |
118 | dest.modelfamily = copy_string(src.modelfamily); | |
119 | dest.modelregexp = copy_string(src.modelregexp); | |
120 | dest.firmwareregexp = copy_string(src.firmwareregexp); | |
121 | dest.warningmsg = copy_string(src.warningmsg); | |
122 | dest.presets = copy_string(src.presets); | |
123 | m_custom_tab.push_back(dest); | |
124 | } | |
125 | ||
126 | const char * drive_database::copy_string(const char * src) | |
127 | { | |
128 | char * dest = new char[strlen(src)+1]; | |
129 | try { | |
130 | m_custom_strings.push_back(dest); | |
131 | } | |
132 | catch (...) { | |
133 | delete [] dest; throw; | |
134 | } | |
135 | return strcpy(dest, src); | |
136 | } | |
137 | ||
138 | ||
139 | /// The drive database. | |
140 | static drive_database knowndrives; | |
141 | ||
142 | ||
143 | // Compile regular expression, print message on failure. | |
144 | static bool compile(regular_expression & regex, const char *pattern) | |
145 | { | |
146 | if (!regex.compile(pattern, REG_EXTENDED)) { | |
147 | pout("Internal error: unable to compile regular expression \"%s\": %s\n" | |
148 | "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", | |
149 | pattern, regex.get_errmsg()); | |
150 | return false; | |
151 | } | |
152 | return true; | |
153 | } | |
154 | ||
155 | // Compile & match a regular expression. | |
156 | static bool match(const char * pattern, const char * str) | |
157 | { | |
158 | regular_expression regex; | |
159 | if (!compile(regex, pattern)) | |
160 | return false; | |
161 | return regex.full_match(str); | |
162 | } | |
163 | ||
832b75ed GG |
164 | // Searches knowndrives[] for a drive with the given model number and firmware |
165 | // string. If either the drive's model or firmware strings are not set by the | |
2127e193 GI |
166 | // manufacturer then values of NULL may be used. Returns the entry of the |
167 | // first match in knowndrives[] or 0 if no match if found. | |
168 | const drive_settings * lookup_drive(const char * model, const char * firmware) | |
832b75ed | 169 | { |
2127e193 GI |
170 | if (!model) |
171 | model = ""; | |
172 | if (!firmware) | |
173 | firmware = ""; | |
832b75ed | 174 | |
2127e193 GI |
175 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
176 | // Check whether model matches the regular expression in knowndrives[i]. | |
177 | if (!match(knowndrives[i].modelregexp, model)) | |
178 | continue; | |
832b75ed | 179 | |
2127e193 GI |
180 | // Model matches, now check firmware. "" matches always. |
181 | if (!( !*knowndrives[i].firmwareregexp | |
182 | || match(knowndrives[i].firmwareregexp, firmware))) | |
183 | continue; | |
832b75ed | 184 | |
2127e193 GI |
185 | // Found |
186 | return &knowndrives[i]; | |
832b75ed GG |
187 | } |
188 | ||
2127e193 GI |
189 | // Not found |
190 | return 0; | |
832b75ed GG |
191 | } |
192 | ||
2127e193 | 193 | // Parse '-v' and '-F' options in preset string, return false on error. |
bed94269 GI |
194 | static bool parse_presets(const char * presets, ata_vendor_attr_defs & defs, |
195 | unsigned char & fix_firmwarebug) | |
2127e193 GI |
196 | { |
197 | for (int i = 0; ; ) { | |
198 | i += strspn(presets+i, " \t"); | |
199 | if (!presets[i]) | |
200 | break; | |
201 | char opt, arg[40+1+13]; int len = -1; | |
202 | if (!(sscanf(presets+i, "-%c %40[^ ]%n", &opt, arg, &len) >= 2 && len > 0)) | |
203 | return false; | |
204 | if (opt == 'v') { | |
bed94269 GI |
205 | // Parse "-v N,format[,name]" |
206 | if (!parse_attribute_def(arg, defs, PRIOR_DATABASE)) | |
2127e193 | 207 | return false; |
2127e193 GI |
208 | } |
209 | else if (opt == 'F') { | |
210 | unsigned char fix; | |
211 | if (!strcmp(arg, "samsung")) | |
212 | fix = FIX_SAMSUNG; | |
213 | else if (!strcmp(arg, "samsung2")) | |
214 | fix = FIX_SAMSUNG2; | |
215 | else if (!strcmp(arg, "samsung3")) | |
216 | fix = FIX_SAMSUNG3; | |
217 | else | |
218 | return false; | |
219 | // Set only if not set by user | |
220 | if (fix_firmwarebug == FIX_NOTSPECIFIED) | |
221 | fix_firmwarebug = fix; | |
222 | } | |
223 | else | |
224 | return false; | |
832b75ed | 225 | |
2127e193 GI |
226 | i += len; |
227 | } | |
228 | return true; | |
229 | } | |
230 | ||
231 | // Shows one entry of knowndrives[], returns #errors. | |
232 | static int showonepreset(const drive_settings * dbentry) | |
233 | { | |
832b75ed | 234 | // Basic error check |
2127e193 GI |
235 | if (!( dbentry |
236 | && dbentry->modelfamily | |
237 | && dbentry->modelregexp && *dbentry->modelregexp | |
238 | && dbentry->firmwareregexp | |
239 | && dbentry->warningmsg | |
240 | && dbentry->presets )) { | |
241 | pout("Invalid drive database entry. Please report\n" | |
832b75ed | 242 | "this error to smartmontools developers at " PACKAGE_BUGREPORT ".\n"); |
2127e193 | 243 | return 1; |
832b75ed GG |
244 | } |
245 | ||
2127e193 GI |
246 | // print and check model and firmware regular expressions |
247 | int errcnt = 0; | |
248 | regular_expression regex; | |
249 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL REGEXP:", dbentry->modelregexp); | |
250 | if (!compile(regex, dbentry->modelregexp)) | |
251 | errcnt++; | |
832b75ed | 252 | |
2127e193 GI |
253 | pout("%-*s %s\n", TABLEPRINTWIDTH, "FIRMWARE REGEXP:", *dbentry->firmwareregexp ? |
254 | dbentry->firmwareregexp : ".*"); // preserve old output (TODO: Change) | |
255 | if (*dbentry->firmwareregexp && !compile(regex, dbentry->firmwareregexp)) | |
256 | errcnt++; | |
257 | ||
258 | pout("%-*s %s\n", TABLEPRINTWIDTH, "MODEL FAMILY:", dbentry->modelfamily); | |
259 | ||
260 | // if there are any presets, then show them | |
261 | unsigned char fix_firmwarebug = 0; | |
262 | bool first_preset = true; | |
263 | if (*dbentry->presets) { | |
bed94269 GI |
264 | ata_vendor_attr_defs defs; |
265 | if (!parse_presets(dbentry->presets, defs, fix_firmwarebug)) { | |
2127e193 GI |
266 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); |
267 | errcnt++; | |
268 | } | |
269 | for (int i = 0; i < MAX_ATTRIBUTE_NUM; i++) { | |
bed94269 | 270 | if (defs[i].priority != PRIOR_DEFAULT) { |
2127e193 | 271 | // Use leading zeros instead of spaces so that everything lines up. |
bed94269 GI |
272 | pout("%-*s %03d %s\n", TABLEPRINTWIDTH, first_preset ? "ATTRIBUTE OPTIONS:" : "", |
273 | i, ata_get_smart_attr_name(i, defs).c_str()); | |
2127e193 GI |
274 | first_preset = false; |
275 | } | |
276 | } | |
832b75ed | 277 | } |
2127e193 | 278 | if (first_preset) |
832b75ed GG |
279 | pout("%-*s %s\n", TABLEPRINTWIDTH, "ATTRIBUTE OPTIONS:", "None preset; no -v options are required."); |
280 | ||
2127e193 GI |
281 | // describe firmwarefix |
282 | if (fix_firmwarebug) { | |
283 | const char * fixdesc; | |
284 | switch (fix_firmwarebug) { | |
285 | case FIX_SAMSUNG: | |
286 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung)"; | |
287 | break; | |
288 | case FIX_SAMSUNG2: | |
289 | fixdesc = "Fixes byte order in some SMART data (same as -F samsung2)"; | |
290 | break; | |
291 | case FIX_SAMSUNG3: | |
292 | fixdesc = "Fixes completed self-test reported as in progress (same as -F samsung3)"; | |
293 | break; | |
294 | default: | |
295 | fixdesc = "UNKNOWN"; errcnt++; | |
296 | break; | |
297 | } | |
298 | pout("%-*s %s\n", TABLEPRINTWIDTH, "OTHER PRESETS:", fixdesc); | |
832b75ed | 299 | } |
2127e193 | 300 | |
832b75ed | 301 | // Print any special warnings |
2127e193 GI |
302 | if (*dbentry->warningmsg) |
303 | pout("%-*s %s\n", TABLEPRINTWIDTH, "WARNINGS:", dbentry->warningmsg); | |
304 | return errcnt; | |
832b75ed GG |
305 | } |
306 | ||
307 | // Shows all presets for drives in knowndrives[]. | |
2127e193 GI |
308 | // Returns #syntax errors. |
309 | int showallpresets() | |
310 | { | |
832b75ed GG |
311 | // loop over all entries in the knowndrives[] table, printing them |
312 | // out in a nice format | |
2127e193 GI |
313 | int errcnt = 0; |
314 | for (unsigned i = 0; i < knowndrives.size(); i++) { | |
315 | errcnt += showonepreset(&knowndrives[i]); | |
832b75ed GG |
316 | pout("\n"); |
317 | } | |
318 | ||
2127e193 GI |
319 | pout("Total number of entries :%5u\n" |
320 | "Entries read from file(s):%5u\n\n", | |
321 | knowndrives.size(), knowndrives.custom_size()); | |
322 | ||
832b75ed GG |
323 | pout("For information about adding a drive to the database see the FAQ on the\n"); |
324 | pout("smartmontools home page: " PACKAGE_HOMEPAGE "\n"); | |
2127e193 GI |
325 | |
326 | if (errcnt > 0) | |
327 | pout("\nFound %d syntax error(s) in database.\n" | |
328 | "Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n", errcnt); | |
329 | return errcnt; | |
832b75ed GG |
330 | } |
331 | ||
332 | // Shows all matching presets for a drive in knowndrives[]. | |
333 | // Returns # matching entries. | |
2127e193 GI |
334 | int showmatchingpresets(const char *model, const char *firmware) |
335 | { | |
832b75ed GG |
336 | int cnt = 0; |
337 | const char * firmwaremsg = (firmware ? firmware : "(any)"); | |
832b75ed | 338 | |
2127e193 GI |
339 | for (unsigned i = 0; i < knowndrives.size(); i++) { |
340 | if (!match(knowndrives[i].modelregexp, model)) | |
832b75ed | 341 | continue; |
2127e193 GI |
342 | if ( firmware && *knowndrives[i].firmwareregexp |
343 | && !match(knowndrives[i].firmwareregexp, firmware)) | |
832b75ed | 344 | continue; |
2127e193 | 345 | // Found |
832b75ed GG |
346 | if (++cnt == 1) |
347 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
348 | "%-*s %s\n" | |
349 | "%-*s %s\n" | |
350 | "match smartmontools Drive Database entry:\n", | |
351 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmwaremsg); | |
352 | else if (cnt == 2) | |
353 | pout("and match these additional entries:\n"); | |
354 | showonepreset(&knowndrives[i]); | |
355 | pout("\n"); | |
356 | } | |
832b75ed GG |
357 | if (cnt == 0) |
358 | pout("No presets are defined for this drive. Its identity strings:\n" | |
359 | "MODEL: %s\n" | |
360 | "FIRMWARE: %s\n" | |
361 | "do not match any of the known regular expressions.\n", | |
362 | model, firmwaremsg); | |
363 | return cnt; | |
364 | } | |
365 | ||
366 | // Shows the presets (if any) that are available for the given drive. | |
2127e193 GI |
367 | void show_presets(const ata_identify_device * drive, bool fix_swapped_id) |
368 | { | |
832b75ed GG |
369 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
370 | ||
371 | // get the drive's model/firmware strings | |
2127e193 GI |
372 | format_ata_string(model, drive->model, MODEL_STRING_LENGTH, fix_swapped_id); |
373 | format_ata_string(firmware, drive->fw_rev, FIRMWARE_STRING_LENGTH, fix_swapped_id); | |
832b75ed GG |
374 | |
375 | // and search to see if they match values in the table | |
2127e193 GI |
376 | const drive_settings * dbentry = lookup_drive(model, firmware); |
377 | if (!dbentry) { | |
832b75ed GG |
378 | // no matches found |
379 | pout("No presets are defined for this drive. Its identity strings:\n" | |
380 | "MODEL: %s\n" | |
381 | "FIRMWARE: %s\n" | |
382 | "do not match any of the known regular expressions.\n" | |
383 | "Use -P showall to list all known regular expressions.\n", | |
384 | model, firmware); | |
385 | return; | |
386 | } | |
387 | ||
388 | // We found a matching drive. Print out all information about it. | |
389 | pout("Drive found in smartmontools Database. Drive identity strings:\n" | |
390 | "%-*s %s\n" | |
391 | "%-*s %s\n" | |
392 | "match smartmontools Drive Database entry:\n", | |
393 | TABLEPRINTWIDTH, "MODEL:", model, TABLEPRINTWIDTH, "FIRMWARE:", firmware); | |
2127e193 | 394 | showonepreset(dbentry); |
832b75ed GG |
395 | } |
396 | ||
397 | // Sets preset vendor attribute options in opts by finding the entry | |
398 | // (if any) for the given drive in knowndrives[]. Values that have | |
2127e193 GI |
399 | // already been set in opts will not be changed. Returns false if drive |
400 | // not recognized. | |
bed94269 | 401 | bool apply_presets(const ata_identify_device *drive, ata_vendor_attr_defs & defs, |
2127e193 GI |
402 | unsigned char & fix_firmwarebug, bool fix_swapped_id) |
403 | { | |
832b75ed | 404 | // get the drive's model/firmware strings |
2127e193 GI |
405 | char model[MODEL_STRING_LENGTH+1], firmware[FIRMWARE_STRING_LENGTH+1]; |
406 | format_ata_string(model, drive->model, MODEL_STRING_LENGTH, fix_swapped_id); | |
407 | format_ata_string(firmware, drive->fw_rev, FIRMWARE_STRING_LENGTH, fix_swapped_id); | |
832b75ed GG |
408 | |
409 | // Look up the drive in knowndrives[]. | |
2127e193 GI |
410 | const drive_settings * dbentry = lookup_drive(model, firmware); |
411 | if (!dbentry) | |
412 | return false; | |
413 | ||
414 | if (*dbentry->presets) { | |
415 | // Apply presets | |
bed94269 | 416 | if (!parse_presets(dbentry->presets, defs, fix_firmwarebug)) |
2127e193 GI |
417 | pout("Syntax error in preset option string \"%s\"\n", dbentry->presets); |
418 | } | |
419 | return true; | |
420 | } | |
421 | ||
422 | ||
423 | ///////////////////////////////////////////////////////////////////////////// | |
424 | // Parser for drive database files | |
425 | ||
426 | // Abstract pointer to read file input. | |
427 | // Operations supported: c = *p; c = p[1]; ++p; | |
428 | class stdin_iterator | |
429 | { | |
430 | public: | |
431 | explicit stdin_iterator(FILE * f) | |
432 | : m_f(f) { get(); get(); } | |
433 | ||
434 | stdin_iterator & operator++() | |
435 | { get(); return *this; } | |
436 | ||
437 | char operator*() const | |
438 | { return m_c; } | |
439 | ||
440 | char operator[](int i) const | |
441 | { | |
442 | if (i != 1) | |
443 | fail(); | |
444 | return m_next; | |
445 | } | |
446 | ||
447 | private: | |
448 | FILE * m_f; | |
449 | char m_c, m_next; | |
450 | void get(); | |
451 | void fail() const; | |
452 | }; | |
453 | ||
454 | void stdin_iterator::get() | |
455 | { | |
456 | m_c = m_next; | |
457 | int ch = getc(m_f); | |
458 | m_next = (ch != EOF ? ch : 0); | |
459 | } | |
460 | ||
461 | void stdin_iterator::fail() const | |
462 | { | |
463 | throw std::runtime_error("stdin_iterator: wrong usage"); | |
464 | } | |
465 | ||
466 | ||
467 | // Use above as parser input 'pointer'. Can easily be changed later | |
468 | // to e.g. 'const char *' if above is too slow. | |
469 | typedef stdin_iterator parse_ptr; | |
470 | ||
471 | // Skip whitespace and comments. | |
472 | static parse_ptr skip_white(parse_ptr src, const char * path, int & line) | |
473 | { | |
474 | for ( ; ; ++src) switch (*src) { | |
475 | case ' ': case '\t': | |
476 | continue; | |
477 | ||
478 | case '\n': | |
479 | ++line; | |
480 | continue; | |
481 | ||
482 | case '/': | |
483 | switch (src[1]) { | |
484 | case '/': | |
485 | // skip '// comment' | |
486 | ++src; ++src; | |
487 | while (*src && *src != '\n') | |
488 | ++src; | |
489 | if (*src) | |
490 | ++line; | |
491 | break; | |
492 | case '*': | |
493 | // skip '/* comment */' | |
494 | ++src; ++src; | |
495 | for (;;) { | |
496 | if (!*src) { | |
497 | pout("%s(%d): Missing '*/'\n", path, line); | |
498 | return src; | |
499 | } | |
500 | char c = *src; ++src; | |
501 | if (c == '\n') | |
502 | ++line; | |
503 | else if (c == '*' && *src == '/') | |
504 | break; | |
505 | } | |
506 | break; | |
507 | default: | |
508 | return src; | |
832b75ed | 509 | } |
2127e193 GI |
510 | continue; |
511 | ||
512 | default: | |
513 | return src; | |
514 | } | |
515 | } | |
516 | ||
517 | // Info about a token. | |
518 | struct token_info | |
519 | { | |
520 | char type; | |
521 | int line; | |
522 | std::string value; | |
523 | ||
524 | token_info() : type(0), line(0) { } | |
525 | }; | |
526 | ||
527 | // Get next token. | |
528 | static parse_ptr get_token(parse_ptr src, token_info & token, const char * path, int & line) | |
529 | { | |
530 | src = skip_white(src, path, line); | |
531 | switch (*src) { | |
532 | case '{': case '}': case ',': | |
533 | // Simple token | |
534 | token.type = *src; token.line = line; | |
535 | ++src; | |
536 | break; | |
537 | ||
538 | case '"': | |
539 | // String constant | |
540 | token.type = '"'; token.line = line; | |
541 | token.value = ""; | |
542 | do { | |
543 | for (++src; *src != '"'; ++src) { | |
544 | char c = *src; | |
545 | if (!c || c == '\n' || (c == '\\' && !src[1])) { | |
546 | pout("%s(%d): Missing terminating '\"'\n", path, line); | |
547 | token.type = '?'; token.line = line; | |
548 | return src; | |
549 | } | |
550 | if (c == '\\') { | |
551 | c = *++src; | |
552 | switch (c) { | |
553 | case 'n' : c = '\n'; break; | |
554 | case '\n': ++line; break; | |
555 | case '\\': case '"': break; | |
556 | default: | |
557 | pout("%s(%d): Unknown escape sequence '\\%c'\n", path, line, c); | |
558 | token.type = '?'; token.line = line; | |
559 | continue; | |
560 | } | |
561 | } | |
562 | token.value += c; | |
563 | } | |
564 | // Lookahead to detect string constant concatentation | |
565 | src = skip_white(++src, path, line); | |
566 | } while (*src == '"'); | |
567 | break; | |
568 | ||
569 | case 0: | |
570 | // EOF | |
571 | token.type = 0; token.line = line; | |
572 | break; | |
573 | ||
574 | default: | |
575 | pout("%s(%d): Syntax error, invalid char '%c'\n", path, line, *src); | |
576 | token.type = '?'; token.line = line; | |
577 | while (*src && *src != '\n') | |
578 | ++src; | |
579 | break; | |
580 | } | |
581 | ||
582 | return src; | |
583 | } | |
584 | ||
585 | // Parse drive database from abstract input pointer. | |
586 | static bool parse_drive_database(parse_ptr src, drive_database & db, const char * path) | |
587 | { | |
588 | int state = 0, field = 0; | |
589 | std::string values[5]; | |
590 | bool ok = true; | |
591 | ||
592 | token_info token; int line = 1; | |
593 | src = get_token(src, token, path, line); | |
594 | for (;;) { | |
595 | // EOF is ok after '}', trailing ',' is also allowed. | |
596 | if (!token.type && (state == 0 || state == 4)) | |
597 | break; | |
598 | ||
599 | // Check expected token | |
600 | const char expect[] = "{\",},"; | |
601 | if (token.type != expect[state]) { | |
602 | if (token.type != '?') | |
603 | pout("%s(%d): Syntax error, '%c' expected\n", path, token.line, expect[state]); | |
604 | ok = false; | |
605 | // Skip to next entry | |
606 | while (token.type && token.type != '{') | |
607 | src = get_token(src, token, path, line); | |
608 | state = 0; | |
609 | if (token.type) | |
610 | continue; | |
611 | break; | |
832b75ed | 612 | } |
2127e193 GI |
613 | |
614 | // Interpret parser state | |
615 | switch (state) { | |
616 | case 0: // ... ^{...} | |
617 | state = 1; field = 0; | |
618 | break; | |
619 | case 1: // {... ^"..." ...} | |
620 | switch (field) { | |
621 | case 1: case 2: | |
622 | if (!token.value.empty()) { | |
623 | regular_expression regex; | |
624 | if (!regex.compile(token.value.c_str(), REG_EXTENDED)) { | |
625 | pout("%s(%d): Error in regular expression: %s\n", path, token.line, regex.get_errmsg()); | |
626 | ok = false; | |
627 | } | |
628 | } | |
629 | else if (field == 1) { | |
630 | pout("%s(%d): Missing regular expression for drive model\n", path, token.line); | |
631 | ok = false; | |
632 | } | |
633 | break; | |
634 | case 4: | |
635 | if (!token.value.empty()) { | |
bed94269 GI |
636 | ata_vendor_attr_defs defs; unsigned char fix = 0; |
637 | if (!parse_presets(token.value.c_str(), defs, fix)) { | |
2127e193 GI |
638 | pout("%s(%d): Syntax error in preset option string\n", path, token.line); |
639 | ok = false; | |
640 | } | |
641 | } | |
642 | break; | |
643 | } | |
644 | values[field] = token.value; | |
645 | state = (++field < 5 ? 2 : 3); | |
646 | break; | |
647 | case 2: // {... "..."^, ...} | |
648 | state = 1; | |
649 | break; | |
650 | case 3: // {...^}, ... | |
651 | { | |
652 | drive_settings entry; | |
653 | entry.modelfamily = values[0].c_str(); | |
654 | entry.modelregexp = values[1].c_str(); | |
655 | entry.firmwareregexp = values[2].c_str(); | |
656 | entry.warningmsg = values[3].c_str(); | |
657 | entry.presets = values[4].c_str(); | |
658 | db.push_back(entry); | |
659 | } | |
660 | state = 4; | |
661 | break; | |
662 | case 4: // {...}^, ... | |
663 | state = 0; | |
664 | break; | |
665 | default: | |
666 | pout("Bad state %d\n", state); | |
667 | return false; | |
668 | } | |
669 | src = get_token(src, token, path, line); | |
832b75ed | 670 | } |
2127e193 GI |
671 | return ok; |
672 | } | |
673 | ||
674 | // Read drive database from file. | |
675 | bool read_drive_database(const char * path) | |
676 | { | |
a23d5117 GI |
677 | stdio_file f(path, "r" |
678 | #ifdef __CYGWIN__ // Allow files with '\r\n'. | |
679 | "t" | |
680 | #endif | |
681 | ); | |
2127e193 GI |
682 | if (!f) { |
683 | pout("%s: cannot open drive database file\n", path); | |
684 | return false; | |
685 | } | |
686 | ||
687 | return parse_drive_database(parse_ptr(f), knowndrives, path); | |
688 | } | |
689 | ||
690 | // Read drive databases from standard places. | |
691 | bool read_default_drive_databases() | |
692 | { | |
693 | #ifndef _WIN32 | |
694 | // Read file for local additions: /{,usr/local/}etc/smart_drivedb.h | |
695 | static const char db1[] = SMARTMONTOOLS_SYSCONFDIR"/smart_drivedb.h"; | |
696 | #else | |
697 | static const char db1[] = "./smart_drivedb.h"; | |
698 | #endif | |
699 | if (!access(db1, 0)) { | |
700 | if (!read_drive_database(db1)) | |
701 | return false; | |
702 | } | |
703 | ||
704 | #ifdef SMARTMONTOOLS_DRIVEDBDIR | |
705 | // Read file from package: // /usr/{,local/}share/smartmontools/drivedb.h | |
706 | static const char db2[] = SMARTMONTOOLS_DRIVEDBDIR"/drivedb.h"; | |
707 | if (!access(db2, 0)) { | |
708 | if (!read_drive_database(db2)) | |
709 | return false; | |
710 | } | |
711 | else | |
712 | #endif | |
713 | { | |
714 | // Append builtin table. | |
715 | knowndrives.append(builtin_knowndrives, | |
716 | sizeof(builtin_knowndrives)/sizeof(builtin_knowndrives[0])); | |
717 | } | |
718 | ||
719 | return true; | |
832b75ed | 720 | } |