options_parser.cc 31 KB

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