1/*
2 Copyright (c) 2016, Facebook, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
16
17#include <my_global.h>
18
19/* This C++ file's header */
20#include "./rdb_utils.h"
21
22/* C++ standard header files */
23#include <array>
24#include <string>
25#include <vector>
26#include <sstream>
27
28/* C standard header files */
29#include <ctype.h>
30
31/* MyRocks header files */
32#include "./ha_rocksdb.h"
33
34/*
35 Both innobase/include/ut0counter.h and rocksdb/port/port_posix.h define
36 CACHE_LINE_SIZE.
37*/
38#ifdef CACHE_LINE_SIZE
39# undef CACHE_LINE_SIZE
40#endif
41
42/* RocksDB header files */
43#include "util/compression.h"
44
45namespace myrocks {
46
47/*
48 Skip past any spaces in the input
49*/
50const char *rdb_skip_spaces(const struct charset_info_st *const cs,
51 const char *str) {
52 DBUG_ASSERT(cs != nullptr);
53 DBUG_ASSERT(str != nullptr);
54
55 while (my_isspace(cs, *str)) {
56 str++;
57 }
58
59 return str;
60}
61
62/*
63 Compare (ignoring case) to see if str2 is the next data in str1.
64 Note that str1 can be longer but we only compare up to the number
65 of characters in str2.
66*/
67bool rdb_compare_strings_ic(const char *const str1, const char *const str2) {
68 DBUG_ASSERT(str1 != nullptr);
69 DBUG_ASSERT(str2 != nullptr);
70
71 // Scan through the strings
72 size_t ii;
73 for (ii = 0; str2[ii]; ii++) {
74 if (toupper(static_cast<int>(str1[ii])) !=
75 toupper(static_cast<int>(str2[ii]))) {
76 return false;
77 }
78 }
79
80 return true;
81}
82
83/*
84 Scan through an input string looking for pattern, ignoring case
85 and skipping all data enclosed in quotes.
86*/
87const char *rdb_find_in_string(const char *str, const char *pattern,
88 bool *const succeeded) {
89 char quote = '\0';
90 bool escape = false;
91
92 DBUG_ASSERT(str != nullptr);
93 DBUG_ASSERT(pattern != nullptr);
94 DBUG_ASSERT(succeeded != nullptr);
95
96 *succeeded = false;
97
98 for (; *str; str++) {
99 /* If we found a our starting quote character */
100 if (*str == quote) {
101 /* If it was escaped ignore it */
102 if (escape) {
103 escape = false;
104 }
105 /* Otherwise we are now outside of the quoted string */
106 else {
107 quote = '\0';
108 }
109 }
110 /* Else if we are currently inside a quoted string? */
111 else if (quote != '\0') {
112 /* If so, check for the escape character */
113 escape = !escape && *str == '\\';
114 }
115 /* Else if we found a quote we are starting a quoted string */
116 else if (*str == '"' || *str == '\'' || *str == '`') {
117 quote = *str;
118 }
119 /* Else we are outside of a quoted string - look for our pattern */
120 else {
121 if (rdb_compare_strings_ic(str, pattern)) {
122 *succeeded = true;
123 return str;
124 }
125 }
126 }
127
128 // Return the character after the found pattern or the null terminateor
129 // if the pattern wasn't found.
130 return str;
131}
132
133/*
134 See if the next valid token matches the specified string
135*/
136const char *rdb_check_next_token(const struct charset_info_st *const cs,
137 const char *str, const char *const pattern,
138 bool *const succeeded) {
139 DBUG_ASSERT(cs != nullptr);
140 DBUG_ASSERT(str != nullptr);
141 DBUG_ASSERT(pattern != nullptr);
142 DBUG_ASSERT(succeeded != nullptr);
143
144 // Move past any spaces
145 str = rdb_skip_spaces(cs, str);
146
147 // See if the next characters match the pattern
148 if (rdb_compare_strings_ic(str, pattern)) {
149 *succeeded = true;
150 return str + strlen(pattern);
151 }
152
153 *succeeded = false;
154 return str;
155}
156
157/*
158 Parse id
159*/
160const char *rdb_parse_id(const struct charset_info_st *const cs,
161 const char *str, std::string *const id) {
162 DBUG_ASSERT(cs != nullptr);
163 DBUG_ASSERT(str != nullptr);
164
165 // Move past any spaces
166 str = rdb_skip_spaces(cs, str);
167
168 if (*str == '\0') {
169 return str;
170 }
171
172 char quote = '\0';
173 if (*str == '`' || *str == '"') {
174 quote = *str++;
175 }
176
177 size_t len = 0;
178 const char *start = str;
179
180 if (quote != '\0') {
181 for (;;) {
182 if (*str == '\0') {
183 return str;
184 }
185
186 if (*str == quote) {
187 str++;
188 if (*str != quote) {
189 break;
190 }
191 }
192
193 str++;
194 len++;
195 }
196 } else {
197 while (!my_isspace(cs, *str) && *str != '(' && *str != ')' && *str != '.' &&
198 *str != ',' && *str != '\0') {
199 str++;
200 len++;
201 }
202 }
203
204 // If the user requested the id create it and return it
205 if (id != nullptr) {
206 *id = std::string("");
207 id->reserve(len);
208 while (len--) {
209 *id += *start;
210 if (*start++ == quote) {
211 start++;
212 }
213 }
214 }
215
216 return str;
217}
218
219/*
220 Skip id
221*/
222const char *rdb_skip_id(const struct charset_info_st *const cs,
223 const char *str) {
224 DBUG_ASSERT(cs != nullptr);
225 DBUG_ASSERT(str != nullptr);
226
227 return rdb_parse_id(cs, str, nullptr);
228}
229
230/*
231 Parses a given string into tokens (if any) separated by a specific delimiter.
232*/
233const std::vector<std::string> parse_into_tokens(
234 const std::string& s, const char delim) {
235 std::vector<std::string> tokens;
236 std::string t;
237 std::stringstream ss(s);
238
239 while (getline(ss, t, delim)) {
240 tokens.push_back(t);
241 }
242
243 return tokens;
244}
245
246static const std::size_t rdb_hex_bytes_per_char = 2;
247static const std::array<char, 16> rdb_hexdigit = {{'0', '1', '2', '3', '4', '5',
248 '6', '7', '8', '9', 'a', 'b',
249 'c', 'd', 'e', 'f'}};
250
251/*
252 Convert data into a hex string with optional maximum length.
253 If the data is larger than the maximum length trancate it and append "..".
254*/
255std::string rdb_hexdump(const char *data, const std::size_t data_len,
256 const std::size_t maxsize) {
257 DBUG_ASSERT(data != nullptr);
258
259 // Count the elements in the string
260 std::size_t elems = data_len;
261 // Calculate the amount of output needed
262 std::size_t len = elems * rdb_hex_bytes_per_char;
263 std::string str;
264
265 if (maxsize != 0 && len > maxsize) {
266 // If the amount of output is too large adjust the settings
267 // and leave room for the ".." at the end
268 elems = (maxsize - 2) / rdb_hex_bytes_per_char;
269 len = elems * rdb_hex_bytes_per_char + 2;
270 }
271
272 // Reserve sufficient space to avoid reallocations
273 str.reserve(len);
274
275 // Loop through the input data and build the output string
276 for (std::size_t ii = 0; ii < elems; ii++, data++) {
277 uint8_t ch = (uint8_t)*data;
278 str += rdb_hexdigit[ch >> 4];
279 str += rdb_hexdigit[ch & 0x0F];
280 }
281
282 // If we can't fit it all add the ".."
283 if (elems != data_len) {
284 str += "..";
285 }
286
287 return str;
288}
289
290/*
291 Attempt to access the database subdirectory to see if it exists
292*/
293bool rdb_database_exists(const std::string &db_name) {
294 const std::string dir =
295 std::string(mysql_real_data_home) + FN_DIRSEP + db_name;
296 struct st_my_dir *const dir_info =
297 my_dir(dir.c_str(), MYF(MY_DONT_SORT | MY_WANT_STAT));
298 if (dir_info == nullptr) {
299 return false;
300 }
301
302 my_dirend(dir_info);
303 return true;
304}
305
306void rdb_log_status_error(const rocksdb::Status &s, const char *msg) {
307 if (msg == nullptr) {
308 // NO_LINT_DEBUG
309 sql_print_error("RocksDB: status error, code: %d, error message: %s",
310 s.code(), s.ToString().c_str());
311 return;
312 }
313
314 // NO_LINT_DEBUG
315 sql_print_error("RocksDB: %s, Status Code: %d, Status: %s", msg, s.code(),
316 s.ToString().c_str());
317}
318
319/*
320 @brief
321 Return a comma-separated string with compiled-in compression types.
322 Not thread-safe.
323*/
324const char *get_rocksdb_supported_compression_types()
325{
326 static std::string compression_methods_buf;
327 static bool inited=false;
328 if (!inited)
329 {
330 inited= true;
331 std::vector<rocksdb::CompressionType> known_types=
332 {
333 rocksdb::kSnappyCompression,
334 rocksdb::kZlibCompression,
335 rocksdb::kBZip2Compression,
336 rocksdb::kLZ4Compression,
337 rocksdb::kLZ4HCCompression,
338 rocksdb::kXpressCompression,
339 rocksdb::kZSTDNotFinalCompression
340 };
341
342 for (auto typ : known_types)
343 {
344 if (CompressionTypeSupported(typ))
345 {
346 if (compression_methods_buf.size())
347 compression_methods_buf.append(",");
348 compression_methods_buf.append(CompressionTypeToString(typ));
349 }
350 }
351 }
352 return compression_methods_buf.c_str();
353}
354
355bool rdb_check_rocksdb_corruption() {
356 return !my_access(myrocks::rdb_corruption_marker_file_name().c_str(), F_OK);
357}
358
359void rdb_persist_corruption_marker() {
360 const std::string &fileName(myrocks::rdb_corruption_marker_file_name());
361 /* O_SYNC is not supported on windows */
362 int fd = my_open(fileName.c_str(), O_CREAT | IF_WIN(0, O_SYNC), MYF(MY_WME));
363 if (fd < 0) {
364 sql_print_error("RocksDB: Can't create file %s to mark rocksdb as "
365 "corrupted.",
366 fileName.c_str());
367 } else {
368 sql_print_information("RocksDB: Creating the file %s to abort mysqld "
369 "restarts. Remove this file from the data directory "
370 "after fixing the corruption to recover. ",
371 fileName.c_str());
372 }
373
374#ifdef _WIN32
375 /* A replacement for O_SYNC flag above */
376 if (fd >= 0)
377 my_sync(fd, MYF(0));
378#endif
379
380 int ret = my_close(fd, MYF(MY_WME));
381 if (ret) {
382 // NO_LINT_DEBUG
383 sql_print_error("RocksDB: Error (%d) closing the file %s", ret,
384 fileName.c_str());
385 }
386}
387
388} // namespace myrocks
389