YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

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