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