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
38namespace 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