1 | /* -*- c-basic-offset: 2 -*- */ |
2 | /* |
3 | Copyright(C) 2015-2017 Kouhei Sutou <kou@clear-code.com> |
4 | |
5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | This library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with this library; if not, write to the Free Software |
17 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
18 | */ |
19 | |
20 | #include <mrn_mysql.h> |
21 | #include <mrn_mysql_compat.h> |
22 | #include <mrn_constants.hpp> |
23 | |
24 | #include "mrn_database_repairer.hpp" |
25 | #include "mrn_path_mapper.hpp" |
26 | |
27 | // for debug |
28 | #define MRN_CLASS_NAME "mrn::DatabaseRepairer" |
29 | |
30 | #include <sys/stat.h> |
31 | #include <sys/types.h> |
32 | #include <errno.h> |
33 | |
34 | #ifndef WIN32 |
35 | # include <dirent.h> |
36 | #endif |
37 | |
38 | namespace mrn { |
39 | struct CheckResult { |
40 | CheckResult() : |
41 | is_crashed(false), |
42 | is_corrupt(false) { |
43 | } |
44 | |
45 | bool is_crashed; |
46 | bool is_corrupt; |
47 | }; |
48 | |
49 | DatabaseRepairer::DatabaseRepairer(grn_ctx *ctx, THD *thd) |
50 | : ctx_(ctx), |
51 | thd_(thd), |
52 | base_directory_(NULL), |
53 | base_directory_buffer_(), |
54 | path_prefix_(NULL), |
55 | path_prefix_buffer_(), |
56 | path_prefix_length_(0), |
57 | mrn_db_file_suffix_length_(strlen(MRN_DB_FILE_SUFFIX)) { |
58 | } |
59 | |
60 | DatabaseRepairer::~DatabaseRepairer() { |
61 | } |
62 | |
63 | bool DatabaseRepairer::is_crashed(void) { |
64 | MRN_DBUG_ENTER_METHOD(); |
65 | |
66 | CheckResult result; |
67 | each_database(&DatabaseRepairer::check_body, &result); |
68 | |
69 | DBUG_RETURN(result.is_crashed); |
70 | } |
71 | |
72 | bool DatabaseRepairer::is_corrupt(void) { |
73 | MRN_DBUG_ENTER_METHOD(); |
74 | |
75 | CheckResult result; |
76 | each_database(&DatabaseRepairer::check_body, &result); |
77 | |
78 | DBUG_RETURN(result.is_corrupt); |
79 | } |
80 | |
81 | bool DatabaseRepairer::repair(void) { |
82 | MRN_DBUG_ENTER_METHOD(); |
83 | |
84 | bool succeeded = true; |
85 | each_database(&DatabaseRepairer::repair_body, &succeeded); |
86 | |
87 | DBUG_RETURN(succeeded); |
88 | } |
89 | |
90 | void DatabaseRepairer::each_database(EachBodyFunc each_body_func, |
91 | void *user_data) { |
92 | MRN_DBUG_ENTER_METHOD(); |
93 | |
94 | detect_paths(); |
95 | |
96 | #ifdef WIN32 |
97 | WIN32_FIND_DATA data; |
98 | HANDLE finder = FindFirstFile(base_directory_, &data); |
99 | if (finder == INVALID_HANDLE_VALUE) { |
100 | DBUG_VOID_RETURN; |
101 | } |
102 | |
103 | grn_ctx ctx; |
104 | grn_rc rc = grn_ctx_init(&ctx, 0); |
105 | if (rc == GRN_SUCCESS) { |
106 | do { |
107 | each_database_body(data.cFileName, &ctx, each_body_func, user_data); |
108 | } while (FindNextFile(finder, &data) != 0); |
109 | grn_ctx_fin(&ctx); |
110 | } else { |
111 | GRN_LOG(ctx_, GRN_LOG_WARNING, |
112 | "[mroonga][database][repairer][each] " |
113 | "failed to initialize grn_ctx: %d: %s" , |
114 | rc, grn_rc_to_string(rc)); |
115 | } |
116 | FindClose(finder); |
117 | #else |
118 | DIR *dir = opendir(base_directory_); |
119 | if (!dir) { |
120 | DBUG_VOID_RETURN; |
121 | } |
122 | |
123 | grn_ctx ctx; |
124 | grn_rc rc = grn_ctx_init(&ctx, 0); |
125 | if (rc == GRN_SUCCESS) { |
126 | while (struct dirent *entry = readdir(dir)) { |
127 | each_database_body(entry->d_name, &ctx, each_body_func, user_data); |
128 | } |
129 | grn_ctx_fin(&ctx); |
130 | } else { |
131 | GRN_LOG(ctx_, GRN_LOG_WARNING, |
132 | "[mroonga][database][repairer][each] " |
133 | "failed to initialize grn_ctx: %d: %s" , |
134 | rc, grn_rc_to_string(rc)); |
135 | } |
136 | closedir(dir); |
137 | #endif |
138 | |
139 | DBUG_VOID_RETURN; |
140 | } |
141 | |
142 | void DatabaseRepairer::each_database_body(const char *base_path, |
143 | grn_ctx *ctx, |
144 | EachBodyFunc each_body_func, |
145 | void *user_data) { |
146 | MRN_DBUG_ENTER_METHOD(); |
147 | |
148 | if (path_prefix_length_ > 0 && |
149 | strncmp(base_path, path_prefix_, path_prefix_length_) != 0) { |
150 | DBUG_VOID_RETURN; |
151 | } |
152 | |
153 | size_t path_length = strlen(base_path); |
154 | if (path_length <= mrn_db_file_suffix_length_) { |
155 | DBUG_VOID_RETURN; |
156 | } |
157 | |
158 | if (strncmp(base_path + (path_length - mrn_db_file_suffix_length_), |
159 | MRN_DB_FILE_SUFFIX, mrn_db_file_suffix_length_) != 0) { |
160 | DBUG_VOID_RETURN; |
161 | } |
162 | |
163 | char db_path[MRN_MAX_PATH_SIZE]; |
164 | snprintf(db_path, MRN_MAX_PATH_SIZE, |
165 | "%s%c%s" , base_directory_, FN_LIBCHAR, base_path); |
166 | grn_obj *db = grn_db_open(ctx, db_path); |
167 | if (!db) { |
168 | DBUG_VOID_RETURN; |
169 | } |
170 | |
171 | (this->*each_body_func)(ctx, db, db_path, user_data); |
172 | |
173 | grn_obj_close(ctx, db); |
174 | |
175 | DBUG_VOID_RETURN; |
176 | } |
177 | |
178 | void DatabaseRepairer::detect_paths(void) { |
179 | MRN_DBUG_ENTER_METHOD(); |
180 | |
181 | const char *raw_path_prefix = mrn::PathMapper::default_path_prefix; |
182 | |
183 | if (!raw_path_prefix) { |
184 | base_directory_ = "." ; |
185 | path_prefix_ = NULL; |
186 | DBUG_VOID_RETURN; |
187 | } |
188 | |
189 | strcpy(base_directory_buffer_, raw_path_prefix); |
190 | size_t raw_path_prefix_length = strlen(raw_path_prefix); |
191 | size_t separator_position = raw_path_prefix_length; |
192 | for (; separator_position > 0; separator_position--) { |
193 | if (mrn_is_directory_separator(base_directory_buffer_[separator_position])) { |
194 | break; |
195 | } |
196 | } |
197 | if (separator_position == 0 || |
198 | separator_position == raw_path_prefix_length) { |
199 | base_directory_ = "." ; |
200 | } else { |
201 | base_directory_buffer_[separator_position] = '\0'; |
202 | base_directory_ = base_directory_buffer_; |
203 | strcpy(path_prefix_buffer_, raw_path_prefix + separator_position + 1); |
204 | path_prefix_ = path_prefix_buffer_; |
205 | path_prefix_length_ = strlen(path_prefix_); |
206 | } |
207 | |
208 | DBUG_VOID_RETURN; |
209 | } |
210 | |
211 | void DatabaseRepairer::check_body(grn_ctx *ctx, |
212 | grn_obj *db, |
213 | const char *db_path, |
214 | void *user_data) { |
215 | MRN_DBUG_ENTER_METHOD(); |
216 | |
217 | CheckResult *result = static_cast<CheckResult *>(user_data); |
218 | |
219 | if (grn_obj_is_locked(ctx, db)) { |
220 | result->is_crashed = true; |
221 | result->is_corrupt = true; |
222 | DBUG_VOID_RETURN; |
223 | } |
224 | |
225 | grn_table_cursor *cursor; |
226 | cursor = grn_table_cursor_open(ctx, db, |
227 | NULL, 0, |
228 | NULL, 0, |
229 | 0, -1, GRN_CURSOR_BY_ID); |
230 | if (!cursor) { |
231 | result->is_crashed = true; |
232 | result->is_corrupt = true; |
233 | DBUG_VOID_RETURN; |
234 | } |
235 | |
236 | grn_id id; |
237 | while ((id = grn_table_cursor_next(ctx, cursor)) != GRN_ID_NIL) { |
238 | if (grn_id_is_builtin(ctx, id)) { |
239 | continue; |
240 | } |
241 | |
242 | grn_obj *object = grn_ctx_at(ctx, id); |
243 | |
244 | if (!object) { |
245 | if (ctx->rc == GRN_SUCCESS) { |
246 | continue; |
247 | } else { |
248 | result->is_corrupt = true; |
249 | break; |
250 | } |
251 | } |
252 | |
253 | switch (object->header.type) { |
254 | case GRN_TABLE_HASH_KEY : |
255 | case GRN_TABLE_PAT_KEY: |
256 | case GRN_TABLE_DAT_KEY: |
257 | case GRN_TABLE_NO_KEY: |
258 | case GRN_COLUMN_FIX_SIZE: |
259 | case GRN_COLUMN_VAR_SIZE: |
260 | case GRN_COLUMN_INDEX: |
261 | if (grn_obj_is_locked(ctx_, object)) { |
262 | result->is_crashed = true; |
263 | result->is_corrupt = true; |
264 | } |
265 | break; |
266 | default: |
267 | break; |
268 | } |
269 | |
270 | grn_obj_unlink(ctx, object); |
271 | |
272 | if (result->is_crashed || result->is_corrupt) { |
273 | break; |
274 | } |
275 | } |
276 | grn_table_cursor_close(ctx, cursor); |
277 | |
278 | DBUG_VOID_RETURN; |
279 | } |
280 | |
281 | void DatabaseRepairer::repair_body(grn_ctx *ctx, |
282 | grn_obj *db, |
283 | const char *db_path, |
284 | void *user_data) { |
285 | MRN_DBUG_ENTER_METHOD(); |
286 | |
287 | bool *succeeded = static_cast<bool *>(user_data); |
288 | if (grn_db_recover(ctx, db) != GRN_SUCCESS) { |
289 | push_warning_printf(thd_, |
290 | MRN_SEVERITY_WARNING, |
291 | ER_NOT_KEYFILE, |
292 | "mroonga: repair: " |
293 | "Failed to recover database: <%s>: <%s>" , |
294 | db_path, ctx->errbuf); |
295 | *succeeded = false; |
296 | } |
297 | |
298 | DBUG_VOID_RETURN; |
299 | } |
300 | } |
301 | |