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