diff options
Diffstat (limited to 'src/namecache/plugin_namecache_postgres.c')
-rw-r--r-- | src/namecache/plugin_namecache_postgres.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/namecache/plugin_namecache_postgres.c b/src/namecache/plugin_namecache_postgres.c new file mode 100644 index 000000000..72cfcf243 --- /dev/null +++ b/src/namecache/plugin_namecache_postgres.c | |||
@@ -0,0 +1,440 @@ | |||
1 | /* | ||
2 | * This file is part of GNUnet | ||
3 | * (C) 2009-2013 Christian Grothoff (and other contributing authors) | ||
4 | * | ||
5 | * GNUnet is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published | ||
7 | * by the Free Software Foundation; either version 3, or (at your | ||
8 | * 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 | * General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with GNUnet; see the file COPYING. If not, write to the | ||
17 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | ||
18 | * Boston, MA 02111-1307, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file namecache/plugin_namecache_postgres.c | ||
23 | * @brief postgres-based namecache backend | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | #include "platform.h" | ||
27 | #include "gnunet_namecache_plugin.h" | ||
28 | #include "gnunet_namecache_service.h" | ||
29 | #include "gnunet_gnsrecord_lib.h" | ||
30 | #include "gnunet_postgres_lib.h" | ||
31 | #include "namecache.h" | ||
32 | |||
33 | |||
34 | /** | ||
35 | * After how many ms "busy" should a DB operation fail for good? | ||
36 | * A low value makes sure that we are more responsive to requests | ||
37 | * (especially PUTs). A high value guarantees a higher success | ||
38 | * rate (SELECTs in iterate can take several seconds despite LIMIT=1). | ||
39 | * | ||
40 | * The default value of 1s should ensure that users do not experience | ||
41 | * huge latencies while at the same time allowing operations to succeed | ||
42 | * with reasonable probability. | ||
43 | */ | ||
44 | #define BUSY_TIMEOUT_MS 1000 | ||
45 | |||
46 | |||
47 | /** | ||
48 | * Log an error message at log-level 'level' that indicates | ||
49 | * a failure of the command 'cmd' on file 'filename' | ||
50 | * with the message given by strerror(errno). | ||
51 | */ | ||
52 | #define LOG_POSTGRES(db, level, cmd) do { GNUNET_log_from (level, "namecache-postgres", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0) | ||
53 | |||
54 | #define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__) | ||
55 | |||
56 | |||
57 | /** | ||
58 | * Context for all functions in this plugin. | ||
59 | */ | ||
60 | struct Plugin | ||
61 | { | ||
62 | |||
63 | const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
64 | |||
65 | /** | ||
66 | * Native Postgres database handle. | ||
67 | */ | ||
68 | PGconn *dbh; | ||
69 | |||
70 | }; | ||
71 | |||
72 | |||
73 | /** | ||
74 | * Create our database indices. | ||
75 | * | ||
76 | * @param dbh handle to the database | ||
77 | */ | ||
78 | static void | ||
79 | create_indices (PGconn * dbh) | ||
80 | { | ||
81 | /* create indices */ | ||
82 | if ( (GNUNET_OK != | ||
83 | GNUNET_POSTGRES_exec (dbh, | ||
84 | "CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)")) || | ||
85 | (GNUNET_OK != | ||
86 | GNUNET_POSTGRES_exec (dbh, | ||
87 | "CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)")) ) | ||
88 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
89 | _("Failed to create indices\n")); | ||
90 | } | ||
91 | |||
92 | |||
93 | /** | ||
94 | * Initialize the database connections and associated | ||
95 | * data structures (create tables and indices | ||
96 | * as needed as well). | ||
97 | * | ||
98 | * @param plugin the plugin context (state for this module) | ||
99 | * @return GNUNET_OK on success | ||
100 | */ | ||
101 | static int | ||
102 | database_setup (struct Plugin *plugin) | ||
103 | { | ||
104 | PGresult *res; | ||
105 | |||
106 | plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg, | ||
107 | "namecache-postgres"); | ||
108 | if (NULL == plugin->dbh) | ||
109 | return GNUNET_SYSERR; | ||
110 | if (GNUNET_YES == | ||
111 | GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg, | ||
112 | "namecache-postgres", | ||
113 | "TEMPORARY_TABLE")) | ||
114 | { | ||
115 | res = | ||
116 | PQexec (plugin->dbh, | ||
117 | "CREATE TEMPORARY TABLE ns096blocks (" | ||
118 | " query BYTEA NOT NULL DEFAULT ''," | ||
119 | " block BYTEA NOT NULL DEFAULT ''," | ||
120 | " expiration_time BIGINT NOT NULL DEFAULT 0" | ||
121 | ")" "WITH OIDS"); | ||
122 | } | ||
123 | else | ||
124 | { | ||
125 | res = | ||
126 | PQexec (plugin->dbh, | ||
127 | "CREATE TABLE ns096blocks (" | ||
128 | " query BYTEA NOT NULL DEFAULT ''," | ||
129 | " block BYTEA NOT NULL DEFAULT ''," | ||
130 | " expiration_time BIGINT NOT NULL DEFAULT 0" | ||
131 | ")" "WITH OIDS"); | ||
132 | } | ||
133 | if ( (NULL == res) || | ||
134 | ((PQresultStatus (res) != PGRES_COMMAND_OK) && | ||
135 | (0 != strcmp ("42P07", /* duplicate table */ | ||
136 | PQresultErrorField | ||
137 | (res, | ||
138 | PG_DIAG_SQLSTATE))))) | ||
139 | { | ||
140 | (void) GNUNET_POSTGRES_check_result (plugin->dbh, res, | ||
141 | PGRES_COMMAND_OK, "CREATE TABLE", | ||
142 | "ns096blocks"); | ||
143 | PQfinish (plugin->dbh); | ||
144 | plugin->dbh = NULL; | ||
145 | return GNUNET_SYSERR; | ||
146 | } | ||
147 | if (PQresultStatus (res) == PGRES_COMMAND_OK) | ||
148 | create_indices (plugin->dbh); | ||
149 | PQclear (res); | ||
150 | |||
151 | if ((GNUNET_OK != | ||
152 | GNUNET_POSTGRES_prepare (plugin->dbh, | ||
153 | "cache_block", | ||
154 | "INSERT INTO ns096blocks (query, block, expiration_time) VALUES " | ||
155 | "($1, $2, $3)", 3)) || | ||
156 | (GNUNET_OK != | ||
157 | GNUNET_POSTGRES_prepare (plugin->dbh, | ||
158 | "expire_blocks", | ||
159 | "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) || | ||
160 | (GNUNET_OK != | ||
161 | GNUNET_POSTGRES_prepare (plugin->dbh, | ||
162 | "delete_block", | ||
163 | "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) || | ||
164 | (GNUNET_OK != | ||
165 | GNUNET_POSTGRES_prepare (plugin->dbh, | ||
166 | "lookup_block", | ||
167 | "SELECT block FROM ns096blocks WHERE query=$1" | ||
168 | " ORDER BY expiration_time DESC LIMIT 1", 1))) | ||
169 | { | ||
170 | PQfinish (plugin->dbh); | ||
171 | plugin->dbh = NULL; | ||
172 | return GNUNET_SYSERR; | ||
173 | } | ||
174 | return GNUNET_OK; | ||
175 | } | ||
176 | |||
177 | |||
178 | /** | ||
179 | * Removes any expired block. | ||
180 | * | ||
181 | * @param plugin the plugin | ||
182 | */ | ||
183 | static void | ||
184 | namecache_postgres_expire_blocks (struct Plugin *plugin) | ||
185 | { | ||
186 | struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); | ||
187 | struct GNUNET_TIME_AbsoluteNBO now_be = GNUNET_TIME_absolute_hton (now); | ||
188 | const char *paramValues[] = { | ||
189 | (const char *) &now_be | ||
190 | }; | ||
191 | int paramLengths[] = { | ||
192 | sizeof (now_be) | ||
193 | }; | ||
194 | const int paramFormats[] = { 1 }; | ||
195 | PGresult *res; | ||
196 | |||
197 | res = | ||
198 | PQexecPrepared (plugin->dbh, "expire_blocks", 1, | ||
199 | paramValues, paramLengths, paramFormats, 1); | ||
200 | if (GNUNET_OK != | ||
201 | GNUNET_POSTGRES_check_result (plugin->dbh, | ||
202 | res, | ||
203 | PGRES_COMMAND_OK, | ||
204 | "PQexecPrepared", | ||
205 | "expire_blocks")) | ||
206 | return; | ||
207 | PQclear (res); | ||
208 | } | ||
209 | |||
210 | |||
211 | /** | ||
212 | * Delete older block in the datastore. | ||
213 | * | ||
214 | * @param the plugin | ||
215 | * @param query query for the block | ||
216 | * @param expiration time how old does the block have to be for deletion | ||
217 | * @return #GNUNET_OK on success, else #GNUNET_SYSERR | ||
218 | */ | ||
219 | static void | ||
220 | delete_old_block (struct Plugin *plugin, | ||
221 | const struct GNUNET_HashCode *query, | ||
222 | struct GNUNET_TIME_AbsoluteNBO expiration_time) | ||
223 | { | ||
224 | const char *paramValues[] = { | ||
225 | (const char *) query, | ||
226 | (const char *) &expiration_time | ||
227 | }; | ||
228 | int paramLengths[] = { | ||
229 | sizeof (*query), | ||
230 | sizeof (expiration_time) | ||
231 | }; | ||
232 | const int paramFormats[] = { 1, 1 }; | ||
233 | PGresult *res; | ||
234 | |||
235 | res = | ||
236 | PQexecPrepared (plugin->dbh, "delete_block", 2, | ||
237 | paramValues, paramLengths, paramFormats, 1); | ||
238 | if (GNUNET_OK != | ||
239 | GNUNET_POSTGRES_check_result (plugin->dbh, | ||
240 | res, | ||
241 | PGRES_COMMAND_OK, | ||
242 | "PQexecPrepared", | ||
243 | "delete_block")) | ||
244 | return; | ||
245 | PQclear (res); | ||
246 | } | ||
247 | |||
248 | |||
249 | /** | ||
250 | * Cache a block in the datastore. | ||
251 | * | ||
252 | * @param cls closure (internal context for the plugin) | ||
253 | * @param block block to cache | ||
254 | * @return #GNUNET_OK on success, else #GNUNET_SYSERR | ||
255 | */ | ||
256 | static int | ||
257 | namecache_postgres_cache_block (void *cls, | ||
258 | const struct GNUNET_NAMESTORE_Block *block) | ||
259 | { | ||
260 | struct Plugin *plugin = cls; | ||
261 | struct GNUNET_HashCode query; | ||
262 | size_t block_size = ntohl (block->purpose.size) + | ||
263 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + | ||
264 | sizeof (struct GNUNET_CRYPTO_EcdsaSignature); | ||
265 | const char *paramValues[] = { | ||
266 | (const char *) &query, | ||
267 | (const char *) block, | ||
268 | (const char *) &block->expiration_time | ||
269 | }; | ||
270 | int paramLengths[] = { | ||
271 | sizeof (query), | ||
272 | (int) block_size, | ||
273 | sizeof (block->expiration_time) | ||
274 | }; | ||
275 | const int paramFormats[] = { 1, 1, 1 }; | ||
276 | PGresult *res; | ||
277 | |||
278 | namecache_postgres_expire_blocks (plugin); | ||
279 | GNUNET_CRYPTO_hash (&block->derived_key, | ||
280 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), | ||
281 | &query); | ||
282 | if (block_size > 64 * 65536) | ||
283 | { | ||
284 | GNUNET_break (0); | ||
285 | return GNUNET_SYSERR; | ||
286 | } | ||
287 | delete_old_block (plugin, &query, block->expiration_time); | ||
288 | |||
289 | res = | ||
290 | PQexecPrepared (plugin->dbh, "cache_block", 3, | ||
291 | paramValues, paramLengths, paramFormats, 1); | ||
292 | if (GNUNET_OK != | ||
293 | GNUNET_POSTGRES_check_result (plugin->dbh, | ||
294 | res, | ||
295 | PGRES_COMMAND_OK, | ||
296 | "PQexecPrepared", | ||
297 | "cache_block")) | ||
298 | return GNUNET_SYSERR; | ||
299 | PQclear (res); | ||
300 | return GNUNET_OK; | ||
301 | } | ||
302 | |||
303 | |||
304 | /** | ||
305 | * Get the block for a particular zone and label in the | ||
306 | * datastore. Will return at most one result to the iterator. | ||
307 | * | ||
308 | * @param cls closure (internal context for the plugin) | ||
309 | * @param query hash of public key derived from the zone and the label | ||
310 | * @param iter function to call with the result | ||
311 | * @param iter_cls closure for @a iter | ||
312 | * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error | ||
313 | */ | ||
314 | static int | ||
315 | namecache_postgres_lookup_block (void *cls, | ||
316 | const struct GNUNET_HashCode *query, | ||
317 | GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls) | ||
318 | { | ||
319 | struct Plugin *plugin = cls; | ||
320 | const char *paramValues[] = { | ||
321 | (const char *) query | ||
322 | }; | ||
323 | int paramLengths[] = { | ||
324 | sizeof (*query) | ||
325 | }; | ||
326 | const int paramFormats[] = { 1 }; | ||
327 | PGresult *res; | ||
328 | unsigned int cnt; | ||
329 | size_t bsize; | ||
330 | const struct GNUNET_NAMESTORE_Block *block; | ||
331 | |||
332 | res = PQexecPrepared (plugin->dbh, | ||
333 | "lookup_block", 1, | ||
334 | paramValues, paramLengths, paramFormats, | ||
335 | 1); | ||
336 | if (GNUNET_OK != | ||
337 | GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, | ||
338 | "PQexecPrepared", | ||
339 | "lookup_block")) | ||
340 | { | ||
341 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
342 | "Failing lookup (postgres error)\n"); | ||
343 | return GNUNET_SYSERR; | ||
344 | } | ||
345 | if (0 == (cnt = PQntuples (res))) | ||
346 | { | ||
347 | /* no result */ | ||
348 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
349 | "Ending iteration (no more results)\n"); | ||
350 | PQclear (res); | ||
351 | return GNUNET_NO; | ||
352 | } | ||
353 | GNUNET_assert (1 == cnt); | ||
354 | GNUNET_assert (1 != PQnfields (res)); | ||
355 | bsize = PQgetlength (res, 0, 0); | ||
356 | block = (const struct GNUNET_NAMESTORE_Block *) PQgetvalue (res, 0, 0); | ||
357 | if ( (bsize < sizeof (*block)) || | ||
358 | (bsize != ntohl (block->purpose.size) + | ||
359 | sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + | ||
360 | sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) ) | ||
361 | { | ||
362 | GNUNET_break (0); | ||
363 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
364 | "Failing lookup (corrupt block)\n"); | ||
365 | PQclear (res); | ||
366 | return GNUNET_SYSERR; | ||
367 | } | ||
368 | iter (iter_cls, block); | ||
369 | PQclear (res); | ||
370 | return GNUNET_OK; | ||
371 | } | ||
372 | |||
373 | |||
374 | /** | ||
375 | * Shutdown database connection and associate data | ||
376 | * structures. | ||
377 | * | ||
378 | * @param plugin the plugin context (state for this module) | ||
379 | */ | ||
380 | static void | ||
381 | database_shutdown (struct Plugin *plugin) | ||
382 | { | ||
383 | PQfinish (plugin->dbh); | ||
384 | plugin->dbh = NULL; | ||
385 | } | ||
386 | |||
387 | |||
388 | /** | ||
389 | * Entry point for the plugin. | ||
390 | * | ||
391 | * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*" | ||
392 | * @return NULL on error, othrewise the plugin context | ||
393 | */ | ||
394 | void * | ||
395 | libgnunet_plugin_namecache_postgres_init (void *cls) | ||
396 | { | ||
397 | static struct Plugin plugin; | ||
398 | const struct GNUNET_CONFIGURATION_Handle *cfg = cls; | ||
399 | struct GNUNET_NAMECACHE_PluginFunctions *api; | ||
400 | |||
401 | if (NULL != plugin.cfg) | ||
402 | return NULL; /* can only initialize once! */ | ||
403 | memset (&plugin, 0, sizeof (struct Plugin)); | ||
404 | plugin.cfg = cfg; | ||
405 | if (GNUNET_OK != database_setup (&plugin)) | ||
406 | { | ||
407 | database_shutdown (&plugin); | ||
408 | return NULL; | ||
409 | } | ||
410 | api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions); | ||
411 | api->cls = &plugin; | ||
412 | api->cache_block = &namecache_postgres_cache_block; | ||
413 | api->lookup_block = &namecache_postgres_lookup_block; | ||
414 | LOG (GNUNET_ERROR_TYPE_INFO, | ||
415 | _("Postgres database running\n")); | ||
416 | return api; | ||
417 | } | ||
418 | |||
419 | |||
420 | /** | ||
421 | * Exit point from the plugin. | ||
422 | * | ||
423 | * @param cls the plugin context (as returned by "init") | ||
424 | * @return always NULL | ||
425 | */ | ||
426 | void * | ||
427 | libgnunet_plugin_namecache_postgres_done (void *cls) | ||
428 | { | ||
429 | struct GNUNET_NAMECACHE_PluginFunctions *api = cls; | ||
430 | struct Plugin *plugin = api->cls; | ||
431 | |||
432 | database_shutdown (plugin); | ||
433 | plugin->cfg = NULL; | ||
434 | GNUNET_free (api); | ||
435 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
436 | "postgres plugin is finished\n"); | ||
437 | return NULL; | ||
438 | } | ||
439 | |||
440 | /* end of plugin_namecache_postgres.c */ | ||