]> git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/options/options_parser.cc
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / rocksdb / options / options_parser.cc
1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5
6 #ifndef ROCKSDB_LITE
7
8 #include "options/options_parser.h"
9
10 #include <cmath>
11 #include <map>
12 #include <string>
13 #include <utility>
14 #include <vector>
15
16 #include "options/options_helper.h"
17 #include "rocksdb/convenience.h"
18 #include "rocksdb/db.h"
19 #include "util/cast_util.h"
20 #include "util/file_reader_writer.h"
21 #include "util/string_util.h"
22 #include "util/sync_point.h"
23
24 #include "port/port.h"
25
26 namespace rocksdb {
27
28 static const std::string option_file_header =
29 "# This is a RocksDB option file.\n"
30 "#\n"
31 "# For detailed file format spec, please refer to the example file\n"
32 "# in examples/rocksdb_option_file_example.ini\n"
33 "#\n"
34 "\n";
35
36 Status PersistRocksDBOptions(const DBOptions& db_opt,
37 const std::vector<std::string>& cf_names,
38 const std::vector<ColumnFamilyOptions>& cf_opts,
39 const std::string& file_name, Env* env) {
40 TEST_SYNC_POINT("PersistRocksDBOptions:start");
41 if (cf_names.size() != cf_opts.size()) {
42 return Status::InvalidArgument(
43 "cf_names.size() and cf_opts.size() must be the same");
44 }
45 std::unique_ptr<WritableFile> wf;
46
47 Status s = env->NewWritableFile(file_name, &wf, EnvOptions());
48 if (!s.ok()) {
49 return s;
50 }
51 std::unique_ptr<WritableFileWriter> writable;
52 writable.reset(new WritableFileWriter(std::move(wf), file_name, EnvOptions(),
53 nullptr /* statistics */));
54
55 std::string options_file_content;
56
57 writable->Append(option_file_header + "[" +
58 opt_section_titles[kOptionSectionVersion] +
59 "]\n"
60 " rocksdb_version=" +
61 ToString(ROCKSDB_MAJOR) + "." + ToString(ROCKSDB_MINOR) +
62 "." + ToString(ROCKSDB_PATCH) + "\n");
63 writable->Append(" options_file_version=" +
64 ToString(ROCKSDB_OPTION_FILE_MAJOR) + "." +
65 ToString(ROCKSDB_OPTION_FILE_MINOR) + "\n");
66 writable->Append("\n[" + opt_section_titles[kOptionSectionDBOptions] +
67 "]\n ");
68
69 s = GetStringFromDBOptions(&options_file_content, db_opt, "\n ");
70 if (!s.ok()) {
71 writable->Close();
72 return s;
73 }
74 writable->Append(options_file_content + "\n");
75
76 for (size_t i = 0; i < cf_opts.size(); ++i) {
77 // CFOptions section
78 writable->Append("\n[" + opt_section_titles[kOptionSectionCFOptions] +
79 " \"" + EscapeOptionString(cf_names[i]) + "\"]\n ");
80 s = GetStringFromColumnFamilyOptions(&options_file_content, cf_opts[i],
81 "\n ");
82 if (!s.ok()) {
83 writable->Close();
84 return s;
85 }
86 writable->Append(options_file_content + "\n");
87 // TableOptions section
88 auto* tf = cf_opts[i].table_factory.get();
89 if (tf != nullptr) {
90 writable->Append("[" + opt_section_titles[kOptionSectionTableOptions] +
91 tf->Name() + " \"" + EscapeOptionString(cf_names[i]) +
92 "\"]\n ");
93 options_file_content.clear();
94 s = tf->GetOptionString(&options_file_content, "\n ");
95 if (!s.ok()) {
96 return s;
97 }
98 writable->Append(options_file_content + "\n");
99 }
100 }
101 writable->Sync(true /* use_fsync */);
102 writable->Close();
103
104 return RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
105 db_opt, cf_names, cf_opts, file_name, env);
106 }
107
108 RocksDBOptionsParser::RocksDBOptionsParser() { Reset(); }
109
110 void RocksDBOptionsParser::Reset() {
111 db_opt_ = DBOptions();
112 db_opt_map_.clear();
113 cf_names_.clear();
114 cf_opts_.clear();
115 cf_opt_maps_.clear();
116 has_version_section_ = false;
117 has_db_options_ = false;
118 has_default_cf_options_ = false;
119 for (int i = 0; i < 3; ++i) {
120 db_version[i] = 0;
121 opt_file_version[i] = 0;
122 }
123 }
124
125 bool RocksDBOptionsParser::IsSection(const std::string& line) {
126 if (line.size() < 2) {
127 return false;
128 }
129 if (line[0] != '[' || line[line.size() - 1] != ']') {
130 return false;
131 }
132 return true;
133 }
134
135 Status RocksDBOptionsParser::ParseSection(OptionSection* section,
136 std::string* title,
137 std::string* argument,
138 const std::string& line,
139 const int line_num) {
140 *section = kOptionSectionUnknown;
141 // A section is of the form [<SectionName> "<SectionArg>"], where
142 // "<SectionArg>" is optional.
143 size_t arg_start_pos = line.find("\"");
144 size_t arg_end_pos = line.rfind("\"");
145 // The following if-then check tries to identify whether the input
146 // section has the optional section argument.
147 if (arg_start_pos != std::string::npos && arg_start_pos != arg_end_pos) {
148 *title = TrimAndRemoveComment(line.substr(1, arg_start_pos - 1), true);
149 *argument = UnescapeOptionString(
150 line.substr(arg_start_pos + 1, arg_end_pos - arg_start_pos - 1));
151 } else {
152 *title = TrimAndRemoveComment(line.substr(1, line.size() - 2), true);
153 *argument = "";
154 }
155 for (int i = 0; i < kOptionSectionUnknown; ++i) {
156 if (title->find(opt_section_titles[i]) == 0) {
157 if (i == kOptionSectionVersion || i == kOptionSectionDBOptions ||
158 i == kOptionSectionCFOptions) {
159 if (title->size() == opt_section_titles[i].size()) {
160 // if true, then it indicats equal
161 *section = static_cast<OptionSection>(i);
162 return CheckSection(*section, *argument, line_num);
163 }
164 } else if (i == kOptionSectionTableOptions) {
165 // This type of sections has a sufffix at the end of the
166 // section title
167 if (title->size() > opt_section_titles[i].size()) {
168 *section = static_cast<OptionSection>(i);
169 return CheckSection(*section, *argument, line_num);
170 }
171 }
172 }
173 }
174 return Status::InvalidArgument(std::string("Unknown section ") + line);
175 }
176
177 Status RocksDBOptionsParser::InvalidArgument(const int line_num,
178 const std::string& message) {
179 return Status::InvalidArgument(
180 "[RocksDBOptionsParser Error] ",
181 message + " (at line " + ToString(line_num) + ")");
182 }
183
184 Status RocksDBOptionsParser::ParseStatement(std::string* name,
185 std::string* value,
186 const std::string& line,
187 const int line_num) {
188 size_t eq_pos = line.find("=");
189 if (eq_pos == std::string::npos) {
190 return InvalidArgument(line_num, "A valid statement must have a '='.");
191 }
192
193 *name = TrimAndRemoveComment(line.substr(0, eq_pos), true);
194 *value =
195 TrimAndRemoveComment(line.substr(eq_pos + 1, line.size() - eq_pos - 1));
196 if (name->empty()) {
197 return InvalidArgument(line_num,
198 "A valid statement must have a variable name.");
199 }
200 return Status::OK();
201 }
202
203 Status RocksDBOptionsParser::Parse(const std::string& file_name, Env* env,
204 bool ignore_unknown_options) {
205 Reset();
206
207 std::unique_ptr<SequentialFile> seq_file;
208 Status s = env->NewSequentialFile(file_name, &seq_file, EnvOptions());
209 if (!s.ok()) {
210 return s;
211 }
212
213 OptionSection section = kOptionSectionUnknown;
214 std::string title;
215 std::string argument;
216 std::unordered_map<std::string, std::string> opt_map;
217 std::istringstream iss;
218 std::string line;
219 bool has_data = true;
220 // we only support single-lined statement.
221 for (int line_num = 1;
222 ReadOneLine(&iss, seq_file.get(), &line, &has_data, &s); ++line_num) {
223 if (!s.ok()) {
224 return s;
225 }
226 line = TrimAndRemoveComment(line);
227 if (line.empty()) {
228 continue;
229 }
230 if (IsSection(line)) {
231 s = EndSection(section, title, argument, opt_map, ignore_unknown_options);
232 opt_map.clear();
233 if (!s.ok()) {
234 return s;
235 }
236
237 // If the option file is not generated by a higher minor version,
238 // there shouldn't be any unknown option.
239 if (ignore_unknown_options && section == kOptionSectionVersion) {
240 if (db_version[0] < ROCKSDB_MAJOR || (db_version[0] == ROCKSDB_MAJOR &&
241 db_version[1] <= ROCKSDB_MINOR)) {
242 ignore_unknown_options = false;
243 }
244 }
245
246 s = ParseSection(&section, &title, &argument, line, line_num);
247 if (!s.ok()) {
248 return s;
249 }
250 } else {
251 std::string name;
252 std::string value;
253 s = ParseStatement(&name, &value, line, line_num);
254 if (!s.ok()) {
255 return s;
256 }
257 opt_map.insert({name, value});
258 }
259 }
260
261 s = EndSection(section, title, argument, opt_map, ignore_unknown_options);
262 opt_map.clear();
263 if (!s.ok()) {
264 return s;
265 }
266 return ValidityCheck();
267 }
268
269 Status RocksDBOptionsParser::CheckSection(const OptionSection section,
270 const std::string& section_arg,
271 const int line_num) {
272 if (section == kOptionSectionDBOptions) {
273 if (has_db_options_) {
274 return InvalidArgument(
275 line_num,
276 "More than one DBOption section found in the option config file");
277 }
278 has_db_options_ = true;
279 } else if (section == kOptionSectionCFOptions) {
280 bool is_default_cf = (section_arg == kDefaultColumnFamilyName);
281 if (cf_opts_.size() == 0 && !is_default_cf) {
282 return InvalidArgument(
283 line_num,
284 "Default column family must be the first CFOptions section "
285 "in the option config file");
286 } else if (cf_opts_.size() != 0 && is_default_cf) {
287 return InvalidArgument(
288 line_num,
289 "Default column family must be the first CFOptions section "
290 "in the optio/n config file");
291 } else if (GetCFOptions(section_arg) != nullptr) {
292 return InvalidArgument(
293 line_num,
294 "Two identical column families found in option config file");
295 }
296 has_default_cf_options_ |= is_default_cf;
297 } else if (section == kOptionSectionTableOptions) {
298 if (GetCFOptions(section_arg) == nullptr) {
299 return InvalidArgument(
300 line_num, std::string(
301 "Does not find a matched column family name in "
302 "TableOptions section. Column Family Name:") +
303 section_arg);
304 }
305 } else if (section == kOptionSectionVersion) {
306 if (has_version_section_) {
307 return InvalidArgument(
308 line_num,
309 "More than one Version section found in the option config file.");
310 }
311 has_version_section_ = true;
312 }
313 return Status::OK();
314 }
315
316 Status RocksDBOptionsParser::ParseVersionNumber(const std::string& ver_name,
317 const std::string& ver_string,
318 const int max_count,
319 int* version) {
320 int version_index = 0;
321 int current_number = 0;
322 int current_digit_count = 0;
323 bool has_dot = false;
324 for (int i = 0; i < max_count; ++i) {
325 version[i] = 0;
326 }
327 const int kBufferSize = 200;
328 char buffer[kBufferSize];
329 for (size_t i = 0; i < ver_string.size(); ++i) {
330 if (ver_string[i] == '.') {
331 if (version_index >= max_count - 1) {
332 snprintf(buffer, sizeof(buffer) - 1,
333 "A valid %s can only contains at most %d dots.",
334 ver_name.c_str(), max_count - 1);
335 return Status::InvalidArgument(buffer);
336 }
337 if (current_digit_count == 0) {
338 snprintf(buffer, sizeof(buffer) - 1,
339 "A valid %s must have at least one digit before each dot.",
340 ver_name.c_str());
341 return Status::InvalidArgument(buffer);
342 }
343 version[version_index++] = current_number;
344 current_number = 0;
345 current_digit_count = 0;
346 has_dot = true;
347 } else if (isdigit(ver_string[i])) {
348 current_number = current_number * 10 + (ver_string[i] - '0');
349 current_digit_count++;
350 } else {
351 snprintf(buffer, sizeof(buffer) - 1,
352 "A valid %s can only contains dots and numbers.",
353 ver_name.c_str());
354 return Status::InvalidArgument(buffer);
355 }
356 }
357 version[version_index] = current_number;
358 if (has_dot && current_digit_count == 0) {
359 snprintf(buffer, sizeof(buffer) - 1,
360 "A valid %s must have at least one digit after each dot.",
361 ver_name.c_str());
362 return Status::InvalidArgument(buffer);
363 }
364 return Status::OK();
365 }
366
367 Status RocksDBOptionsParser::EndSection(
368 const OptionSection section, const std::string& section_title,
369 const std::string& section_arg,
370 const std::unordered_map<std::string, std::string>& opt_map,
371 bool ignore_unknown_options) {
372 Status s;
373 if (section == kOptionSectionDBOptions) {
374 s = GetDBOptionsFromMap(DBOptions(), opt_map, &db_opt_, true,
375 ignore_unknown_options);
376 if (!s.ok()) {
377 return s;
378 }
379 db_opt_map_ = opt_map;
380 } else if (section == kOptionSectionCFOptions) {
381 // This condition should be ensured earlier in ParseSection
382 // so we make an assertion here.
383 assert(GetCFOptions(section_arg) == nullptr);
384 cf_names_.emplace_back(section_arg);
385 cf_opts_.emplace_back();
386 s = GetColumnFamilyOptionsFromMap(ColumnFamilyOptions(), opt_map,
387 &cf_opts_.back(), true,
388 ignore_unknown_options);
389 if (!s.ok()) {
390 return s;
391 }
392 // keep the parsed string.
393 cf_opt_maps_.emplace_back(opt_map);
394 } else if (section == kOptionSectionTableOptions) {
395 assert(GetCFOptions(section_arg) != nullptr);
396 auto* cf_opt = GetCFOptionsImpl(section_arg);
397 if (cf_opt == nullptr) {
398 return Status::InvalidArgument(
399 "The specified column family must be defined before the "
400 "TableOptions section:",
401 section_arg);
402 }
403 // Ignore error as table factory deserialization is optional
404 s = GetTableFactoryFromMap(
405 section_title.substr(
406 opt_section_titles[kOptionSectionTableOptions].size()),
407 opt_map, &(cf_opt->table_factory), ignore_unknown_options);
408 if (!s.ok()) {
409 return s;
410 }
411 } else if (section == kOptionSectionVersion) {
412 for (const auto pair : opt_map) {
413 if (pair.first == "rocksdb_version") {
414 s = ParseVersionNumber(pair.first, pair.second, 3, db_version);
415 if (!s.ok()) {
416 return s;
417 }
418 } else if (pair.first == "options_file_version") {
419 s = ParseVersionNumber(pair.first, pair.second, 2, opt_file_version);
420 if (!s.ok()) {
421 return s;
422 }
423 if (opt_file_version[0] < 1) {
424 return Status::InvalidArgument(
425 "A valid options_file_version must be at least 1.");
426 }
427 }
428 }
429 }
430 return Status::OK();
431 }
432
433 Status RocksDBOptionsParser::ValidityCheck() {
434 if (!has_db_options_) {
435 return Status::Corruption(
436 "A RocksDB Option file must have a single DBOptions section");
437 }
438 if (!has_default_cf_options_) {
439 return Status::Corruption(
440 "A RocksDB Option file must have a single CFOptions:default section");
441 }
442
443 return Status::OK();
444 }
445
446 std::string RocksDBOptionsParser::TrimAndRemoveComment(const std::string& line,
447 bool trim_only) {
448 size_t start = 0;
449 size_t end = line.size();
450
451 // we only support "#" style comment
452 if (!trim_only) {
453 size_t search_pos = 0;
454 while (search_pos < line.size()) {
455 size_t comment_pos = line.find('#', search_pos);
456 if (comment_pos == std::string::npos) {
457 break;
458 }
459 if (comment_pos == 0 || line[comment_pos - 1] != '\\') {
460 end = comment_pos;
461 break;
462 }
463 search_pos = comment_pos + 1;
464 }
465 }
466
467 while (start < end && isspace(line[start]) != 0) {
468 ++start;
469 }
470
471 // start < end implies end > 0.
472 while (start < end && isspace(line[end - 1]) != 0) {
473 --end;
474 }
475
476 if (start < end) {
477 return line.substr(start, end - start);
478 }
479
480 return "";
481 }
482
483 namespace {
484 bool AreEqualDoubles(const double a, const double b) {
485 return (fabs(a - b) < 0.00001);
486 }
487 } // namespace
488
489 bool AreEqualOptions(
490 const char* opt1, const char* opt2, const OptionTypeInfo& type_info,
491 const std::string& opt_name,
492 const std::unordered_map<std::string, std::string>* opt_map) {
493 const char* offset1 = opt1 + type_info.offset;
494 const char* offset2 = opt2 + type_info.offset;
495
496 switch (type_info.type) {
497 case OptionType::kBoolean:
498 return (*reinterpret_cast<const bool*>(offset1) ==
499 *reinterpret_cast<const bool*>(offset2));
500 case OptionType::kInt:
501 return (*reinterpret_cast<const int*>(offset1) ==
502 *reinterpret_cast<const int*>(offset2));
503 case OptionType::kInt32T:
504 return (*reinterpret_cast<const int32_t*>(offset1) ==
505 *reinterpret_cast<const int32_t*>(offset2));
506 case OptionType::kInt64T:
507 {
508 int64_t v1, v2;
509 GetUnaligned(reinterpret_cast<const int64_t*>(offset1), &v1);
510 GetUnaligned(reinterpret_cast<const int64_t*>(offset2), &v2);
511 return (v1 == v2);
512 }
513 case OptionType::kVectorInt:
514 return (*reinterpret_cast<const std::vector<int>*>(offset1) ==
515 *reinterpret_cast<const std::vector<int>*>(offset2));
516 case OptionType::kUInt:
517 return (*reinterpret_cast<const unsigned int*>(offset1) ==
518 *reinterpret_cast<const unsigned int*>(offset2));
519 case OptionType::kUInt32T:
520 return (*reinterpret_cast<const uint32_t*>(offset1) ==
521 *reinterpret_cast<const uint32_t*>(offset2));
522 case OptionType::kUInt64T:
523 {
524 uint64_t v1, v2;
525 GetUnaligned(reinterpret_cast<const uint64_t*>(offset1), &v1);
526 GetUnaligned(reinterpret_cast<const uint64_t*>(offset2), &v2);
527 return (v1 == v2);
528 }
529 case OptionType::kSizeT:
530 {
531 size_t v1, v2;
532 GetUnaligned(reinterpret_cast<const size_t*>(offset1), &v1);
533 GetUnaligned(reinterpret_cast<const size_t*>(offset2), &v2);
534 return (v1 == v2);
535 }
536 case OptionType::kString:
537 return (*reinterpret_cast<const std::string*>(offset1) ==
538 *reinterpret_cast<const std::string*>(offset2));
539 case OptionType::kDouble:
540 return AreEqualDoubles(*reinterpret_cast<const double*>(offset1),
541 *reinterpret_cast<const double*>(offset2));
542 case OptionType::kCompactionStyle:
543 return (*reinterpret_cast<const CompactionStyle*>(offset1) ==
544 *reinterpret_cast<const CompactionStyle*>(offset2));
545 case OptionType::kCompactionPri:
546 return (*reinterpret_cast<const CompactionPri*>(offset1) ==
547 *reinterpret_cast<const CompactionPri*>(offset2));
548 case OptionType::kCompressionType:
549 return (*reinterpret_cast<const CompressionType*>(offset1) ==
550 *reinterpret_cast<const CompressionType*>(offset2));
551 case OptionType::kVectorCompressionType: {
552 const auto* vec1 =
553 reinterpret_cast<const std::vector<CompressionType>*>(offset1);
554 const auto* vec2 =
555 reinterpret_cast<const std::vector<CompressionType>*>(offset2);
556 return (*vec1 == *vec2);
557 }
558 case OptionType::kChecksumType:
559 return (*reinterpret_cast<const ChecksumType*>(offset1) ==
560 *reinterpret_cast<const ChecksumType*>(offset2));
561 case OptionType::kBlockBasedTableIndexType:
562 return (
563 *reinterpret_cast<const BlockBasedTableOptions::IndexType*>(
564 offset1) ==
565 *reinterpret_cast<const BlockBasedTableOptions::IndexType*>(offset2));
566 case OptionType::kBlockBasedTableDataBlockIndexType:
567 return (
568 *reinterpret_cast<const BlockBasedTableOptions::DataBlockIndexType*>(
569 offset1) ==
570 *reinterpret_cast<const BlockBasedTableOptions::DataBlockIndexType*>(
571 offset2));
572 case OptionType::kWALRecoveryMode:
573 return (*reinterpret_cast<const WALRecoveryMode*>(offset1) ==
574 *reinterpret_cast<const WALRecoveryMode*>(offset2));
575 case OptionType::kAccessHint:
576 return (*reinterpret_cast<const DBOptions::AccessHint*>(offset1) ==
577 *reinterpret_cast<const DBOptions::AccessHint*>(offset2));
578 case OptionType::kInfoLogLevel:
579 return (*reinterpret_cast<const InfoLogLevel*>(offset1) ==
580 *reinterpret_cast<const InfoLogLevel*>(offset2));
581 case OptionType::kCompactionOptionsFIFO: {
582 CompactionOptionsFIFO lhs =
583 *reinterpret_cast<const CompactionOptionsFIFO*>(offset1);
584 CompactionOptionsFIFO rhs =
585 *reinterpret_cast<const CompactionOptionsFIFO*>(offset2);
586 if (lhs.max_table_files_size == rhs.max_table_files_size &&
587 lhs.allow_compaction == rhs.allow_compaction) {
588 return true;
589 }
590 return false;
591 }
592 case OptionType::kCompactionOptionsUniversal: {
593 CompactionOptionsUniversal lhs =
594 *reinterpret_cast<const CompactionOptionsUniversal*>(offset1);
595 CompactionOptionsUniversal rhs =
596 *reinterpret_cast<const CompactionOptionsUniversal*>(offset2);
597 if (lhs.size_ratio == rhs.size_ratio &&
598 lhs.min_merge_width == rhs.min_merge_width &&
599 lhs.max_merge_width == rhs.max_merge_width &&
600 lhs.max_size_amplification_percent ==
601 rhs.max_size_amplification_percent &&
602 lhs.compression_size_percent == rhs.compression_size_percent &&
603 lhs.stop_style == rhs.stop_style &&
604 lhs.allow_trivial_move == rhs.allow_trivial_move) {
605 return true;
606 }
607 return false;
608 }
609 default:
610 if (type_info.verification == OptionVerificationType::kByName ||
611 type_info.verification ==
612 OptionVerificationType::kByNameAllowFromNull ||
613 type_info.verification == OptionVerificationType::kByNameAllowNull) {
614 std::string value1;
615 bool result =
616 SerializeSingleOptionHelper(offset1, type_info.type, &value1);
617 if (result == false) {
618 return false;
619 }
620 if (opt_map == nullptr) {
621 return true;
622 }
623 auto iter = opt_map->find(opt_name);
624 if (iter == opt_map->end()) {
625 return true;
626 } else {
627 if (type_info.verification ==
628 OptionVerificationType::kByNameAllowNull) {
629 if (iter->second == kNullptrString || value1 == kNullptrString) {
630 return true;
631 }
632 } else if (type_info.verification ==
633 OptionVerificationType::kByNameAllowFromNull) {
634 if (iter->second == kNullptrString) {
635 return true;
636 }
637 }
638 return (value1 == iter->second);
639 }
640 }
641 return false;
642 }
643 }
644
645 Status RocksDBOptionsParser::VerifyRocksDBOptionsFromFile(
646 const DBOptions& db_opt, const std::vector<std::string>& cf_names,
647 const std::vector<ColumnFamilyOptions>& cf_opts,
648 const std::string& file_name, Env* env,
649 OptionsSanityCheckLevel sanity_check_level, bool ignore_unknown_options) {
650 RocksDBOptionsParser parser;
651 std::unique_ptr<SequentialFile> seq_file;
652 Status s = parser.Parse(file_name, env, ignore_unknown_options);
653 if (!s.ok()) {
654 return s;
655 }
656
657 // Verify DBOptions
658 s = VerifyDBOptions(db_opt, *parser.db_opt(), parser.db_opt_map(),
659 sanity_check_level);
660 if (!s.ok()) {
661 return s;
662 }
663
664 // Verify ColumnFamily Name
665 if (cf_names.size() != parser.cf_names()->size()) {
666 if (sanity_check_level >= kSanityLevelLooselyCompatible) {
667 return Status::InvalidArgument(
668 "[RocksDBOptionParser Error] The persisted options does not have "
669 "the same number of column family names as the db instance.");
670 } else if (cf_opts.size() > parser.cf_opts()->size()) {
671 return Status::InvalidArgument(
672 "[RocksDBOptionsParser Error]",
673 "The persisted options file has less number of column family "
674 "names than that of the specified one.");
675 }
676 }
677 for (size_t i = 0; i < cf_names.size(); ++i) {
678 if (cf_names[i] != parser.cf_names()->at(i)) {
679 return Status::InvalidArgument(
680 "[RocksDBOptionParser Error] The persisted options and the db"
681 "instance does not have the same name for column family ",
682 ToString(i));
683 }
684 }
685
686 // Verify Column Family Options
687 if (cf_opts.size() != parser.cf_opts()->size()) {
688 if (sanity_check_level >= kSanityLevelLooselyCompatible) {
689 return Status::InvalidArgument(
690 "[RocksDBOptionsParser Error]",
691 "The persisted options does not have the same number of "
692 "column families as the db instance.");
693 } else if (cf_opts.size() > parser.cf_opts()->size()) {
694 return Status::InvalidArgument(
695 "[RocksDBOptionsParser Error]",
696 "The persisted options file has less number of column families "
697 "than that of the specified number.");
698 }
699 }
700 for (size_t i = 0; i < cf_opts.size(); ++i) {
701 s = VerifyCFOptions(cf_opts[i], parser.cf_opts()->at(i),
702 &(parser.cf_opt_maps()->at(i)), sanity_check_level);
703 if (!s.ok()) {
704 return s;
705 }
706 s = VerifyTableFactory(cf_opts[i].table_factory.get(),
707 parser.cf_opts()->at(i).table_factory.get(),
708 sanity_check_level);
709 if (!s.ok()) {
710 return s;
711 }
712 }
713
714 return Status::OK();
715 }
716
717 Status RocksDBOptionsParser::VerifyDBOptions(
718 const DBOptions& base_opt, const DBOptions& persisted_opt,
719 const std::unordered_map<std::string, std::string>* /*opt_map*/,
720 OptionsSanityCheckLevel sanity_check_level) {
721 for (auto pair : db_options_type_info) {
722 if (pair.second.verification == OptionVerificationType::kDeprecated) {
723 // We skip checking deprecated variables as they might
724 // contain random values since they might not be initialized
725 continue;
726 }
727 if (DBOptionSanityCheckLevel(pair.first) <= sanity_check_level) {
728 if (!AreEqualOptions(reinterpret_cast<const char*>(&base_opt),
729 reinterpret_cast<const char*>(&persisted_opt),
730 pair.second, pair.first, nullptr)) {
731 const size_t kBufferSize = 2048;
732 char buffer[kBufferSize];
733 std::string base_value;
734 std::string persisted_value;
735 SerializeSingleOptionHelper(
736 reinterpret_cast<const char*>(&base_opt) + pair.second.offset,
737 pair.second.type, &base_value);
738 SerializeSingleOptionHelper(
739 reinterpret_cast<const char*>(&persisted_opt) + pair.second.offset,
740 pair.second.type, &persisted_value);
741 snprintf(buffer, sizeof(buffer),
742 "[RocksDBOptionsParser]: "
743 "failed the verification on DBOptions::%s --- "
744 "The specified one is %s while the persisted one is %s.\n",
745 pair.first.c_str(), base_value.c_str(),
746 persisted_value.c_str());
747 return Status::InvalidArgument(Slice(buffer, strlen(buffer)));
748 }
749 }
750 }
751 return Status::OK();
752 }
753
754 Status RocksDBOptionsParser::VerifyCFOptions(
755 const ColumnFamilyOptions& base_opt,
756 const ColumnFamilyOptions& persisted_opt,
757 const std::unordered_map<std::string, std::string>* persisted_opt_map,
758 OptionsSanityCheckLevel sanity_check_level) {
759 for (auto& pair : cf_options_type_info) {
760 if (pair.second.verification == OptionVerificationType::kDeprecated) {
761 // We skip checking deprecated variables as they might
762 // contain random values since they might not be initialized
763 continue;
764 }
765 if (CFOptionSanityCheckLevel(pair.first) <= sanity_check_level) {
766 if (!AreEqualOptions(reinterpret_cast<const char*>(&base_opt),
767 reinterpret_cast<const char*>(&persisted_opt),
768 pair.second, pair.first, persisted_opt_map)) {
769 const size_t kBufferSize = 2048;
770 char buffer[kBufferSize];
771 std::string base_value;
772 std::string persisted_value;
773 SerializeSingleOptionHelper(
774 reinterpret_cast<const char*>(&base_opt) + pair.second.offset,
775 pair.second.type, &base_value);
776 SerializeSingleOptionHelper(
777 reinterpret_cast<const char*>(&persisted_opt) + pair.second.offset,
778 pair.second.type, &persisted_value);
779 snprintf(buffer, sizeof(buffer),
780 "[RocksDBOptionsParser]: "
781 "failed the verification on ColumnFamilyOptions::%s --- "
782 "The specified one is %s while the persisted one is %s.\n",
783 pair.first.c_str(), base_value.c_str(),
784 persisted_value.c_str());
785 return Status::InvalidArgument(Slice(buffer, sizeof(buffer)));
786 }
787 }
788 }
789 return Status::OK();
790 }
791
792 Status RocksDBOptionsParser::VerifyTableFactory(
793 const TableFactory* base_tf, const TableFactory* file_tf,
794 OptionsSanityCheckLevel sanity_check_level) {
795 if (base_tf && file_tf) {
796 if (sanity_check_level > kSanityLevelNone &&
797 std::string(base_tf->Name()) != std::string(file_tf->Name())) {
798 return Status::Corruption(
799 "[RocksDBOptionsParser]: "
800 "failed the verification on TableFactory->Name()");
801 }
802 if (base_tf->Name() == BlockBasedTableFactory::kName) {
803 return VerifyBlockBasedTableFactory(
804 static_cast_with_check<const BlockBasedTableFactory,
805 const TableFactory>(base_tf),
806 static_cast_with_check<const BlockBasedTableFactory,
807 const TableFactory>(file_tf),
808 sanity_check_level);
809 }
810 // TODO(yhchiang): add checks for other table factory types
811 } else {
812 // TODO(yhchiang): further support sanity check here
813 }
814 return Status::OK();
815 }
816 } // namespace rocksdb
817
818 #endif // !ROCKSDB_LITE