-Merge branch 'master' of ssh://gnunet.org/gnunet into gsoc2018/rest_api
[oweals/gnunet.git] / src / mysql / mysql.c
1
2 /*
3      This file is part of GNUnet
4      Copyright (C) 2012 GNUnet e.V.
5
6      GNUnet is free software: you can redistribute it and/or modify it
7      under the terms of the GNU Affero General Public License as published
8      by the Free Software Foundation, either version 3 of the License,
9      or (at your option) any later version.
10
11      GNUnet is distributed in the hope that it will be useful, but
12      WITHOUT ANY WARRANTY; without even the implied warranty of
13      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14      Affero General Public License for more details.
15     
16      You should have received a copy of the GNU Affero General Public License
17      along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 /**
20  * @file mysql/mysql.c
21  * @brief library to help with access to a MySQL database
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include <mysql/mysql.h>
26 #include "gnunet_mysql_lib.h"
27
28 /**
29  * Maximum number of supported parameters for a prepared
30  * statement.  Increase if needed.
31  */
32 #define MAX_PARAM 16
33
34
35 /**
36  * Die with an error message that indicates
37  * a failure of the command 'cmd' with the message given
38  * by strerror(errno).
39  */
40 #define DIE_MYSQL(cmd, dbh) do { GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); GNUNET_assert (0); } while(0);
41
42 /**
43  * Log an error message at log-level 'level' that indicates
44  * a failure of the command 'cmd' on file 'filename'
45  * with the message given by strerror(errno).
46  */
47 #define LOG_MYSQL(level, cmd, dbh) do { GNUNET_log_from (level, "mysql", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);
48
49
50 /**
51  * Mysql context.
52  */
53 struct GNUNET_MYSQL_Context
54 {
55
56   /**
57    * Our configuration.
58    */
59   const struct GNUNET_CONFIGURATION_Handle *cfg;
60
61   /**
62    * Our section.
63    */
64   const char *section;
65
66   /**
67    * Handle to the mysql database.
68    */
69   MYSQL *dbf;
70
71   /**
72    * Head of list of our prepared statements.
73    */
74   struct GNUNET_MYSQL_StatementHandle *shead;
75
76   /**
77    * Tail of list of our prepared statements.
78    */
79   struct GNUNET_MYSQL_StatementHandle *stail;
80
81   /**
82    * Filename of "my.cnf" (msyql configuration).
83    */
84   char *cnffile;
85
86 };
87
88
89 /**
90  * Handle for a prepared statement.
91  */
92 struct GNUNET_MYSQL_StatementHandle
93 {
94
95   /**
96    * Kept in a DLL.
97    */
98   struct GNUNET_MYSQL_StatementHandle *next;
99
100   /**
101    * Kept in a DLL.
102    */
103   struct GNUNET_MYSQL_StatementHandle *prev;
104
105   /**
106    * Mysql Context the statement handle belongs to.
107    */
108   struct GNUNET_MYSQL_Context *mc;
109
110   /**
111    * Original query string.
112    */
113   char *query;
114
115   /**
116    * Handle to MySQL prepared statement.
117    */
118   MYSQL_STMT *statement;
119
120   /**
121    * Is the MySQL prepared statement valid, or do we need to re-initialize it?
122    */
123   int valid;
124
125 };
126
127
128 /**
129  * Obtain the location of ".my.cnf".
130  *
131  * @param cfg our configuration
132  * @param section the section
133  * @return NULL on error
134  */
135 static char *
136 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
137                  const char *section)
138 {
139   char *cnffile;
140   char *home_dir;
141   struct stat st;
142
143 #ifndef WINDOWS
144   struct passwd *pw;
145 #endif
146   int configured;
147
148 #ifndef WINDOWS
149   pw = getpwuid (getuid ());
150   if (!pw)
151   {
152     GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_ERROR, "mysql", "getpwuid");
153     return NULL;
154   }
155   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (cfg, section, "CONFIG"))
156   {
157     GNUNET_assert (GNUNET_OK ==
158                    GNUNET_CONFIGURATION_get_value_filename (cfg, section,
159                                                             "CONFIG",
160                                                             &cnffile));
161     configured = GNUNET_YES;
162   }
163   else
164   {
165     home_dir = GNUNET_strdup (pw->pw_dir);
166     GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
167     GNUNET_free (home_dir);
168     configured = GNUNET_NO;
169   }
170 #else
171   home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
172   plibc_conv_to_win_path ("~/", home_dir);
173   GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
174   GNUNET_free (home_dir);
175   configured = GNUNET_NO;
176 #endif
177   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
178                    _("Trying to use file `%s' for MySQL configuration.\n"),
179                    cnffile);
180   if ((0 != STAT (cnffile, &st)) || (0 != ACCESS (cnffile, R_OK)) ||
181       (!S_ISREG (st.st_mode)))
182   {
183     if (configured == GNUNET_YES)
184       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
185                        _("Could not access file `%s': %s\n"), cnffile,
186                        STRERROR (errno));
187     GNUNET_free (cnffile);
188     return NULL;
189   }
190   return cnffile;
191 }
192
193
194 /**
195  * Open the connection with the database (and initialize
196  * our default options).
197  *
198  * @param mc database context to initialze
199  * @return #GNUNET_OK on success
200  */
201 static int
202 iopen (struct GNUNET_MYSQL_Context *mc)
203 {
204   char *mysql_dbname;
205   char *mysql_server;
206   char *mysql_user;
207   char *mysql_password;
208   unsigned long long mysql_port;
209   my_bool reconnect;
210   unsigned int timeout;
211
212   mc->dbf = mysql_init (NULL);
213   if (mc->dbf == NULL)
214     return GNUNET_SYSERR;
215   if (mc->cnffile != NULL)
216     mysql_options (mc->dbf, MYSQL_READ_DEFAULT_FILE, mc->cnffile);
217   mysql_options (mc->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
218   reconnect = 0;
219   mysql_options (mc->dbf, MYSQL_OPT_RECONNECT, &reconnect);
220   mysql_options (mc->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
221   mysql_options (mc->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
222   timeout = 60;                 /* in seconds */
223   mysql_options (mc->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
224   mysql_options (mc->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
225   mysql_dbname = NULL;
226   if (GNUNET_YES ==
227       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "DATABASE"))
228     GNUNET_assert (GNUNET_OK ==
229                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
230                                                           "DATABASE",
231                                                           &mysql_dbname));
232   else
233     mysql_dbname = GNUNET_strdup ("gnunet");
234   mysql_user = NULL;
235   if (GNUNET_YES ==
236       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "USER"))
237   {
238     GNUNET_assert (GNUNET_OK ==
239                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
240                                                           "USER", &mysql_user));
241   }
242   mysql_password = NULL;
243   if (GNUNET_YES ==
244       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PASSWORD"))
245   {
246     GNUNET_assert (GNUNET_OK ==
247                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
248                                                           "PASSWORD",
249                                                           &mysql_password));
250   }
251   mysql_server = NULL;
252   if (GNUNET_YES ==
253       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "HOST"))
254   {
255     GNUNET_assert (GNUNET_OK ==
256                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
257                                                           "HOST",
258                                                           &mysql_server));
259   }
260   mysql_port = 0;
261   if (GNUNET_YES ==
262       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PORT"))
263   {
264     GNUNET_assert (GNUNET_OK ==
265                    GNUNET_CONFIGURATION_get_value_number (mc->cfg, mc->section,
266                                                           "PORT", &mysql_port));
267   }
268
269   GNUNET_assert (mysql_dbname != NULL);
270   mysql_real_connect (mc->dbf, mysql_server, mysql_user, mysql_password,
271                       mysql_dbname, (unsigned int) mysql_port, NULL,
272                       CLIENT_IGNORE_SIGPIPE);
273   GNUNET_free_non_null (mysql_server);
274   GNUNET_free_non_null (mysql_user);
275   GNUNET_free_non_null (mysql_password);
276   GNUNET_free (mysql_dbname);
277   if (mysql_error (mc->dbf)[0])
278   {
279     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", mc);
280     return GNUNET_SYSERR;
281   }
282   return GNUNET_OK;
283 }
284
285
286 /**
287  * Create a mysql context.
288  *
289  * @param cfg configuration
290  * @param section configuration section to use to get MySQL configuration options
291  * @return the mysql context
292  */
293 struct GNUNET_MYSQL_Context *
294 GNUNET_MYSQL_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
295                              const char *section)
296 {
297   struct GNUNET_MYSQL_Context *mc;
298
299   mc = GNUNET_new (struct GNUNET_MYSQL_Context);
300   mc->cfg = cfg;
301   mc->section = section;
302   mc->cnffile = get_my_cnf_path (cfg,
303                                  section);
304
305   return mc;
306 }
307
308
309 /**
310  * Close database connection and all prepared statements (we got a DB
311  * error).
312  *
313  * @param mc mysql context
314  */
315 void
316 GNUNET_MYSQL_statements_invalidate (struct GNUNET_MYSQL_Context *mc)
317 {
318   struct GNUNET_MYSQL_StatementHandle *sh;
319
320   for (sh = mc->shead; NULL != sh; sh = sh->next)
321   {
322     if (GNUNET_YES == sh->valid)
323     {
324       mysql_stmt_close (sh->statement);
325       sh->valid = GNUNET_NO;
326     }
327     sh->statement = NULL;
328   }
329   if (NULL != mc->dbf)
330   {
331     mysql_close (mc->dbf);
332     mc->dbf = NULL;
333   }
334 }
335
336
337 /**
338  * Destroy a mysql context.  Also frees all associated prepared statements.
339  *
340  * @param mc context to destroy
341  */
342 void
343 GNUNET_MYSQL_context_destroy (struct GNUNET_MYSQL_Context *mc)
344 {
345   struct GNUNET_MYSQL_StatementHandle *sh;
346
347   GNUNET_MYSQL_statements_invalidate (mc);
348   while (NULL != (sh = mc->shead))
349   {
350     GNUNET_CONTAINER_DLL_remove (mc->shead, mc->stail, sh);
351     GNUNET_free (sh->query);
352     GNUNET_free (sh);
353   }
354   GNUNET_free (mc);
355   mysql_library_end ();
356 }
357
358
359 /**
360  * Prepare a statement.  Prepared statements are automatically discarded
361  * when the MySQL context is destroyed.
362  *
363  * @param mc mysql context
364  * @param query query text
365  * @return prepared statement, NULL on error
366  */
367 struct GNUNET_MYSQL_StatementHandle *
368 GNUNET_MYSQL_statement_prepare (struct GNUNET_MYSQL_Context *mc,
369                                 const char *query)
370 {
371   struct GNUNET_MYSQL_StatementHandle *sh;
372
373   sh = GNUNET_new (struct GNUNET_MYSQL_StatementHandle);
374   sh->mc = mc;
375   sh->query = GNUNET_strdup (query);
376   GNUNET_CONTAINER_DLL_insert (mc->shead, mc->stail, sh);
377   return sh;
378 }
379
380
381 /**
382  * Run a SQL statement.
383  *
384  * @param mc mysql context
385  * @param sql SQL statement to run
386  * @return #GNUNET_OK on success
387  *         #GNUNET_SYSERR if there was a problem
388  */
389 int
390 GNUNET_MYSQL_statement_run (struct GNUNET_MYSQL_Context *mc,
391                             const char *sql)
392 {
393   if ( (NULL == mc->dbf) &&
394        (GNUNET_OK != iopen (mc)) )
395     return GNUNET_SYSERR;
396   mysql_query (mc->dbf, sql);
397   if (mysql_error (mc->dbf)[0])
398   {
399     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR,
400                "mysql_query",
401                mc);
402     GNUNET_MYSQL_statements_invalidate (mc);
403     return GNUNET_SYSERR;
404   }
405   return GNUNET_OK;
406 }
407
408
409 /**
410  * Prepare a statement for running.
411  *
412  * @param mc mysql context
413  * @param sh statement handle to prepare
414  * @return #GNUNET_OK on success
415  */
416 static int
417 prepare_statement (struct GNUNET_MYSQL_StatementHandle *sh)
418 {
419   struct GNUNET_MYSQL_Context *mc = sh->mc;
420
421   if (GNUNET_YES == sh->valid)
422     return GNUNET_OK;
423   if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
424     return GNUNET_SYSERR;
425   sh->statement = mysql_stmt_init (mc->dbf);
426   if (NULL == sh->statement)
427   {
428     GNUNET_MYSQL_statements_invalidate (mc);
429     return GNUNET_SYSERR;
430   }
431   if (0 != mysql_stmt_prepare (sh->statement, sh->query, strlen (sh->query)))
432   {
433     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
434                      "prepare_statement: %s\n", sh->query);
435     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", mc);
436     mysql_stmt_close (sh->statement);
437     sh->statement = NULL;
438     GNUNET_MYSQL_statements_invalidate (mc);
439     return GNUNET_SYSERR;
440   }
441   sh->valid = GNUNET_YES;
442   return GNUNET_OK;
443 }
444
445
446 /**
447  * Get internal handle for a prepared statement.  This function should rarely
448  * be used, and if, with caution!  On failures during the interaction with
449  * the handle, you must call 'GNUNET_MYSQL_statements_invalidate'!
450  *
451  * @param sh prepared statement to introspect
452  * @return MySQL statement handle, NULL on error
453  */
454 MYSQL_STMT *
455 GNUNET_MYSQL_statement_get_stmt (struct GNUNET_MYSQL_StatementHandle *sh)
456 {
457   (void) prepare_statement (sh);
458   return sh->statement;
459 }
460
461
462 /* end of mysql.c */