/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(§ion, &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 |