plugin_namestore_postgres.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /*
  2. * This file is part of GNUnet
  3. * Copyright (C) 2009-2013, 2016-2018 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. SPDX-License-Identifier: AGPL3.0-or-later
  18. */
  19. /**
  20. * @file namestore/plugin_namestore_postgres.c
  21. * @brief postgres-based namestore backend
  22. * @author Christian Grothoff
  23. */
  24. #include "platform.h"
  25. #include "gnunet_namestore_plugin.h"
  26. #include "gnunet_namestore_service.h"
  27. #include "gnunet_gnsrecord_lib.h"
  28. #include "gnunet_pq_lib.h"
  29. #include "namestore.h"
  30. #define LOG(kind, ...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
  31. /**
  32. * Context for all functions in this plugin.
  33. */
  34. struct Plugin
  35. {
  36. /**
  37. * Our configuration.
  38. */
  39. const struct GNUNET_CONFIGURATION_Handle *cfg;
  40. /**
  41. * Postgres database handle.
  42. */
  43. struct GNUNET_PQ_Context *dbh;
  44. };
  45. /**
  46. * Initialize the database connections and associated
  47. * data structures (create tables and indices
  48. * as needed as well).
  49. *
  50. * @param plugin the plugin context (state for this module)
  51. * @return #GNUNET_OK on success
  52. */
  53. static int
  54. database_setup (struct Plugin *plugin)
  55. {
  56. struct GNUNET_PQ_ExecuteStatement es_temporary =
  57. GNUNET_PQ_make_execute (
  58. "CREATE TEMPORARY TABLE IF NOT EXISTS ns098records ("
  59. " seq BIGSERIAL PRIMARY KEY,"
  60. " zone_private_key BYTEA NOT NULL DEFAULT '',"
  61. " pkey BYTEA DEFAULT '',"
  62. " rvalue BYTEA NOT NULL DEFAULT '',"
  63. " record_count INTEGER NOT NULL DEFAULT 0,"
  64. " record_data BYTEA NOT NULL DEFAULT '',"
  65. " label TEXT NOT NULL DEFAULT '',"
  66. " CONSTRAINT zl UNIQUE (zone_private_key,label)"
  67. ")");
  68. struct GNUNET_PQ_ExecuteStatement es_default =
  69. GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns098records ("
  70. " seq BIGSERIAL PRIMARY KEY,"
  71. " zone_private_key BYTEA NOT NULL DEFAULT '',"
  72. " pkey BYTEA DEFAULT '',"
  73. " rvalue BYTEA NOT NULL DEFAULT '',"
  74. " record_count INTEGER NOT NULL DEFAULT 0,"
  75. " record_data BYTEA NOT NULL DEFAULT '',"
  76. " label TEXT NOT NULL DEFAULT '',"
  77. " CONSTRAINT zl UNIQUE (zone_private_key,label)"
  78. ")");
  79. const struct GNUNET_PQ_ExecuteStatement *cr;
  80. struct GNUNET_PQ_ExecuteStatement sc = GNUNET_PQ_EXECUTE_STATEMENT_END;
  81. if (GNUNET_YES ==
  82. GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
  83. "namestore-postgres",
  84. "TEMPORARY_TABLE"))
  85. {
  86. cr = &es_temporary;
  87. }
  88. else
  89. {
  90. cr = &es_default;
  91. }
  92. if (GNUNET_YES ==
  93. GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
  94. "namestore-postgres",
  95. "ASYNC_COMMIT"))
  96. sc = GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off");
  97. {
  98. struct GNUNET_PQ_ExecuteStatement es[] = {
  99. *cr,
  100. GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_reverse "
  101. "ON ns098records (zone_private_key,pkey)"),
  102. GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_iter "
  103. "ON ns098records (zone_private_key,seq)"),
  104. GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_label "
  105. "ON ns098records (label)"),
  106. GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS zone_label "
  107. "ON ns098records (zone_private_key,label)"),
  108. sc,
  109. GNUNET_PQ_EXECUTE_STATEMENT_END
  110. };
  111. struct GNUNET_PQ_PreparedStatement ps[] = {
  112. GNUNET_PQ_make_prepare ("store_records",
  113. "INSERT INTO ns098records"
  114. " (zone_private_key, pkey, rvalue, record_count, record_data, label)"
  115. " VALUES ($1, $2, $3, $4, $5, $6)"
  116. " ON CONFLICT ON CONSTRAINT zl"
  117. " DO UPDATE"
  118. " SET pkey=$2,rvalue=$3,record_count=$4,record_data=$5"
  119. " WHERE ns098records.zone_private_key = $1"
  120. " AND ns098records.label = $6",
  121. 6),
  122. GNUNET_PQ_make_prepare ("delete_records",
  123. "DELETE FROM ns098records "
  124. "WHERE zone_private_key=$1 AND label=$2",
  125. 2),
  126. GNUNET_PQ_make_prepare ("zone_to_name",
  127. "SELECT seq,record_count,record_data,label FROM ns098records"
  128. " WHERE zone_private_key=$1 AND pkey=$2",
  129. 2),
  130. GNUNET_PQ_make_prepare ("iterate_zone",
  131. "SELECT seq,record_count,record_data,label FROM ns098records "
  132. "WHERE zone_private_key=$1 AND seq > $2 ORDER BY seq ASC LIMIT $3",
  133. 3),
  134. GNUNET_PQ_make_prepare ("iterate_all_zones",
  135. "SELECT seq,record_count,record_data,label,zone_private_key"
  136. " FROM ns098records WHERE seq > $1 ORDER BY seq ASC LIMIT $2",
  137. 2),
  138. GNUNET_PQ_make_prepare ("lookup_label",
  139. "SELECT seq,record_count,record_data,label "
  140. "FROM ns098records WHERE zone_private_key=$1 AND label=$2",
  141. 2),
  142. GNUNET_PQ_PREPARED_STATEMENT_END
  143. };
  144. plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
  145. "namestore-postgres",
  146. NULL,
  147. es,
  148. ps);
  149. }
  150. if (NULL == plugin->dbh)
  151. return GNUNET_SYSERR;
  152. return GNUNET_OK;
  153. }
  154. /**
  155. * Store a record in the datastore. Removes any existing record in the
  156. * same zone with the same name.
  157. *
  158. * @param cls closure (internal context for the plugin)
  159. * @param zone_key private key of the zone
  160. * @param label name that is being mapped (at most 255 characters long)
  161. * @param rd_count number of entries in @a rd array
  162. * @param rd array of records with data to store
  163. * @return #GNUNET_OK on success, else #GNUNET_SYSERR
  164. */
  165. static int
  166. namestore_postgres_store_records (void *cls,
  167. const struct
  168. GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
  169. const char *label,
  170. unsigned int rd_count,
  171. const struct GNUNET_GNSRECORD_Data *rd)
  172. {
  173. struct Plugin *plugin = cls;
  174. struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
  175. uint64_t rvalue;
  176. uint32_t rd_count32 = (uint32_t) rd_count;
  177. ssize_t data_size;
  178. memset (&pkey,
  179. 0,
  180. sizeof(pkey));
  181. for (unsigned int i = 0; i < rd_count; i++)
  182. if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
  183. {
  184. GNUNET_break (sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey) ==
  185. rd[i].data_size);
  186. GNUNET_memcpy (&pkey,
  187. rd[i].data,
  188. rd[i].data_size);
  189. break;
  190. }
  191. rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
  192. UINT64_MAX);
  193. data_size = GNUNET_GNSRECORD_records_get_size (rd_count,
  194. rd);
  195. if (data_size < 0)
  196. {
  197. GNUNET_break (0);
  198. return GNUNET_SYSERR;
  199. }
  200. if (data_size >= UINT16_MAX)
  201. {
  202. GNUNET_break (0);
  203. return GNUNET_SYSERR;
  204. }
  205. /* if record set is empty, delete existing records */
  206. if (0 == rd_count)
  207. {
  208. struct GNUNET_PQ_QueryParam params[] = {
  209. GNUNET_PQ_query_param_auto_from_type (zone_key),
  210. GNUNET_PQ_query_param_string (label),
  211. GNUNET_PQ_query_param_end
  212. };
  213. enum GNUNET_DB_QueryStatus res;
  214. res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
  215. "delete_records",
  216. params);
  217. if ((GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res) &&
  218. (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != res))
  219. {
  220. GNUNET_break (0);
  221. return GNUNET_SYSERR;
  222. }
  223. GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
  224. "postgres",
  225. "Record deleted\n");
  226. return GNUNET_OK;
  227. }
  228. /* otherwise, UPSERT (i.e. UPDATE if exists, otherwise INSERT) */
  229. {
  230. char data[data_size];
  231. struct GNUNET_PQ_QueryParam params[] = {
  232. GNUNET_PQ_query_param_auto_from_type (zone_key),
  233. GNUNET_PQ_query_param_auto_from_type (&pkey),
  234. GNUNET_PQ_query_param_uint64 (&rvalue),
  235. GNUNET_PQ_query_param_uint32 (&rd_count32),
  236. GNUNET_PQ_query_param_fixed_size (data, data_size),
  237. GNUNET_PQ_query_param_string (label),
  238. GNUNET_PQ_query_param_end
  239. };
  240. enum GNUNET_DB_QueryStatus res;
  241. ssize_t ret;
  242. ret = GNUNET_GNSRECORD_records_serialize (rd_count,
  243. rd,
  244. data_size,
  245. data);
  246. if ((ret < 0) ||
  247. (data_size != ret))
  248. {
  249. GNUNET_break (0);
  250. return GNUNET_SYSERR;
  251. }
  252. res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
  253. "store_records",
  254. params);
  255. if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res)
  256. return GNUNET_SYSERR;
  257. }
  258. return GNUNET_OK;
  259. }
  260. /**
  261. * Closure for #parse_result_call_iterator.
  262. */
  263. struct ParserContext
  264. {
  265. /**
  266. * Function to call for each result.
  267. */
  268. GNUNET_NAMESTORE_RecordIterator iter;
  269. /**
  270. * Closure for @e iter.
  271. */
  272. void *iter_cls;
  273. /**
  274. * Zone key, NULL if part of record.
  275. */
  276. const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key;
  277. /**
  278. * Number of results still to return (counted down by
  279. * number of results given to iterator).
  280. */
  281. uint64_t limit;
  282. };
  283. /**
  284. * A statement has been run. We should evaluate the result, and if possible
  285. * call the @a iter in @a cls with the result.
  286. *
  287. * @param cls closure of type `struct ParserContext *`
  288. * @param result the postgres result
  289. * @param num_result the number of results in @a result
  290. */
  291. static void
  292. parse_result_call_iterator (void *cls,
  293. PGresult *res,
  294. unsigned int num_results)
  295. {
  296. struct ParserContext *pc = cls;
  297. if (NULL == pc->iter)
  298. return; /* no need to do more work */
  299. for (unsigned int i = 0; i < num_results; i++)
  300. {
  301. uint64_t serial;
  302. void *data;
  303. size_t data_size;
  304. uint32_t record_count;
  305. char *label;
  306. struct GNUNET_CRYPTO_EcdsaPrivateKey zk;
  307. struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
  308. GNUNET_PQ_result_spec_uint64 ("seq", &serial),
  309. GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
  310. GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
  311. GNUNET_PQ_result_spec_string ("label", &label),
  312. GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
  313. GNUNET_PQ_result_spec_end
  314. };
  315. struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
  316. GNUNET_PQ_result_spec_uint64 ("seq", &serial),
  317. GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
  318. GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
  319. GNUNET_PQ_result_spec_string ("label", &label),
  320. GNUNET_PQ_result_spec_end
  321. };
  322. struct GNUNET_PQ_ResultSpec *rs;
  323. rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
  324. if (GNUNET_YES !=
  325. GNUNET_PQ_extract_result (res,
  326. rs,
  327. i))
  328. {
  329. GNUNET_break (0);
  330. return;
  331. }
  332. if (record_count > 64 * 1024)
  333. {
  334. /* sanity check, don't stack allocate far too much just
  335. because database might contain a large value here */
  336. GNUNET_break (0);
  337. GNUNET_PQ_cleanup_result (rs);
  338. return;
  339. }
  340. {
  341. struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (record_count)];
  342. GNUNET_assert (0 != serial);
  343. if (GNUNET_OK !=
  344. GNUNET_GNSRECORD_records_deserialize (data_size,
  345. data,
  346. record_count,
  347. rd))
  348. {
  349. GNUNET_break (0);
  350. GNUNET_PQ_cleanup_result (rs);
  351. return;
  352. }
  353. pc->iter (pc->iter_cls,
  354. serial,
  355. (NULL == pc->zone_key) ? &zk : pc->zone_key,
  356. label,
  357. record_count,
  358. rd);
  359. }
  360. GNUNET_PQ_cleanup_result (rs);
  361. }
  362. pc->limit -= num_results;
  363. }
  364. /**
  365. * Lookup records in the datastore for which we are the authority.
  366. *
  367. * @param cls closure (internal context for the plugin)
  368. * @param zone private key of the zone
  369. * @param label name of the record in the zone
  370. * @param iter function to call with the result
  371. * @param iter_cls closure for @a iter
  372. * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
  373. */
  374. static int
  375. namestore_postgres_lookup_records (void *cls,
  376. const struct
  377. GNUNET_CRYPTO_EcdsaPrivateKey *zone,
  378. const char *label,
  379. GNUNET_NAMESTORE_RecordIterator iter,
  380. void *iter_cls)
  381. {
  382. struct Plugin *plugin = cls;
  383. struct GNUNET_PQ_QueryParam params[] = {
  384. GNUNET_PQ_query_param_auto_from_type (zone),
  385. GNUNET_PQ_query_param_string (label),
  386. GNUNET_PQ_query_param_end
  387. };
  388. struct ParserContext pc;
  389. enum GNUNET_DB_QueryStatus res;
  390. if (NULL == zone)
  391. {
  392. GNUNET_break (0);
  393. return GNUNET_SYSERR;
  394. }
  395. pc.iter = iter;
  396. pc.iter_cls = iter_cls;
  397. pc.zone_key = zone;
  398. res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
  399. "lookup_label",
  400. params,
  401. &parse_result_call_iterator,
  402. &pc);
  403. if (res < 0)
  404. return GNUNET_SYSERR;
  405. if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res)
  406. return GNUNET_NO;
  407. return GNUNET_OK;
  408. }
  409. /**
  410. * Iterate over the results for a particular key and zone in the
  411. * datastore. Will return at most one result to the iterator.
  412. *
  413. * @param cls closure (internal context for the plugin)
  414. * @param zone hash of public key of the zone, NULL to iterate over all zones
  415. * @param serial serial number to exclude in the list of all matching records
  416. * @param limit maximum number of results to fetch
  417. * @param iter function to call with the result
  418. * @param iter_cls closure for @a iter
  419. * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
  420. */
  421. static int
  422. namestore_postgres_iterate_records (void *cls,
  423. const struct
  424. GNUNET_CRYPTO_EcdsaPrivateKey *zone,
  425. uint64_t serial,
  426. uint64_t limit,
  427. GNUNET_NAMESTORE_RecordIterator iter,
  428. void *iter_cls)
  429. {
  430. struct Plugin *plugin = cls;
  431. enum GNUNET_DB_QueryStatus res;
  432. struct ParserContext pc;
  433. pc.iter = iter;
  434. pc.iter_cls = iter_cls;
  435. pc.zone_key = zone;
  436. pc.limit = limit;
  437. if (NULL == zone)
  438. {
  439. struct GNUNET_PQ_QueryParam params_without_zone[] = {
  440. GNUNET_PQ_query_param_uint64 (&serial),
  441. GNUNET_PQ_query_param_uint64 (&limit),
  442. GNUNET_PQ_query_param_end
  443. };
  444. res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
  445. "iterate_all_zones",
  446. params_without_zone,
  447. &parse_result_call_iterator,
  448. &pc);
  449. }
  450. else
  451. {
  452. struct GNUNET_PQ_QueryParam params_with_zone[] = {
  453. GNUNET_PQ_query_param_auto_from_type (zone),
  454. GNUNET_PQ_query_param_uint64 (&serial),
  455. GNUNET_PQ_query_param_uint64 (&limit),
  456. GNUNET_PQ_query_param_end
  457. };
  458. res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
  459. "iterate_zone",
  460. params_with_zone,
  461. &parse_result_call_iterator,
  462. &pc);
  463. }
  464. if (res < 0)
  465. return GNUNET_SYSERR;
  466. if ((GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res) ||
  467. (pc.limit > 0))
  468. return GNUNET_NO;
  469. return GNUNET_OK;
  470. }
  471. /**
  472. * Look for an existing PKEY delegation record for a given public key.
  473. * Returns at most one result to the iterator.
  474. *
  475. * @param cls closure (internal context for the plugin)
  476. * @param zone private key of the zone to look up in, never NULL
  477. * @param value_zone public key of the target zone (value), never NULL
  478. * @param iter function to call with the result
  479. * @param iter_cls closure for @a iter
  480. * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
  481. */
  482. static int
  483. namestore_postgres_zone_to_name (void *cls,
  484. const struct
  485. GNUNET_CRYPTO_EcdsaPrivateKey *zone,
  486. const struct
  487. GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
  488. GNUNET_NAMESTORE_RecordIterator iter,
  489. void *iter_cls)
  490. {
  491. struct Plugin *plugin = cls;
  492. struct GNUNET_PQ_QueryParam params[] = {
  493. GNUNET_PQ_query_param_auto_from_type (zone),
  494. GNUNET_PQ_query_param_auto_from_type (value_zone),
  495. GNUNET_PQ_query_param_end
  496. };
  497. enum GNUNET_DB_QueryStatus res;
  498. struct ParserContext pc;
  499. pc.iter = iter;
  500. pc.iter_cls = iter_cls;
  501. pc.zone_key = zone;
  502. res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
  503. "zone_to_name",
  504. params,
  505. &parse_result_call_iterator,
  506. &pc);
  507. if (res < 0)
  508. return GNUNET_SYSERR;
  509. return GNUNET_OK;
  510. }
  511. /**
  512. * Shutdown database connection and associate data
  513. * structures.
  514. *
  515. * @param plugin the plugin context (state for this module)
  516. */
  517. static void
  518. database_shutdown (struct Plugin *plugin)
  519. {
  520. GNUNET_PQ_disconnect (plugin->dbh);
  521. plugin->dbh = NULL;
  522. }
  523. /**
  524. * Entry point for the plugin.
  525. *
  526. * @param cls the `struct GNUNET_NAMESTORE_PluginEnvironment*`
  527. * @return NULL on error, othrewise the plugin context
  528. */
  529. void *
  530. libgnunet_plugin_namestore_postgres_init (void *cls)
  531. {
  532. static struct Plugin plugin;
  533. const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
  534. struct GNUNET_NAMESTORE_PluginFunctions *api;
  535. if (NULL != plugin.cfg)
  536. return NULL; /* can only initialize once! */
  537. memset (&plugin, 0, sizeof(struct Plugin));
  538. plugin.cfg = cfg;
  539. if (GNUNET_OK != database_setup (&plugin))
  540. {
  541. database_shutdown (&plugin);
  542. return NULL;
  543. }
  544. api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
  545. api->cls = &plugin;
  546. api->store_records = &namestore_postgres_store_records;
  547. api->iterate_records = &namestore_postgres_iterate_records;
  548. api->zone_to_name = &namestore_postgres_zone_to_name;
  549. api->lookup_records = &namestore_postgres_lookup_records;
  550. LOG (GNUNET_ERROR_TYPE_INFO,
  551. "Postgres namestore plugin running\n");
  552. return api;
  553. }
  554. /**
  555. * Exit point from the plugin.
  556. *
  557. * @param cls the plugin context (as returned by "init")
  558. * @return always NULL
  559. */
  560. void *
  561. libgnunet_plugin_namestore_postgres_done (void *cls)
  562. {
  563. struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
  564. struct Plugin *plugin = api->cls;
  565. database_shutdown (plugin);
  566. plugin->cfg = NULL;
  567. GNUNET_free (api);
  568. LOG (GNUNET_ERROR_TYPE_DEBUG,
  569. "Postgres namestore plugin is finished\n");
  570. return NULL;
  571. }
  572. /* end of plugin_namestore_postgres.c */