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