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