error handling
[oweals/gnunet.git] / src / pq / pq_connect.c
1 /*
2    This file is part of GNUnet
3    Copyright (C) 2017, 2019, 2020 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 pq/pq_connect.c
22  * @brief functions to connect to libpq (PostGres)
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include "pq.h"
27
28
29 /**
30  * Function called by libpq whenever it wants to log something.
31  * We already log whenever we care, so this function does nothing
32  * and merely exists to silence the libpq logging.
33  *
34  * @param arg the SQL connection that was used
35  * @param res information about some libpq event
36  */
37 static void
38 pq_notice_receiver_cb (void *arg,
39                        const PGresult *res)
40 {
41   /* do nothing, intentionally */
42   (void) arg;
43   (void) res;
44 }
45
46
47 /**
48  * Function called by libpq whenever it wants to log something.
49  * We log those using the GNUnet logger.
50  *
51  * @param arg the SQL connection that was used
52  * @param message information about some libpq event
53  */
54 static void
55 pq_notice_processor_cb (void *arg,
56                         const char *message)
57 {
58   (void) arg;
59   GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
60                    "pq",
61                    "%s",
62                    message);
63 }
64
65
66 /**
67  * Create a connection to the Postgres database using @a config_str for the
68  * configuration.  Initialize logging via GNUnet's log routines and disable
69  * Postgres's logger.  Also ensures that the statements in @a load_path and @a
70  * es are executed whenever we (re)connect to the database, and that the
71  * prepared statements in @a ps are "ready".  If statements in @es fail that
72  * were created with #GNUNET_PQ_make_execute(), then the entire operation
73  * fails.
74  *
75  * In @a load_path, a list of "$XXXX.sql" files is expected where $XXXX
76  * must be a sequence of contiguous integer values starting at 0000.
77  * These files are then loaded in sequence using "psql $config_str" before
78  * running statements from @e es.  The directory is inspected again on
79  * reconnect.
80  *
81  * @param config_str configuration to use
82  * @param load_path path to directory with SQL transactions to run, can be NULL
83  * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
84  *            array of statements to execute upon EACH connection, can be NULL
85  * @param ps array of prepared statements to prepare, can be NULL
86  * @return NULL on error
87  */
88 struct GNUNET_PQ_Context *
89 GNUNET_PQ_connect (const char *config_str,
90                    const char *load_path,
91                    const struct GNUNET_PQ_ExecuteStatement *es,
92                    const struct GNUNET_PQ_PreparedStatement *ps)
93 {
94   struct GNUNET_PQ_Context *db;
95   unsigned int elen = 0;
96   unsigned int plen = 0;
97
98   if (NULL != es)
99     while (NULL != es[elen].sql)
100       elen++;
101   if (NULL != ps)
102     while (NULL != ps[plen].name)
103       plen++;
104
105   db = GNUNET_new (struct GNUNET_PQ_Context);
106   db->config_str = GNUNET_strdup (config_str);
107   if (NULL != load_path)
108     db->load_path = GNUNET_strdup (load_path);
109   if (0 != elen)
110   {
111     db->es = GNUNET_new_array (elen + 1,
112                                struct GNUNET_PQ_ExecuteStatement);
113     memcpy (db->es,
114             es,
115             elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
116   }
117   if (0 != plen)
118   {
119     db->ps = GNUNET_new_array (plen + 1,
120                                struct GNUNET_PQ_PreparedStatement);
121     memcpy (db->ps,
122             ps,
123             plen * sizeof (struct GNUNET_PQ_PreparedStatement));
124   }
125   GNUNET_PQ_reconnect (db);
126   if (NULL == db->conn)
127   {
128     GNUNET_free_non_null (db->load_path);
129     GNUNET_free (db->config_str);
130     GNUNET_free (db);
131     return NULL;
132   }
133   return db;
134 }
135
136
137 /**
138  * Apply patch number @a from path @a load_path.
139  *
140  * @param db database context to use
141  * @param load_path where to find the SQL code to run
142  * @param i patch number to append to the @a load_path
143  * @return #GNUNET_OK on success, #GNUNET_NO if patch @a i does not exist, #GNUNET_SYSERR on error
144  */
145 static int
146 apply_patch (struct GNUNET_PQ_Context *db,
147              const char *load_path,
148              unsigned int i)
149 {
150   struct GNUNET_OS_Process *psql;
151   enum GNUNET_OS_ProcessStatusType type;
152   unsigned long code;
153   size_t slen = strlen (load_path) + 10;
154   char buf[slen];
155
156   GNUNET_snprintf (buf,
157                    sizeof (buf),
158                    "%s%04u.sql",
159                    load_path,
160                    i);
161   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
162               "Applying SQL file `%s' on database %s\n",
163               buf,
164               db->config_str);
165   psql = GNUNET_OS_start_process (GNUNET_NO,
166                                   GNUNET_OS_INHERIT_STD_NONE,
167                                   NULL,
168                                   NULL,
169                                   NULL,
170                                   "psql",
171                                   "psql",
172                                   db->config_str,
173                                   "-f",
174                                   buf,
175                                   "-q",
176                                   NULL);
177   if (NULL == psql)
178   {
179     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
180                               "exec",
181                               "psql");
182     return GNUNET_SYSERR;
183   }
184   GNUNET_assert (GNUNET_OK ==
185                  GNUNET_OS_process_wait_status (psql,
186                                                 &type,
187                                                 &code));
188   GNUNET_OS_process_destroy (psql);
189   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
190        (0 != code) )
191   {
192     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
193                 "Could not run PSQL on file %s: %d",
194                 buf,
195                 (int) code);
196     return GNUNET_SYSERR;
197   }
198   return GNUNET_OK;
199 }
200
201
202 /**
203  * Within the @a db context, run all the SQL files
204  * from the @a load_path from 0000-9999.sql (as long
205  * as the files exist contiguously).
206  *
207  * @param db database context to use
208  * @param load_path where to find the XXXX.sql files
209  * @return #GNUNET_OK on success
210  */
211 int
212 GNUNET_PQ_run_sql (struct GNUNET_PQ_Context *db,
213                    const char *load_path)
214 {
215   const char *load_path_suffix;
216   size_t slen = strlen (load_path) + 10;
217
218   load_path_suffix = strrchr (load_path, '/');
219   if (NULL == load_path_suffix)
220   {
221     GNUNET_break (0);
222     return GNUNET_SYSERR;
223   }
224   load_path_suffix++; /* skip '/' */
225   for (unsigned int i = 1; i<10000; i++)
226   {
227     enum GNUNET_DB_QueryStatus qs;
228     {
229       char buf[slen];
230
231       /* First, check patch actually exists */
232       GNUNET_snprintf (buf,
233                        sizeof (buf),
234                        "%s%04u.sql",
235                        load_path,
236                        i);
237       if (GNUNET_YES !=
238           GNUNET_DISK_file_test (buf))
239         return GNUNET_OK;   /* We are done */
240     }
241
242     /* Second, check with DB versioning schema if this patch was already applied,
243        if so, skip it. */
244     {
245       char patch_name[slen];
246
247       GNUNET_snprintf (patch_name,
248                        sizeof (patch_name),
249                        "%s%04u",
250                        load_path_suffix,
251                        i);
252       {
253         char *applied_by;
254         struct GNUNET_PQ_QueryParam params[] = {
255           GNUNET_PQ_query_param_string (patch_name),
256           GNUNET_PQ_query_param_end
257         };
258         struct GNUNET_PQ_ResultSpec rs[] = {
259           GNUNET_PQ_result_spec_string ("applied_by",
260                                         &applied_by),
261           GNUNET_PQ_result_spec_end
262         };
263
264         qs = GNUNET_PQ_eval_prepared_singleton_select (db,
265                                                        "gnunet_pq_check_patch",
266                                                        params,
267                                                        rs);
268         if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
269         {
270           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
271                       "Database version %s already applied by %s, skipping\n",
272                       patch_name,
273                       applied_by);
274           GNUNET_PQ_cleanup_result (rs);
275         }
276         if (GNUNET_DB_STATUS_HARD_ERROR == qs)
277         {
278           GNUNET_break (0);
279           return GNUNET_SYSERR;
280         }
281       }
282     }
283     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
284       continue; /* patch already applied, skip it */
285
286     /* patch not yet applied, run it! */
287     {
288       int ret;
289
290       ret = apply_patch (db,
291                          load_path,
292                          i);
293       if (GNUNET_NO == ret)
294         break;
295       if (GNUNET_SYSERR == ret)
296         return GNUNET_SYSERR;
297     }
298   }
299   return GNUNET_OK;
300 }
301
302
303 /**
304  * Reinitialize the database @a db if the connection is down.
305  *
306  * @param db database connection to reinitialize
307  */
308 void
309 GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
310 {
311   if (CONNECTION_BAD != PQstatus (db->conn))
312     return;
313   GNUNET_PQ_reconnect (db);
314 }
315
316
317 /**
318  * Reinitialize the database @a db.
319  *
320  * @param db database connection to reinitialize
321  */
322 void
323 GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
324 {
325   if (NULL != db->conn)
326     PQfinish (db->conn);
327   db->conn = PQconnectdb (db->config_str);
328   if ( (NULL == db->conn) ||
329        (CONNECTION_OK != PQstatus (db->conn)) )
330   {
331     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
332                      "pq",
333                      "Database connection to '%s' failed: %s\n",
334                      db->config_str,
335                      (NULL != db->conn) ?
336                      PQerrorMessage (db->conn)
337                      : "PQconnectdb returned NULL");
338     if (NULL != db->conn)
339     {
340       PQfinish (db->conn);
341       db->conn = NULL;
342     }
343     return;
344   }
345   PQsetNoticeReceiver (db->conn,
346                        &pq_notice_receiver_cb,
347                        db);
348   PQsetNoticeProcessor (db->conn,
349                         &pq_notice_processor_cb,
350                         db);
351   if (NULL != db->load_path)
352   {
353     PGresult *res;
354
355     res = PQprepare (db->conn,
356                      "gnunet_pq_check_patch",
357                      "SELECT"
358                      " applied_by"
359                      " FROM _v.patches"
360                      " WHERE patch_name = $1"
361                      " LIMIT 1",
362                      1,
363                      NULL);
364     if (PGRES_COMMAND_OK != PQresultStatus (res))
365     {
366       int ret;
367
368       PQclear (res);
369       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
370                   "Failed to prepare statement to check patch level. Likely versioning schema does not exist yet, loading patch level 0000!\n");
371       ret = apply_patch (db,
372                          db->load_path,
373                          0);
374       if (GNUNET_NO == ret)
375       {
376         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
377                     "Failed to find SQL file to load database versioning logic\n");
378         PQfinish (db->conn);
379         db->conn = NULL;
380         return;
381       }
382       if (GNUNET_SYSERR == ret)
383       {
384         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
385                     "Failed to run SQL logic to setup database versioning logic\n");
386         PQfinish (db->conn);
387         db->conn = NULL;
388         return;
389       }
390       /* try again to prepare our statement! */
391       res = PQprepare (db->conn,
392                        "gnunet_pq_check_patch",
393                        "SELECT"
394                        " applied_by"
395                        " FROM _v.patches"
396                        " WHERE patch_name = $1"
397                        " LIMIT 1",
398                        1,
399                        NULL);
400       if (PGRES_COMMAND_OK != PQresultStatus (res))
401       {
402         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
403                     "Failed to run SQL logic to setup database versioning logic: %s/%s\n",
404                     PQresultErrorMessage (res),
405                     PQerrorMessage (db->conn));
406         PQclear (res);
407         PQfinish (db->conn);
408         db->conn = NULL;
409         return;
410       }
411     }
412     PQclear (res);
413
414     if (GNUNET_SYSERR ==
415         GNUNET_PQ_run_sql (db,
416                            db->load_path))
417     {
418       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
419                   "Failed to load SQL statements from `%s'\n",
420                   db->load_path);
421       PQfinish (db->conn);
422       db->conn = NULL;
423       return;
424     }
425   }
426   if ( (NULL != db->es) &&
427        (GNUNET_OK !=
428         GNUNET_PQ_exec_statements (db,
429                                    db->es)) )
430   {
431     PQfinish (db->conn);
432     db->conn = NULL;
433     return;
434   }
435   if ( (NULL != db->ps) &&
436        (GNUNET_OK !=
437         GNUNET_PQ_prepare_statements (db,
438                                       db->ps)) )
439   {
440     PQfinish (db->conn);
441     db->conn = NULL;
442     return;
443   }
444 }
445
446
447 /**
448  * Connect to a postgres database using the configuration
449  * option "CONFIG" in @a section.  Also ensures that the
450  * statements in @a es are executed whenever we (re)connect to the
451  * database, and that the prepared statements in @a ps are "ready".
452  *
453  * The caller does not have to ensure that @a es and @a ps remain allocated
454  * and initialized in memory until #GNUNET_PQ_disconnect() is called, as a copy will be made.
455  *
456  * @param cfg configuration
457  * @param section configuration section to use to get Postgres configuration options
458  * @param load_path_suffix suffix to append to the SQL_DIR in the configuration
459  * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
460  *            array of statements to execute upon EACH connection, can be NULL
461  * @param ps array of prepared statements to prepare, can be NULL
462  * @return the postgres handle, NULL on error
463  */
464 struct GNUNET_PQ_Context *
465 GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
466                             const char *section,
467                             const char *load_path_suffix,
468                             const struct GNUNET_PQ_ExecuteStatement *es,
469                             const struct GNUNET_PQ_PreparedStatement *ps)
470 {
471   struct GNUNET_PQ_Context *db;
472   char *conninfo;
473   char *load_path;
474   char *sp;
475
476   if (GNUNET_OK !=
477       GNUNET_CONFIGURATION_get_value_string (cfg,
478                                              section,
479                                              "CONFIG",
480                                              &conninfo))
481     conninfo = NULL;
482   load_path = NULL;
483   sp = NULL;
484   if ( (NULL != load_path_suffix) &&
485        (GNUNET_OK ==
486         GNUNET_CONFIGURATION_get_value_filename (cfg,
487                                                  section,
488                                                  "SQL_DIR",
489                                                  &sp)) )
490     GNUNET_asprintf (&load_path,
491                      "%s%s",
492                      sp,
493                      load_path_suffix);
494   db = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo,
495                           load_path,
496                           es,
497                           ps);
498   GNUNET_free_non_null (load_path);
499   GNUNET_free_non_null (sp);
500   GNUNET_free_non_null (conninfo);
501   return db;
502 }
503
504
505 /**
506  * Disconnect from the database, destroying the prepared statements
507  * and releasing other associated resources.
508  *
509  * @param db database handle to disconnect (will be free'd)
510  */
511 void
512 GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
513 {
514   GNUNET_free_non_null (db->es);
515   GNUNET_free_non_null (db->ps);
516   GNUNET_free_non_null (db->load_path);
517   GNUNET_free_non_null (db->config_str);
518   PQfinish (db->conn);
519   GNUNET_free (db);
520 }
521
522
523 /* end of pq/pq_connect.c */