use NULL value in load_path_suffix to NOT load any files
[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  * Within the @a db context, run all the SQL files
139  * from the @a load_path from 0000-9999.sql (as long
140  * as the files exist contiguously).
141  *
142  * @param db database context to use
143  * @param load_path where to find the XXXX.sql files
144  * @return #GNUNET_OK on success
145  */
146 int
147 GNUNET_PQ_run_sql (struct GNUNET_PQ_Context *db,
148                    const char *load_path)
149 {
150   size_t slen = strlen (load_path) + 10;
151
152   for (unsigned int i = 0; i<10000; i++)
153   {
154     char buf[slen];
155     struct GNUNET_OS_Process *psql;
156     enum GNUNET_OS_ProcessStatusType type;
157     unsigned long code;
158
159     GNUNET_snprintf (buf,
160                      sizeof (buf),
161                      "%s%04u.sql",
162                      load_path,
163                      i);
164     if (GNUNET_YES !=
165         GNUNET_DISK_file_test (buf))
166       break; /* We are done */
167     psql = GNUNET_OS_start_process (GNUNET_NO,
168                                     GNUNET_OS_INHERIT_STD_NONE,
169                                     NULL,
170                                     NULL,
171                                     NULL,
172                                     "psql",
173                                     "psql",
174                                     db->config_str,
175                                     "-f",
176                                     buf,
177                                     "-q",
178                                     NULL);
179     if (NULL == psql)
180     {
181       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
182                                 "exec",
183                                 "psql");
184       return GNUNET_SYSERR;
185     }
186     GNUNET_assert (GNUNET_OK ==
187                    GNUNET_OS_process_wait_status (psql,
188                                                   &type,
189                                                   &code));
190     GNUNET_OS_process_destroy (psql);
191     if ( (GNUNET_OS_PROCESS_EXITED != type) ||
192          (0 != code) )
193     {
194       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
195                   "Could not run PSQL on file %s: %d",
196                   buf,
197                   (int) code);
198       return GNUNET_SYSERR;
199     }
200   }
201   return GNUNET_OK;
202 }
203
204
205 /**
206  * Reinitialize the database @a db if the connection is down.
207  *
208  * @param db database connection to reinitialize
209  */
210 void
211 GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
212 {
213   if (CONNECTION_BAD != PQstatus (db->conn))
214     return;
215   GNUNET_PQ_reconnect (db);
216 }
217
218
219 /**
220  * Reinitialize the database @a db.
221  *
222  * @param db database connection to reinitialize
223  */
224 void
225 GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
226 {
227   if (NULL != db->conn)
228     PQfinish (db->conn);
229   db->conn = PQconnectdb (db->config_str);
230   if ((NULL == db->conn) ||
231       (CONNECTION_OK != PQstatus (db->conn)))
232   {
233     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
234                      "pq",
235                      "Database connection to '%s' failed: %s\n",
236                      db->config_str,
237                      (NULL != db->conn) ?
238                      PQerrorMessage (db->conn)
239                      : "PQconnectdb returned NULL");
240     if (NULL != db->conn)
241     {
242       PQfinish (db->conn);
243       db->conn = NULL;
244     }
245     return;
246   }
247   PQsetNoticeReceiver (db->conn,
248                        &pq_notice_receiver_cb,
249                        db);
250   PQsetNoticeProcessor (db->conn,
251                         &pq_notice_processor_cb,
252                         db);
253   if ( (NULL != db->load_path) &&
254        (GNUNET_OK !=
255         GNUNET_PQ_run_sql (db,
256                            db->load_path)) )
257   {
258     PQfinish (db->conn);
259     db->conn = NULL;
260     return;
261   }
262   if ( (NULL != db->es) &&
263        (GNUNET_OK !=
264         GNUNET_PQ_exec_statements (db,
265                                    db->es)) )
266   {
267     PQfinish (db->conn);
268     db->conn = NULL;
269     return;
270   }
271   if ( (NULL != db->ps) &&
272        (GNUNET_OK !=
273         GNUNET_PQ_prepare_statements (db,
274                                       db->ps)) )
275   {
276     PQfinish (db->conn);
277     db->conn = NULL;
278     return;
279   }
280 }
281
282
283 /**
284  * Connect to a postgres database using the configuration
285  * option "CONFIG" in @a section.  Also ensures that the
286  * statements in @a es are executed whenever we (re)connect to the
287  * database, and that the prepared statements in @a ps are "ready".
288  *
289  * The caller does not have to ensure that @a es and @a ps remain allocated
290  * and initialized in memory until #GNUNET_PQ_disconnect() is called, as a copy will be made.
291  *
292  * @param cfg configuration
293  * @param section configuration section to use to get Postgres configuration options
294  * @param load_path_suffix suffix to append to the SQL_DIR in the configuration
295  * @param es #GNUNET_PQ_PREPARED_STATEMENT_END-terminated
296  *            array of statements to execute upon EACH connection, can be NULL
297  * @param ps array of prepared statements to prepare, can be NULL
298  * @return the postgres handle, NULL on error
299  */
300 struct GNUNET_PQ_Context *
301 GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
302                             const char *section,
303                             const char *load_path_suffix,
304                             const struct GNUNET_PQ_ExecuteStatement *es,
305                             const struct GNUNET_PQ_PreparedStatement *ps)
306 {
307   struct GNUNET_PQ_Context *db;
308   char *conninfo;
309   char *load_path;
310   char *sp;
311
312   if (GNUNET_OK !=
313       GNUNET_CONFIGURATION_get_value_string (cfg,
314                                              section,
315                                              "CONFIG",
316                                              &conninfo))
317     conninfo = NULL;
318   load_path = NULL;
319   sp = NULL;
320   if ( (NULL != load_path_suffix) &&
321        (GNUNET_OK ==
322         GNUNET_CONFIGURATION_get_value_filename (cfg,
323                                                  section,
324                                                  "SQL_DIR",
325                                                  &sp)) )
326     GNUNET_asprintf (&load_path,
327                      "%s%s",
328                      sp,
329                      load_path_suffix);
330   db = GNUNET_PQ_connect (conninfo == NULL ? "" : conninfo,
331                           load_path,
332                           es,
333                           ps);
334   GNUNET_free_non_null (load_path);
335   GNUNET_free_non_null (sp);
336   GNUNET_free_non_null (conninfo);
337   return db;
338 }
339
340
341 /**
342  * Disconnect from the database, destroying the prepared statements
343  * and releasing other associated resources.
344  *
345  * @param db database handle to disconnect (will be free'd)
346  */
347 void
348 GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
349 {
350   GNUNET_free_non_null (db->es);
351   GNUNET_free_non_null (db->ps);
352   GNUNET_free_non_null (db->load_path);
353   GNUNET_free_non_null (db->config_str);
354   PQfinish (db->conn);
355   GNUNET_free (db);
356 }
357
358
359 /* end of pq/pq_connect.c */