aboutsummaryrefslogtreecommitdiff
path: root/src/lib/pq/pq_connect.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/pq/pq_connect.c')
-rw-r--r--src/lib/pq/pq_connect.c705
1 files changed, 705 insertions, 0 deletions
diff --git a/src/lib/pq/pq_connect.c b/src/lib/pq/pq_connect.c
new file mode 100644
index 000000000..c46d865a3
--- /dev/null
+++ b/src/lib/pq/pq_connect.c
@@ -0,0 +1,705 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2017, 2019, 2020, 2021, 2023 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 * @author Özgür Kesim
25 */
26#include "platform.h"
27#include "pq.h"
28#include <pthread.h>
29
30
31/**
32 * Function called by libpq whenever it wants to log something.
33 * We already log whenever we care, so this function does nothing
34 * and merely exists to silence the libpq logging.
35 *
36 * @param arg the SQL connection that was used
37 * @param res information about some libpq event
38 */
39static void
40pq_notice_receiver_cb (void *arg,
41 const PGresult *res)
42{
43 /* do nothing, intentionally */
44 (void) arg;
45 (void) res;
46}
47
48
49/**
50 * Function called by libpq whenever it wants to log something.
51 * We log those using the GNUnet logger.
52 *
53 * @param arg the SQL connection that was used
54 * @param message information about some libpq event
55 */
56static void
57pq_notice_processor_cb (void *arg,
58 const char *message)
59{
60 (void) arg;
61 GNUNET_log_from (GNUNET_ERROR_TYPE_INFO,
62 "pq",
63 "%s",
64 message);
65}
66
67
68struct GNUNET_PQ_Context *
69GNUNET_PQ_connect (const char *config_str,
70 const char *load_path,
71 const struct GNUNET_PQ_ExecuteStatement *es,
72 const struct GNUNET_PQ_PreparedStatement *ps)
73{
74 return GNUNET_PQ_connect2 (config_str,
75 load_path,
76 NULL == load_path
77 ? NULL
78 : "",
79 es,
80 ps,
81 GNUNET_PQ_FLAG_NONE);
82}
83
84
85struct GNUNET_PQ_Context *
86GNUNET_PQ_connect2 (const char *config_str,
87 const char *load_path,
88 const char *auto_suffix,
89 const struct GNUNET_PQ_ExecuteStatement *es,
90 const struct GNUNET_PQ_PreparedStatement *ps,
91 enum GNUNET_PQ_Options flags)
92{
93 struct GNUNET_PQ_Context *db;
94 unsigned int elen = 0;
95 unsigned int plen = 0;
96
97 if (NULL != es)
98 while (NULL != es[elen].sql)
99 elen++;
100 if (NULL != ps)
101 while (NULL != ps[plen].name)
102 plen++;
103
104 db = GNUNET_new (struct GNUNET_PQ_Context);
105 db->flags = flags;
106 db->config_str = GNUNET_strdup (config_str);
107 if (NULL != load_path)
108 db->load_path = GNUNET_strdup (load_path);
109 if (NULL != auto_suffix)
110 db->auto_suffix = GNUNET_strdup (auto_suffix);
111 if (0 != elen)
112 {
113 db->es = GNUNET_new_array (elen + 1,
114 struct GNUNET_PQ_ExecuteStatement);
115 memcpy (db->es,
116 es,
117 elen * sizeof (struct GNUNET_PQ_ExecuteStatement));
118 }
119 if (0 != plen)
120 {
121 db->ps = GNUNET_new_array (plen + 1,
122 struct GNUNET_PQ_PreparedStatement);
123 memcpy (db->ps,
124 ps,
125 plen * sizeof (struct GNUNET_PQ_PreparedStatement));
126 }
127 db->channel_map = GNUNET_CONTAINER_multishortmap_create (16,
128 GNUNET_YES);
129 GNUNET_PQ_reconnect (db);
130 if (NULL == db->conn)
131 {
132 GNUNET_CONTAINER_multishortmap_destroy (db->channel_map);
133 GNUNET_free (db->load_path);
134 GNUNET_free (db->auto_suffix);
135 GNUNET_free (db->config_str);
136 GNUNET_free (db);
137 return NULL;
138 }
139 return db;
140}
141
142
143enum GNUNET_GenericReturnValue
144GNUNET_PQ_exec_sql (struct GNUNET_PQ_Context *db,
145 const char *buf)
146{
147 struct GNUNET_OS_Process *psql;
148 enum GNUNET_OS_ProcessStatusType type;
149 unsigned long code;
150 enum GNUNET_GenericReturnValue ret;
151 char *fn;
152
153 GNUNET_asprintf (&fn,
154 "%s%s.sql",
155 db->load_path,
156 buf);
157 if (GNUNET_YES !=
158 GNUNET_DISK_file_test (fn))
159 {
160 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
161 "SQL resource `%s' does not exist\n",
162 fn);
163 GNUNET_free (fn);
164 return GNUNET_NO;
165 }
166 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
167 "Applying SQL file `%s' on database %s\n",
168 fn,
169 db->config_str);
170 psql = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_NONE,
171 NULL,
172 NULL,
173 NULL,
174 "psql",
175 "psql",
176 db->config_str,
177 "-f",
178 fn,
179 "-q",
180 "--set",
181 "ON_ERROR_STOP=1",
182 NULL);
183 if (NULL == psql)
184 {
185 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
186 "exec",
187 "psql");
188 GNUNET_free (fn);
189 return GNUNET_SYSERR;
190 }
191 ret = GNUNET_OS_process_wait_status (psql,
192 &type,
193 &code);
194 if (GNUNET_OK != ret)
195 {
196 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
197 "psql on file %s did not finish, killed it!\n",
198 fn);
199 /* can happen if we got a signal, like CTRL-C, before
200 psql was complete */
201 (void) GNUNET_OS_process_kill (psql,
202 SIGKILL);
203 GNUNET_OS_process_destroy (psql);
204 GNUNET_free (fn);
205 return GNUNET_SYSERR;
206 }
207 GNUNET_OS_process_destroy (psql);
208 if ( (GNUNET_OS_PROCESS_EXITED != type) ||
209 (0 != code) )
210 {
211 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
212 "Could not run PSQL on file %s: psql exit code was %d\n",
213 fn,
214 (int) code);
215 GNUNET_free (fn);
216 return GNUNET_SYSERR;
217 }
218 GNUNET_free (fn);
219 return GNUNET_OK;
220}
221
222
223enum GNUNET_GenericReturnValue
224GNUNET_PQ_run_sql (struct GNUNET_PQ_Context *db,
225 const char *load_path)
226{
227 const char *load_path_suffix;
228 size_t slen = strlen (load_path) + 10;
229
230 load_path_suffix = strrchr (load_path, '/');
231 if (NULL == load_path_suffix)
232 load_path_suffix = load_path;
233 else
234 load_path_suffix++; /* skip '/' */
235 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
236 "Loading SQL resources from `%s'\n",
237 load_path);
238 for (unsigned int i = 1; i<10000; i++)
239 {
240 char patch_name[slen];
241 enum GNUNET_DB_QueryStatus qs;
242
243 /* Check with DB versioning schema if this patch was already applied,
244 if so, skip it. */
245 GNUNET_snprintf (patch_name,
246 sizeof (patch_name),
247 "%s%04u",
248 load_path_suffix,
249 i);
250 {
251 char *applied_by;
252 struct GNUNET_PQ_QueryParam params[] = {
253 GNUNET_PQ_query_param_string (patch_name),
254 GNUNET_PQ_query_param_end
255 };
256 struct GNUNET_PQ_ResultSpec rs[] = {
257 GNUNET_PQ_result_spec_string ("applied_by",
258 &applied_by),
259 GNUNET_PQ_result_spec_end
260 };
261
262 qs = GNUNET_PQ_eval_prepared_singleton_select (db,
263 "gnunet_pq_check_patch",
264 params,
265 rs);
266 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
267 {
268 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
269 "Database version %s already applied by %s, skipping\n",
270 patch_name,
271 applied_by);
272 GNUNET_PQ_cleanup_result (rs);
273 }
274 if (GNUNET_DB_STATUS_HARD_ERROR == qs)
275 {
276 GNUNET_break (0);
277 return GNUNET_SYSERR;
278 }
279 }
280 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
281 continue; /* patch already applied, skip it */
282
283 if (0 != (GNUNET_PQ_FLAG_CHECK_CURRENT & db->flags))
284 {
285 /* We are only checking, found unapplied patch, bad! */
286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
287 "Database outdated, patch %s missing. Aborting!\n",
288 patch_name);
289 return GNUNET_SYSERR;
290 }
291 else
292 {
293 /* patch not yet applied, run it! */
294 enum GNUNET_GenericReturnValue ret;
295
296 GNUNET_snprintf (patch_name,
297 sizeof (patch_name),
298 "%s%04u",
299 load_path,
300 i);
301 ret = GNUNET_PQ_exec_sql (db,
302 patch_name);
303 if (GNUNET_NO == ret)
304 break;
305 if (GNUNET_SYSERR == ret)
306 return GNUNET_SYSERR;
307 }
308 }
309 return GNUNET_OK;
310}
311
312
313void
314GNUNET_PQ_reconnect_if_down (struct GNUNET_PQ_Context *db)
315{
316 if (1 ==
317 PQconsumeInput (db->conn))
318 return;
319 if (CONNECTION_BAD != PQstatus (db->conn))
320 return;
321 GNUNET_PQ_reconnect (db);
322}
323
324
325enum GNUNET_GenericReturnValue
326GNUNET_PQ_get_oid_by_name (
327 struct GNUNET_PQ_Context *db,
328 const char *name,
329 Oid *oid)
330{
331 /* Check if the entry is in the cache already */
332 for (unsigned int i = 0; i < db->oids.num; i++)
333 {
334 /* Pointer comparison */
335 if (name == db->oids.table[i].name)
336 {
337 *oid = db->oids.table[i].oid;
338 return GNUNET_OK;
339 }
340 }
341
342 /* No entry found in cache, ask database */
343 {
344 enum GNUNET_DB_QueryStatus qs;
345 struct GNUNET_PQ_QueryParam params[] = {
346 GNUNET_PQ_query_param_string (name),
347 GNUNET_PQ_query_param_end
348 };
349 struct GNUNET_PQ_ResultSpec spec[] = {
350 GNUNET_PQ_result_spec_uint32 ("oid",
351 oid),
352 GNUNET_PQ_result_spec_end
353 };
354
355 GNUNET_assert (NULL != db);
356
357 qs = GNUNET_PQ_eval_prepared_singleton_select (db,
358 "gnunet_pq_get_oid_by_name",
359 params,
360 spec);
361 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
362 return GNUNET_SYSERR;
363 }
364
365 /* Add the entry to the cache */
366 if (NULL == db->oids.table)
367 {
368 db->oids.table = GNUNET_new_array (8,
369 typeof(*db->oids.table));
370 db->oids.cap = 8;
371 db->oids.num = 0;
372 }
373
374 if (db->oids.cap <= db->oids.num)
375 GNUNET_array_grow (db->oids.table,
376 db->oids.cap,
377 db->oids.cap + 8);
378
379 db->oids.table[db->oids.num].name = name;
380 db->oids.table[db->oids.num].oid = *oid;
381 db->oids.num++;
382
383 return GNUNET_OK;
384}
385
386
387/**
388 * Load the initial set of OIDs for the supported
389 * array-datatypes
390 *
391 * @param db The database context
392 * @return GNUNET_OK on success, GNUNET_SYSERR if any of the types couldn't be found
393 */
394static
395enum GNUNET_GenericReturnValue
396load_initial_oids (struct GNUNET_PQ_Context *db)
397{
398 static const char *typnames[] = {
399 "bool",
400 "int2",
401 "int4",
402 "int8",
403 "bytea",
404 "varchar"
405 };
406 Oid oid;
407
408 for (size_t i = 0; i< sizeof(typnames) / sizeof(*typnames); i++)
409 {
410 if (GNUNET_OK !=
411 GNUNET_PQ_get_oid_by_name (db,
412 typnames[i],
413 &oid))
414 {
415 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
416 "pq",
417 "Couldn't retrieve OID for type %s\n",
418 typnames[i]);
419 return GNUNET_SYSERR;
420 }
421 }
422 return GNUNET_OK;
423}
424
425
426void
427GNUNET_PQ_reconnect (struct GNUNET_PQ_Context *db)
428{
429 GNUNET_PQ_event_reconnect_ (db,
430 -1);
431 if (NULL != db->conn)
432 PQfinish (db->conn);
433 db->conn = PQconnectdb (db->config_str);
434 if ( (NULL == db->conn) ||
435 (CONNECTION_OK != PQstatus (db->conn)) )
436 {
437 GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
438 "pq",
439 "Database connection to '%s' failed: %s\n",
440 db->config_str,
441 (NULL != db->conn)
442 ? PQerrorMessage (db->conn)
443 : "PQconnectdb returned NULL");
444 if (NULL != db->conn)
445 {
446 PQfinish (db->conn);
447 db->conn = NULL;
448 }
449 return;
450 }
451 PQsetNoticeReceiver (db->conn,
452 &pq_notice_receiver_cb,
453 db);
454 PQsetNoticeProcessor (db->conn,
455 &pq_notice_processor_cb,
456 db);
457 if (NULL != db->load_path)
458 {
459 PGresult *res;
460 ExecStatusType est;
461
462 res = PQexec (db->conn,
463 "SELECT"
464 " schema_name"
465 " FROM information_schema.schemata"
466 " WHERE schema_name='_v';");
467 est = PQresultStatus (res);
468 if ( (PGRES_COMMAND_OK != est) &&
469 (PGRES_TUPLES_OK != est) )
470 {
471 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
472 "Failed to run statement to check versioning schema. Bad!\n");
473 PQclear (res);
474 PQfinish (db->conn);
475 db->conn = NULL;
476 return;
477 }
478 if (0 == PQntuples (res))
479 {
480 enum GNUNET_GenericReturnValue ret;
481
482 PQclear (res);
483 if (0 != (db->flags & GNUNET_PQ_FLAG_DROP))
484 {
485 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
486 "Versioning schema does not exist yet. Not attempting drop!\n");
487 PQfinish (db->conn);
488 db->conn = NULL;
489 return;
490 }
491 ret = GNUNET_PQ_exec_sql (db,
492 "versioning");
493 if (GNUNET_NO == ret)
494 {
495 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
496 "Failed to find SQL file to load database versioning logic\n");
497 PQfinish (db->conn);
498 db->conn = NULL;
499 return;
500 }
501 if (GNUNET_SYSERR == ret)
502 {
503 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
504 "Failed to run SQL logic to setup database versioning logic\n");
505 PQfinish (db->conn);
506 db->conn = NULL;
507 return;
508 }
509 }
510 else
511 {
512 PQclear (res);
513 }
514 }
515
516 /* Prepare statement for OID lookup by name */
517 {
518 PGresult *res;
519
520 res = PQprepare (db->conn,
521 "gnunet_pq_get_oid_by_name",
522 "SELECT"
523 " typname, oid"
524 " FROM pg_type"
525 " WHERE typname = $1"
526 " LIMIT 1",
527 1,
528 NULL);
529 if (PGRES_COMMAND_OK != PQresultStatus (res))
530 {
531 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
532 "Failed to run SQL statement prepare OID lookups: %s/%s\n",
533 PQresultErrorMessage (res),
534 PQerrorMessage (db->conn));
535 PQclear (res);
536 PQfinish (db->conn);
537 db->conn = NULL;
538 return;
539 }
540 PQclear (res);
541 }
542
543 /* Reset the OID-cache and retrieve the OIDs for the supported Array types */
544 db->oids.num = 0;
545 if (GNUNET_SYSERR == load_initial_oids (db))
546 {
547 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
548 "Failed to retrieve OID information for array types!\n");
549 PQfinish (db->conn);
550 db->conn = NULL;
551 return;
552 }
553
554
555 if (NULL != db->auto_suffix)
556 {
557 PGresult *res;
558
559 GNUNET_assert (NULL != db->load_path);
560 res = PQprepare (db->conn,
561 "gnunet_pq_check_patch",
562 "SELECT"
563 " applied_by"
564 " FROM _v.patches"
565 " WHERE patch_name = $1"
566 " LIMIT 1",
567 1,
568 NULL);
569 if (PGRES_COMMAND_OK != PQresultStatus (res))
570 {
571 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
572 "Failed to run SQL logic to setup database versioning logic: %s/%s\n",
573 PQresultErrorMessage (res),
574 PQerrorMessage (db->conn));
575 PQclear (res);
576 PQfinish (db->conn);
577 db->conn = NULL;
578 return;
579 }
580 PQclear (res);
581
582 if (GNUNET_SYSERR ==
583 GNUNET_PQ_run_sql (db,
584 db->auto_suffix))
585 {
586 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
587 "Failed to load SQL statements from `%s*'\n",
588 db->auto_suffix);
589 PQfinish (db->conn);
590 db->conn = NULL;
591 return;
592 }
593 }
594
595 if ( (NULL != db->es) &&
596 (GNUNET_OK !=
597 GNUNET_PQ_exec_statements (db,
598 db->es)) )
599 {
600 PQfinish (db->conn);
601 db->conn = NULL;
602 return;
603 }
604 if ( (NULL != db->ps) &&
605 (GNUNET_OK !=
606 GNUNET_PQ_prepare_statements (db,
607 db->ps)) )
608 {
609 PQfinish (db->conn);
610 db->conn = NULL;
611 return;
612 }
613 GNUNET_PQ_event_reconnect_ (db,
614 PQsocket (db->conn));
615}
616
617
618struct GNUNET_PQ_Context *
619GNUNET_PQ_connect_with_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
620 const char *section,
621 const char *load_path_suffix,
622 const struct GNUNET_PQ_ExecuteStatement *es,
623 const struct GNUNET_PQ_PreparedStatement *ps)
624{
625 return GNUNET_PQ_connect_with_cfg2 (cfg,
626 section,
627 load_path_suffix,
628 es,
629 ps,
630 GNUNET_PQ_FLAG_NONE);
631}
632
633
634struct GNUNET_PQ_Context *
635GNUNET_PQ_connect_with_cfg2 (const struct GNUNET_CONFIGURATION_Handle *cfg,
636 const char *section,
637 const char *load_path_suffix,
638 const struct GNUNET_PQ_ExecuteStatement *es,
639 const struct GNUNET_PQ_PreparedStatement *ps,
640 enum GNUNET_PQ_Options flags)
641{
642 struct GNUNET_PQ_Context *db;
643 char *conninfo;
644 char *load_path;
645
646 if (GNUNET_OK !=
647 GNUNET_CONFIGURATION_get_value_string (cfg,
648 section,
649 "CONFIG",
650 &conninfo))
651 conninfo = NULL;
652 load_path = NULL;
653 if (GNUNET_OK !=
654 GNUNET_CONFIGURATION_get_value_filename (cfg,
655 section,
656 "SQL_DIR",
657 &load_path))
658 {
659 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
660 section,
661 "SQL_DIR");
662 }
663 if ( (NULL != load_path_suffix) &&
664 (NULL == load_path) )
665 {
666 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
667 section,
668 "SQL_DIR");
669 return NULL;
670 }
671 db = GNUNET_PQ_connect2 (conninfo == NULL ? "" : conninfo,
672 load_path,
673 load_path_suffix,
674 es,
675 ps,
676 flags);
677 GNUNET_free (load_path);
678 GNUNET_free (conninfo);
679 return db;
680}
681
682
683void
684GNUNET_PQ_disconnect (struct GNUNET_PQ_Context *db)
685{
686 if (NULL == db)
687 return;
688 GNUNET_assert (0 ==
689 GNUNET_CONTAINER_multishortmap_size (db->channel_map));
690 GNUNET_CONTAINER_multishortmap_destroy (db->channel_map);
691 GNUNET_free (db->es);
692 GNUNET_free (db->ps);
693 GNUNET_free (db->load_path);
694 GNUNET_free (db->auto_suffix);
695 GNUNET_free (db->config_str);
696 GNUNET_free (db->oids.table);
697 db->oids.table = NULL;
698 db->oids.num = 0;
699 db->oids.cap = 0;
700 PQfinish (db->conn);
701 GNUNET_free (db);
702}
703
704
705/* end of pq/pq_connect.c */