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 | |
45 | namespace myrocks { |
46 | |
47 | /* |
48 | Skip past any spaces in the input |
49 | */ |
50 | const 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 | */ |
67 | bool 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 | */ |
87 | const 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 | */ |
136 | const 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 | */ |
160 | const 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 | */ |
222 | const 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 | */ |
233 | const 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 | |
246 | static const std::size_t rdb_hex_bytes_per_char = 2; |
247 | static 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 | */ |
255 | std::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 | */ |
293 | bool 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 | |
306 | void 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 | */ |
324 | const 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 | |
355 | bool rdb_check_rocksdb_corruption() { |
356 | return !my_access(myrocks::rdb_corruption_marker_file_name().c_str(), F_OK); |
357 | } |
358 | |
359 | void 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 | |