-make debug output more readable
[oweals/gnunet.git] / src / mysql / mysql.c
1 /*
2      This file is part of GNUnet
3      (C) 2012 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, 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_abort(); } 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    * Original query string.
108    */
109   char *query;
110
111   /**
112    * Handle to MySQL prepared statement.
113    */
114   MYSQL_STMT *statement;
115
116   /**
117    * Is the MySQL prepared statement valid, or do we need to re-initialize it?
118    */
119   int valid;
120
121 };
122
123
124 /**
125  * Obtain the location of ".my.cnf".
126  *
127  * @param cfg our configuration
128  * @return NULL on error
129  */
130 static char *
131 get_my_cnf_path (const struct GNUNET_CONFIGURATION_Handle *cfg,
132                  const char *section)
133 {
134   char *cnffile;
135   char *home_dir;
136   struct stat st;
137
138 #ifndef WINDOWS
139   struct passwd *pw;
140 #endif
141   int configured;
142
143 #ifndef WINDOWS
144   pw = getpwuid (getuid ());
145   if (!pw)
146   {
147     GNUNET_log_from_strerror (GNUNET_ERROR_TYPE_ERROR, "mysql", "getpwuid");
148     return NULL;
149   }
150   if (GNUNET_YES == GNUNET_CONFIGURATION_have_value (cfg, section, "CONFIG"))
151   {
152     GNUNET_assert (GNUNET_OK ==
153                    GNUNET_CONFIGURATION_get_value_filename (cfg, section,
154                                                             "CONFIG",
155                                                             &cnffile));
156     configured = GNUNET_YES;
157   }
158   else
159   {
160     home_dir = GNUNET_strdup (pw->pw_dir);
161     GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
162     GNUNET_free (home_dir);
163     configured = GNUNET_NO;
164   }
165 #else
166   home_dir = (char *) GNUNET_malloc (_MAX_PATH + 1);
167   plibc_conv_to_win_path ("~/", home_dir);
168   GNUNET_asprintf (&cnffile, "%s/.my.cnf", home_dir);
169   GNUNET_free (home_dir);
170   configured = GNUNET_NO;
171 #endif
172   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, "mysql",
173                    _("Trying to use file `%s' for MySQL configuration.\n"),
174                    cnffile);
175   if ((0 != STAT (cnffile, &st)) || (0 != ACCESS (cnffile, R_OK)) ||
176       (!S_ISREG (st.st_mode)))
177   {
178     if (configured == GNUNET_YES)
179       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
180                        _("Could not access file `%s': %s\n"), cnffile,
181                        STRERROR (errno));
182     GNUNET_free (cnffile);
183     return NULL;
184   }
185   return cnffile;
186 }
187
188
189 /**
190  * Open the connection with the database (and initialize
191  * our default options).
192  *
193  * @param mc database context to initialze
194  * @return GNUNET_OK on success
195  */
196 static int
197 iopen (struct GNUNET_MYSQL_Context *mc)
198 {
199   char *mysql_dbname;
200   char *mysql_server;
201   char *mysql_user;
202   char *mysql_password;
203   unsigned long long mysql_port;
204   my_bool reconnect;
205   unsigned int timeout;
206
207   mc->dbf = mysql_init (NULL);
208   if (mc->dbf == NULL)
209     return GNUNET_SYSERR;
210   if (mc->cnffile != NULL)
211     mysql_options (mc->dbf, MYSQL_READ_DEFAULT_FILE, mc->cnffile);
212   mysql_options (mc->dbf, MYSQL_READ_DEFAULT_GROUP, "client");
213   reconnect = 0;
214   mysql_options (mc->dbf, MYSQL_OPT_RECONNECT, &reconnect);
215   mysql_options (mc->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout);
216   mysql_options (mc->dbf, MYSQL_SET_CHARSET_NAME, "UTF8");
217   timeout = 60;                 /* in seconds */
218   mysql_options (mc->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout);
219   mysql_options (mc->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout);
220   mysql_dbname = NULL;
221   if (GNUNET_YES ==
222       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "DATABASE"))
223     GNUNET_assert (GNUNET_OK ==
224                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
225                                                           "DATABASE",
226                                                           &mysql_dbname));
227   else
228     mysql_dbname = GNUNET_strdup ("gnunet");
229   mysql_user = NULL;
230   if (GNUNET_YES ==
231       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "USER"))
232   {
233     GNUNET_assert (GNUNET_OK ==
234                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
235                                                           "USER", &mysql_user));
236   }
237   mysql_password = NULL;
238   if (GNUNET_YES ==
239       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PASSWORD"))
240   {
241     GNUNET_assert (GNUNET_OK ==
242                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
243                                                           "PASSWORD",
244                                                           &mysql_password));
245   }
246   mysql_server = NULL;
247   if (GNUNET_YES ==
248       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "HOST"))
249   {
250     GNUNET_assert (GNUNET_OK ==
251                    GNUNET_CONFIGURATION_get_value_string (mc->cfg, mc->section,
252                                                           "HOST",
253                                                           &mysql_server));
254   }
255   mysql_port = 0;
256   if (GNUNET_YES ==
257       GNUNET_CONFIGURATION_have_value (mc->cfg, mc->section, "PORT"))
258   {
259     GNUNET_assert (GNUNET_OK ==
260                    GNUNET_CONFIGURATION_get_value_number (mc->cfg, mc->section,
261                                                           "PORT", &mysql_port));
262   }
263
264   GNUNET_assert (mysql_dbname != NULL);
265   mysql_real_connect (mc->dbf, mysql_server, mysql_user, mysql_password,
266                       mysql_dbname, (unsigned int) mysql_port, NULL,
267                       CLIENT_IGNORE_SIGPIPE);
268   GNUNET_free_non_null (mysql_server);
269   GNUNET_free_non_null (mysql_user);
270   GNUNET_free_non_null (mysql_password);
271   GNUNET_free (mysql_dbname);
272   if (mysql_error (mc->dbf)[0])
273   {
274     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_real_connect", mc);
275     return GNUNET_SYSERR;
276   }
277   return GNUNET_OK;
278 }
279
280
281 /**
282  * Create a mysql context.
283  *
284  * @param cfg configuration
285  * @param section configuration section to use to get MySQL configuration options
286  * @return the mysql context
287  */
288 struct GNUNET_MYSQL_Context *
289 GNUNET_MYSQL_context_create (const struct GNUNET_CONFIGURATION_Handle *cfg,
290                              const char *section)
291 {
292   struct GNUNET_MYSQL_Context *mc;
293
294   mc = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_Context));
295   mc->cfg = cfg;
296   mc->section = section;
297   mc->cnffile = get_my_cnf_path (cfg, section);
298
299   return mc;
300 }
301
302
303 /**
304  * Close database connection and all prepared statements (we got a DB
305  * error).
306  *
307  * @param mc mysql context
308  */
309 void
310 GNUNET_MYSQL_statements_invalidate (struct GNUNET_MYSQL_Context *mc)
311 {
312   struct GNUNET_MYSQL_StatementHandle *sh;
313
314   for (sh = mc->shead; NULL != sh; sh = sh->next)
315   {
316     if (GNUNET_YES == sh->valid)
317     {
318       mysql_stmt_close (sh->statement);
319       sh->valid = GNUNET_NO;
320     }
321     sh->statement = NULL;
322   }
323   if (NULL != mc->dbf)
324   {
325     mysql_close (mc->dbf);
326     mc->dbf = NULL;
327   }
328 }
329
330
331 /**
332  * Destroy a mysql context.  Also frees all associated prepared statements.
333  *
334  * @param mc context to destroy
335  */
336 void
337 GNUNET_MYSQL_context_destroy (struct GNUNET_MYSQL_Context *mc)
338 {
339   struct GNUNET_MYSQL_StatementHandle *sh;
340
341   GNUNET_MYSQL_statements_invalidate (mc);
342   while (NULL != (sh = mc->shead))
343   {
344     GNUNET_CONTAINER_DLL_remove (mc->shead, mc->stail, sh);
345     GNUNET_free (sh->query);
346     GNUNET_free (sh);
347   }
348   GNUNET_free (mc);
349   mysql_library_end ();
350 }
351
352
353 /**
354  * Prepare a statement.  Prepared statements are automatically discarded
355  * when the MySQL context is destroyed.
356  *
357  * @param mc mysql context
358  * @param query query text
359  * @return prepared statement, NULL on error
360  */
361 struct GNUNET_MYSQL_StatementHandle *
362 GNUNET_MYSQL_statement_prepare (struct GNUNET_MYSQL_Context *mc,
363                                 const char *query)
364 {
365   struct GNUNET_MYSQL_StatementHandle *sh;
366
367   sh = GNUNET_malloc (sizeof (struct GNUNET_MYSQL_StatementHandle));
368   sh->query = GNUNET_strdup (query);
369   GNUNET_CONTAINER_DLL_insert (mc->shead, mc->stail, sh);
370   return sh;
371 }
372
373
374 /**
375  * Run a SQL statement.
376  *
377  * @param mc mysql context
378  * @param sql SQL statement to run
379  * @return GNUNET_OK on success
380  *         GNUNET_SYSERR if there was a problem
381  */
382 int
383 GNUNET_MYSQL_statement_run (struct GNUNET_MYSQL_Context *mc, const char *sql)
384 {
385   if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
386     return GNUNET_SYSERR;
387   mysql_query (mc->dbf, sql);
388   if (mysql_error (mc->dbf)[0])
389   {
390     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_query", mc);
391     GNUNET_MYSQL_statements_invalidate (mc);
392     return GNUNET_SYSERR;
393   }
394   return GNUNET_OK;
395 }
396
397
398 /**
399  * Prepare a statement for running.
400  *
401  * @param mc mysql context
402  * @param sh statement handle to prepare
403  * @return GNUNET_OK on success
404  */
405 static int
406 prepare_statement (struct GNUNET_MYSQL_Context *mc,
407                    struct GNUNET_MYSQL_StatementHandle *sh)
408 {
409   if (GNUNET_YES == sh->valid)
410     return GNUNET_OK;
411   if ((NULL == mc->dbf) && (GNUNET_OK != iopen (mc)))
412     return GNUNET_SYSERR;
413   sh->statement = mysql_stmt_init (mc->dbf);
414   if (NULL == sh->statement)
415   {
416     GNUNET_MYSQL_statements_invalidate (mc);
417     return GNUNET_SYSERR;
418   }
419   if (0 != mysql_stmt_prepare (sh->statement, sh->query, strlen (sh->query)))
420   {
421     LOG_MYSQL (GNUNET_ERROR_TYPE_ERROR, "mysql_stmt_prepare", mc);
422     mysql_stmt_close (sh->statement);
423     sh->statement = NULL;
424     GNUNET_MYSQL_statements_invalidate (mc);
425     return GNUNET_SYSERR;
426   }
427   sh->valid = GNUNET_YES;
428   return GNUNET_OK;
429 }
430
431
432 /**
433  * Get internal handle for a prepared statement.  This function should rarely
434  * be used, and if, with caution!  On failures during the interaction with
435  * the handle, you must call 'GNUNET_MYSQL_statements_invalidate'!
436  *
437  * @param mc mysql context
438  * @param sh prepared statement to introspect
439  * @return MySQL statement handle, NULL on error
440  */
441 MYSQL_STMT *
442 GNUNET_MYSQL_statement_get_stmt (struct GNUNET_MYSQL_Context * mc,
443                                  struct GNUNET_MYSQL_StatementHandle * sh)
444 {
445   (void) prepare_statement (mc, sh);
446   return sh->statement;
447 }
448
449
450 /**
451  * Bind the parameters for the given MySQL statement
452  * and run it.
453  *
454  * @param mc mysql context
455  * @param sh statement to bind and run
456  * @param ap arguments for the binding
457  * @return GNUNET_SYSERR on error, GNUNET_OK on success
458  */
459 static int
460 init_params (struct GNUNET_MYSQL_Context *mc,
461              struct GNUNET_MYSQL_StatementHandle *sh, va_list ap)
462 {
463   MYSQL_BIND qbind[MAX_PARAM];
464   unsigned int pc;
465   unsigned int off;
466   enum enum_field_types ft;
467
468   pc = mysql_stmt_param_count (sh->statement);
469   if (pc > MAX_PARAM)
470   {
471     /* increase internal constant! */
472     GNUNET_break (0);
473     return GNUNET_SYSERR;
474   }
475   memset (qbind, 0, sizeof (qbind));
476   off = 0;
477   ft = 0;
478   while ((pc > 0) && (-1 != (int) (ft = va_arg (ap, enum enum_field_types))))
479   {
480     qbind[off].buffer_type = ft;
481     switch (ft)
482     {
483     case MYSQL_TYPE_FLOAT:
484       qbind[off].buffer = va_arg (ap, float *);
485
486       break;
487     case MYSQL_TYPE_LONGLONG:
488       qbind[off].buffer = va_arg (ap, unsigned long long *);
489       qbind[off].is_unsigned = va_arg (ap, int);
490
491       break;
492     case MYSQL_TYPE_LONG:
493       qbind[off].buffer = va_arg (ap, unsigned int *);
494       qbind[off].is_unsigned = va_arg (ap, int);
495
496       break;
497     case MYSQL_TYPE_VAR_STRING:
498     case MYSQL_TYPE_STRING:
499     case MYSQL_TYPE_BLOB:
500       qbind[off].buffer = va_arg (ap, void *);
501       qbind[off].buffer_length = va_arg (ap, unsigned long);
502       qbind[off].length = va_arg (ap, unsigned long *);
503
504       break;
505     default:
506       /* unsupported type */
507       GNUNET_break (0);
508       return GNUNET_SYSERR;
509     }
510     pc--;
511     off++;
512   }
513   if (!((pc == 0) && (-1 != (int) ft) && (va_arg (ap, int) == -1)))
514   {
515     GNUNET_break (0);
516     return GNUNET_SYSERR;
517   }
518   if (mysql_stmt_bind_param (sh->statement, qbind))
519   {
520     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
521                      _("`%s' failed at %s:%d with error: %s\n"),
522                      "mysql_stmt_bind_param", __FILE__, __LINE__,
523                      mysql_stmt_error (sh->statement));
524     GNUNET_MYSQL_statements_invalidate (mc);
525     return GNUNET_SYSERR;
526   }
527   if (mysql_stmt_execute (sh->statement))
528   {
529     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
530                      _("`%s' failed at %s:%d with error: %s\n"),
531                      "mysql_stmt_execute", __FILE__, __LINE__,
532                      mysql_stmt_error (sh->statement));
533     GNUNET_MYSQL_statements_invalidate (mc);
534     return GNUNET_SYSERR;
535   }
536   return GNUNET_OK;
537 }
538
539
540
541 /**
542  * Run a prepared SELECT statement.
543  *
544  * @param mc mysql context
545  * @param s statement to run
546  * @param result_size number of elements in results array
547  * @param results pointer to already initialized MYSQL_BIND
548  *        array (of sufficient size) for passing results
549  * @param processor function to call on each result
550  * @param processor_cls extra argument to processor
551  * @param ap pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
552  *        values (size + buffer-reference for pointers); terminated
553  *        with "-1"
554  * @return GNUNET_SYSERR on error, otherwise
555  *         the number of successfully affected (or queried) rows
556  */
557 int
558 GNUNET_MYSQL_statement_run_prepared_select_va (struct GNUNET_MYSQL_Context *mc,
559                                                struct
560                                                GNUNET_MYSQL_StatementHandle *s,
561                                                unsigned int result_size,
562                                                MYSQL_BIND * results,
563                                                GNUNET_MYSQL_DataProcessor
564                                                processor, void *processor_cls,
565                                                va_list ap)
566 {
567   int ret;
568   unsigned int rsize;
569   int total;
570
571   if (GNUNET_OK != prepare_statement (mc, s))
572   {
573     GNUNET_break (0);
574     return GNUNET_SYSERR;
575   }
576   if (GNUNET_OK != init_params (mc, s, ap))
577   {
578     GNUNET_break (0);
579     return GNUNET_SYSERR;
580   }
581   rsize = mysql_stmt_field_count (s->statement);
582   if (rsize > result_size)
583   {
584     GNUNET_break (0);
585     return GNUNET_SYSERR;
586   }
587   if (mysql_stmt_bind_result (s->statement, results))
588   {
589     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
590                 _("`%s' failed at %s:%d with error: %s\n"),
591                 "mysql_stmt_bind_result", __FILE__, __LINE__,
592                 mysql_stmt_error (s->statement));
593     GNUNET_MYSQL_statements_invalidate (mc);
594     return GNUNET_SYSERR;
595   }
596
597   total = 0;
598   while (1)
599   {
600     ret = mysql_stmt_fetch (s->statement);
601     if (ret == MYSQL_NO_DATA)
602       break;
603     if (ret != 0)
604     {
605       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR, "mysql",
606                        _("`%s' failed at %s:%d with error: %s\n"),
607                        "mysql_stmt_fetch", __FILE__, __LINE__,
608                        mysql_stmt_error (s->statement));
609       GNUNET_MYSQL_statements_invalidate (mc);
610       return GNUNET_SYSERR;
611     }
612     total++;
613     if ((NULL == processor) ||
614         (GNUNET_OK != processor (processor_cls, rsize, results)))
615       break;
616   }
617   mysql_stmt_reset (s->statement);
618   return total;
619 }
620
621
622 /**
623  * Run a prepared SELECT statement.
624  *
625  * @param mc mysql context
626  * @param sh handle to SELECT statment
627  * @param result_size number of elements in results array
628  * @param results pointer to already initialized MYSQL_BIND
629  *        array (of sufficient size) for passing results
630  * @param processor function to call on each result
631  * @param processor_cls extra argument to processor
632  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
633  *        values (size + buffer-reference for pointers); terminated
634  *        with "-1"
635  * @return GNUNET_SYSERR on error, otherwise
636  *         the number of successfully affected (or queried) rows
637  */
638 int
639 GNUNET_MYSQL_statement_run_prepared_select (struct GNUNET_MYSQL_Context *mc,
640                                             struct GNUNET_MYSQL_StatementHandle
641                                             *sh, unsigned int result_size,
642                                             MYSQL_BIND * results,
643                                             GNUNET_MYSQL_DataProcessor
644                                             processor, void *processor_cls, ...)
645 {
646   va_list ap;
647   int ret;
648
649   va_start (ap, processor_cls);
650   ret =
651       GNUNET_MYSQL_statement_run_prepared_select_va (mc, sh, result_size,
652                                                      results, processor,
653                                                      processor_cls, ap);
654   va_end (ap);
655   return ret;
656 }
657
658
659 /**
660  * Run a prepared statement that does NOT produce results.
661  *
662  * @param mc mysql context
663  * @param sh handle to statment
664  * @param insert_id NULL or address where to store the row ID of whatever
665  *        was inserted (only for INSERT statements!)
666  * @param ... pairs and triplets of "MYSQL_TYPE_XXX" keys and their respective
667  *        values (size + buffer-reference for pointers); terminated
668  *        with "-1"
669  * @return GNUNET_SYSERR on error, otherwise
670  *         the number of successfully affected rows
671  */
672 int
673 GNUNET_MYSQL_statement_run_prepared (struct GNUNET_MYSQL_Context *mc,
674                                      struct GNUNET_MYSQL_StatementHandle *sh,
675                                      unsigned long long *insert_id, ...)
676 {
677   va_list ap;
678   int affected;
679
680   if (GNUNET_OK != prepare_statement (mc, sh))
681     return GNUNET_SYSERR;
682   va_start (ap, insert_id);
683   if (GNUNET_OK != init_params (mc, sh, ap))
684   {
685     va_end (ap);
686     return GNUNET_SYSERR;
687   }
688   va_end (ap);
689   affected = mysql_stmt_affected_rows (sh->statement);
690   if (NULL != insert_id)
691     *insert_id = (unsigned long long) mysql_stmt_insert_id (sh->statement);
692   mysql_stmt_reset (sh->statement);
693   return affected;
694 }
695
696
697 /* end of mysql.c */