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