1 /* -*- c-basic-offset: 2 -*- */
2 /*
3   Copyright(C) 2010 Tetsuro IKEDA
4   Copyright(C) 2010-2013 Kentoku SHIBA
5   Copyright(C) 2011-2015 Kouhei Sutou <kou@clear-code.com>
6 
7   This library is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Lesser General Public
9   License as published by the Free Software Foundation; either
10   version 2.1 of the License, or (at your option) any later version.
11 
12   This library is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   Lesser General Public License for more details.
16 
17   You should have received a copy of the GNU Lesser General Public
18   License along with this library; if not, write to the Free Software
19   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA
20 */
21 
22 #include <mrn_mysql.h>
23 
24 #include "mrn_database_manager.hpp"
25 #include "mrn_encoding.hpp"
26 #include "mrn_lock.hpp"
27 #include "mrn_path_mapper.hpp"
28 
29 #include <groonga/plugin.h>
30 
31 // for debug
32 #define MRN_CLASS_NAME "mrn::DatabaseManager"
33 
34 #ifdef WIN32
35 #  include <direct.h>
36 #  define MRN_MKDIR(pathname, mode) _mkdir((pathname))
37 #else
38 #  include <dirent.h>
39 #  include <unistd.h>
40 #  define MRN_MKDIR(pathname, mode) mkdir((pathname), (mode))
41 #endif
42 
43 extern "C" {
44   grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(grn_ctx *ctx);
45   grn_rc GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(grn_ctx *ctx);
46 }
47 
48 namespace mrn {
DatabaseManager(grn_ctx * ctx,mysql_mutex_t * mutex)49   DatabaseManager::DatabaseManager(grn_ctx *ctx, mysql_mutex_t *mutex)
50     : ctx_(ctx),
51       cache_(NULL),
52       mutex_(mutex) {
53   }
54 
~DatabaseManager(void)55   DatabaseManager::~DatabaseManager(void) {
56     if (cache_) {
57       void *db_address;
58       GRN_HASH_EACH(ctx_, cache_, id, NULL, 0, &db_address, {
59         Database *db;
60         memcpy(&db, db_address, sizeof(grn_obj *));
61         delete db;
62       });
63       grn_hash_close(ctx_, cache_);
64     }
65   }
66 
init(void)67   bool DatabaseManager::init(void) {
68     MRN_DBUG_ENTER_METHOD();
69     cache_ = grn_hash_create(ctx_,
70                              NULL,
71                              GRN_TABLE_MAX_KEY_SIZE,
72                              sizeof(grn_obj *),
73                              GRN_OBJ_KEY_VAR_SIZE);
74     if (!cache_) {
75       GRN_LOG(ctx_, GRN_LOG_ERROR,
76               "failed to initialize hash table for caching opened databases");
77       DBUG_RETURN(false);
78     }
79 
80     DBUG_RETURN(true);
81   }
82 
open(const char * path,Database ** db)83   int DatabaseManager::open(const char *path, Database **db) {
84     MRN_DBUG_ENTER_METHOD();
85 
86     int error = 0;
87     *db = NULL;
88 
89     mrn::PathMapper mapper(path);
90     mrn::Lock lock(mutex_);
91 
92     error = mrn::encoding::set(ctx_, system_charset_info);
93     if (error) {
94       DBUG_RETURN(error);
95     }
96 
97     grn_id id;
98     void *db_address;
99     id = grn_hash_get(ctx_, cache_,
100                       mapper.db_name(), strlen(mapper.db_name()),
101                       &db_address);
102     if (id == GRN_ID_NIL) {
103       grn_obj *grn_db;
104       struct stat db_stat;
105       if (stat(mapper.db_path(), &db_stat)) {
106         GRN_LOG(ctx_, GRN_LOG_INFO,
107                 "database not found. creating...: <%s>", mapper.db_path());
108         if (path[0] == FN_CURLIB &&
109             mrn_is_directory_separator(path[1])) {
110           ensure_database_directory();
111         }
112         grn_db = grn_db_create(ctx_, mapper.db_path(), NULL);
113         if (ctx_->rc) {
114           error = ER_CANT_CREATE_TABLE;
115           my_message(error, ctx_->errbuf, MYF(0));
116           DBUG_RETURN(error);
117         }
118       } else {
119         grn_db = grn_db_open(ctx_, mapper.db_path());
120         if (ctx_->rc) {
121           error = ER_CANT_OPEN_FILE;
122           my_message(error, ctx_->errbuf, MYF(0));
123           DBUG_RETURN(error);
124         }
125       }
126       *db = new Database(ctx_, grn_db);
127       grn_hash_add(ctx_, cache_,
128                    mapper.db_name(), strlen(mapper.db_name()),
129                    &db_address, NULL);
130       memcpy(db_address, db, sizeof(Database *));
131       error = ensure_normalizers_registered((*db)->get());
132       if (!error) {
133         if ((*db)->is_broken()) {
134           error = ER_CANT_OPEN_FILE;
135           char error_message[MRN_MESSAGE_BUFFER_SIZE];
136           snprintf(error_message, MRN_MESSAGE_BUFFER_SIZE,
137                    "mroonga: database: open: "
138                    "The database maybe broken. "
139                    "We recommend you to recreate the database. "
140                    "If the database isn't broken, "
141                    "you can remove this error by running "
142                    "'groonga %s table_remove mroonga_operations' "
143                    "on server. But the latter isn't recommended.",
144                    mapper.db_path());
145           my_message(error, error_message, MYF(0));
146         }
147       }
148     } else {
149       memcpy(db, db_address, sizeof(Database *));
150       grn_ctx_use(ctx_, (*db)->get());
151     }
152 
153     DBUG_RETURN(error);
154   }
155 
close(const char * path)156   void DatabaseManager::close(const char *path) {
157     MRN_DBUG_ENTER_METHOD();
158 
159     mrn::PathMapper mapper(path);
160     mrn::Lock lock(mutex_);
161 
162     grn_id id;
163     void *db_address;
164     id = grn_hash_get(ctx_, cache_,
165                       mapper.db_name(), strlen(mapper.db_name()),
166                       &db_address);
167     if (id == GRN_ID_NIL) {
168       DBUG_VOID_RETURN;
169     }
170 
171     Database *db = NULL;
172     memcpy(&db, db_address, sizeof(Database *));
173     grn_ctx_use(ctx_, db->get());
174     if (db) {
175       delete db;
176     }
177 
178     grn_hash_delete_by_id(ctx_, cache_, id, NULL);
179 
180     DBUG_VOID_RETURN;
181   }
182 
drop(const char * path)183   bool DatabaseManager::drop(const char *path) {
184     MRN_DBUG_ENTER_METHOD();
185 
186     mrn::PathMapper mapper(path);
187     mrn::Lock lock(mutex_);
188 
189     grn_id id;
190     void *db_address;
191     id = grn_hash_get(ctx_, cache_,
192                       mapper.db_name(), strlen(mapper.db_name()),
193                       &db_address);
194 
195     Database *db = NULL;
196     if (id == GRN_ID_NIL) {
197       struct stat dummy;
198       if (stat(mapper.db_path(), &dummy) == 0) {
199         grn_obj *grn_db = grn_db_open(ctx_, mapper.db_path());
200         db = new Database(ctx_, grn_db);
201       }
202     } else {
203       memcpy(&db, db_address, sizeof(Database *));
204       grn_ctx_use(ctx_, db->get());
205     }
206 
207     if (!db) {
208       DBUG_RETURN(false);
209     }
210 
211     if (db->remove() == GRN_SUCCESS) {
212       if (id != GRN_ID_NIL) {
213         grn_hash_delete_by_id(ctx_, cache_, id, NULL);
214       }
215       delete db;
216       DBUG_RETURN(true);
217     } else {
218       GRN_LOG(ctx_, GRN_LOG_ERROR,
219               "failed to drop database: <%s>: <%s>",
220               mapper.db_path(), ctx_->errbuf);
221       if (id == GRN_ID_NIL) {
222         delete db;
223       }
224       DBUG_RETURN(false);
225     }
226   }
227 
clear(void)228   int DatabaseManager::clear(void) {
229     MRN_DBUG_ENTER_METHOD();
230 
231     int error = 0;
232 
233     mrn::Lock lock(mutex_);
234 
235     grn_hash_cursor *cursor;
236     cursor = grn_hash_cursor_open(ctx_, cache_,
237                                   NULL, 0, NULL, 0,
238                                   0, -1, 0);
239     if (ctx_->rc) {
240       my_message(ER_ERROR_ON_READ, ctx_->errbuf, MYF(0));
241       DBUG_RETURN(ER_ERROR_ON_READ);
242     }
243 
244     while (grn_hash_cursor_next(ctx_, cursor) != GRN_ID_NIL) {
245       if (ctx_->rc) {
246         error = ER_ERROR_ON_READ;
247         my_message(error, ctx_->errbuf, MYF(0));
248         break;
249       }
250       void *db_address;
251       Database *db;
252       grn_hash_cursor_get_value(ctx_, cursor, &db_address);
253       memcpy(&db, db_address, sizeof(Database *));
254       grn_ctx_use(ctx_, db->get());
255       grn_rc rc = grn_hash_cursor_delete(ctx_, cursor, NULL);
256       if (rc) {
257         error = ER_ERROR_ON_READ;
258         my_message(error, ctx_->errbuf, MYF(0));
259         break;
260       }
261       delete db;
262     }
263     grn_hash_cursor_close(ctx_, cursor);
264 
265     DBUG_RETURN(error);
266   }
267 
error_message()268   const char *DatabaseManager::error_message() {
269     MRN_DBUG_ENTER_METHOD();
270     DBUG_RETURN(ctx_->errbuf);
271   }
272 
mkdir_p(const char * directory)273   void DatabaseManager::mkdir_p(const char *directory) {
274     MRN_DBUG_ENTER_METHOD();
275 
276     int i = 0;
277     char sub_directory[MRN_MAX_PATH_SIZE];
278     sub_directory[0] = '\0';
279     while (true) {
280       if (mrn_is_directory_separator(directory[i]) ||
281           directory[i] == '\0') {
282         sub_directory[i] = '\0';
283         struct stat directory_status;
284         if (stat(sub_directory, &directory_status) != 0) {
285           DBUG_PRINT("info", ("mroonga: creating directory: <%s>", sub_directory));
286           GRN_LOG(ctx_, GRN_LOG_INFO, "creating directory: <%s>", sub_directory);
287           if (MRN_MKDIR(sub_directory, S_IRWXU) == 0) {
288             DBUG_PRINT("info",
289                        ("mroonga: created directory: <%s>", sub_directory));
290             GRN_LOG(ctx_, GRN_LOG_INFO, "created directory: <%s>", sub_directory);
291           } else {
292             DBUG_PRINT("error",
293                        ("mroonga: failed to create directory: <%s>: <%s>",
294                         sub_directory, strerror(errno)));
295             GRN_LOG(ctx_, GRN_LOG_ERROR,
296                     "failed to create directory: <%s>: <%s>",
297                     sub_directory, strerror(errno));
298             DBUG_VOID_RETURN;
299           }
300         }
301       }
302 
303       if (directory[i] == '\0') {
304         break;
305       }
306 
307       sub_directory[i] = directory[i];
308       ++i;
309     }
310 
311     DBUG_VOID_RETURN;
312   }
313 
ensure_database_directory(void)314   void DatabaseManager::ensure_database_directory(void) {
315     MRN_DBUG_ENTER_METHOD();
316 
317     const char *path_prefix = mrn::PathMapper::default_path_prefix;
318     if (!path_prefix)
319       DBUG_VOID_RETURN;
320 
321     const char *last_path_separator;
322     last_path_separator = strrchr(path_prefix, FN_LIBCHAR);
323 #ifdef FN_LIBCHAR2
324     if (!last_path_separator)
325       last_path_separator = strrchr(path_prefix, FN_LIBCHAR2);
326 #endif
327     if (!last_path_separator)
328       DBUG_VOID_RETURN;
329     if (path_prefix == last_path_separator)
330       DBUG_VOID_RETURN;
331 
332     char database_directory[MRN_MAX_PATH_SIZE];
333     size_t database_directory_length = last_path_separator - path_prefix;
334     strncpy(database_directory, path_prefix, database_directory_length);
335     database_directory[database_directory_length] = '\0';
336     mkdir_p(database_directory);
337 
338     DBUG_VOID_RETURN;
339   }
340 
ensure_normalizers_registered(grn_obj * db)341   int DatabaseManager::ensure_normalizers_registered(grn_obj *db) {
342     MRN_DBUG_ENTER_METHOD();
343 
344     int error = 0;
345 #ifdef WITH_GROONGA_NORMALIZER_MYSQL
346     {
347 #  ifdef MRN_GROONGA_NORMALIZER_MYSQL_EMBEDDED
348       GRN_PLUGIN_IMPL_NAME_TAGGED(init, normalizers_mysql)(ctx_);
349       GRN_PLUGIN_IMPL_NAME_TAGGED(register, normalizers_mysql)(ctx_);
350 #  else
351       grn_obj *mysql_normalizer;
352       mysql_normalizer = grn_ctx_get(ctx_, "NormalizerMySQLGeneralCI", -1);
353       if (mysql_normalizer) {
354         grn_obj_unlink(ctx_, mysql_normalizer);
355       } else {
356         grn_plugin_register(ctx_, GROONGA_NORMALIZER_MYSQL_PLUGIN_NAME);
357       }
358 #  endif
359     }
360 #endif
361 
362     DBUG_RETURN(error);
363   }
364 }
365