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