diff options
Diffstat (limited to 'src/plugin/peerstore')
-rw-r--r-- | src/plugin/peerstore/Makefile.am | 45 | ||||
-rw-r--r-- | src/plugin/peerstore/meson.build | 9 | ||||
-rw-r--r-- | src/plugin/peerstore/plugin_peerstore_sqlite.c | 743 | ||||
-rw-r--r-- | src/plugin/peerstore/test_plugin_peerstore.c | 226 | ||||
-rw-r--r-- | src/plugin/peerstore/test_plugin_peerstore_sqlite.conf | 2 |
5 files changed, 1025 insertions, 0 deletions
diff --git a/src/plugin/peerstore/Makefile.am b/src/plugin/peerstore/Makefile.am new file mode 100644 index 000000000..5e10b706f --- /dev/null +++ b/src/plugin/peerstore/Makefile.am | |||
@@ -0,0 +1,45 @@ | |||
1 | # This Makefile.am is in the public domain | ||
2 | AM_CPPFLAGS = -I$(top_srcdir)/src/include | ||
3 | |||
4 | plugindir = $(libdir)/gnunet | ||
5 | |||
6 | pkgcfgdir= $(pkgdatadir)/config.d/ | ||
7 | |||
8 | libexecdir= $(pkglibdir)/libexec/ | ||
9 | |||
10 | if USE_COVERAGE | ||
11 | AM_CFLAGS = -fprofile-arcs -ftest-coverage | ||
12 | endif | ||
13 | |||
14 | if HAVE_SQLITE | ||
15 | SQLITE_PLUGIN = libgnunet_plugin_peerstore_sqlite.la | ||
16 | SQLITE_TESTS = test_plugin_peerstore_sqlite | ||
17 | libgnunet_plugin_peerstore_sqlite_la_SOURCES = \ | ||
18 | plugin_peerstore_sqlite.c | ||
19 | libgnunet_plugin_peerstore_sqlite_la_LIBADD = \ | ||
20 | $(top_builddir)/src/lib/sq/libgnunetsq.la \ | ||
21 | $(top_builddir)/src/lib/util/libgnunetutil.la \ | ||
22 | $(XLIBS) -lsqlite3 \ | ||
23 | $(LTLIBINTL) | ||
24 | libgnunet_plugin_peerstore_sqlite_la_LDFLAGS = \ | ||
25 | $(GN_PLUGIN_LDFLAGS) | ||
26 | endif | ||
27 | |||
28 | plugin_LTLIBRARIES = \ | ||
29 | $(SQLITE_PLUGIN) | ||
30 | |||
31 | test_plugin_peerstore_sqlite_SOURCES = \ | ||
32 | test_plugin_peerstore.c | ||
33 | test_plugin_peerstore_sqlite_LDADD = \ | ||
34 | $(top_builddir)/src/lib/util/libgnunetutil.la | ||
35 | |||
36 | check_PROGRAMS = \ | ||
37 | $(SQLITE_TESTS) | ||
38 | |||
39 | EXTRA_DIST = \ | ||
40 | test_plugin_peerstore_sqlite.conf | ||
41 | |||
42 | if ENABLE_TEST_RUN | ||
43 | AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; | ||
44 | TESTS = $(check_PROGRAMS) | ||
45 | endif | ||
diff --git a/src/plugin/peerstore/meson.build b/src/plugin/peerstore/meson.build new file mode 100644 index 000000000..1d22ca798 --- /dev/null +++ b/src/plugin/peerstore/meson.build | |||
@@ -0,0 +1,9 @@ | |||
1 | shared_module('gnunet_plugin_peerstore_sqlite', | ||
2 | ['plugin_peerstore_sqlite.c'], | ||
3 | dependencies: [libgnunetutil_dep, | ||
4 | libgnunetsq_dep, | ||
5 | sqlite_dep], | ||
6 | include_directories: [incdir, | ||
7 | configuration_inc], | ||
8 | install: true, | ||
9 | install_dir: get_option('libdir')/'gnunet') | ||
diff --git a/src/plugin/peerstore/plugin_peerstore_sqlite.c b/src/plugin/peerstore/plugin_peerstore_sqlite.c new file mode 100644 index 000000000..c7132bd85 --- /dev/null +++ b/src/plugin/peerstore/plugin_peerstore_sqlite.c | |||
@@ -0,0 +1,743 @@ | |||
1 | /* | ||
2 | * This file is part of GNUnet | ||
3 | * Copyright (C) 2013, 2017 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 | /** | ||
22 | * @file peerstore/plugin_peerstore_sqlite.c | ||
23 | * @brief sqlite-based peerstore backend | ||
24 | * @author Omar Tarabai | ||
25 | * @author Christian Grothoff | ||
26 | */ | ||
27 | |||
28 | #include "platform.h" | ||
29 | #include "gnunet_peerstore_plugin.h" | ||
30 | #include "gnunet_peerstore_service.h" | ||
31 | #include "gnunet_sq_lib.h" | ||
32 | #include "../../service/peerstore/peerstore.h" | ||
33 | #include <sqlite3.h> | ||
34 | |||
35 | /** | ||
36 | * After how many ms "busy" should a DB operation fail for good? A | ||
37 | * low value makes sure that we are more responsive to requests | ||
38 | * (especially PUTs). A high value guarantees a higher success rate | ||
39 | * (SELECTs in iterate can take several seconds despite LIMIT=1). | ||
40 | * | ||
41 | * The default value of 1s should ensure that users do not experience | ||
42 | * huge latencies while at the same time allowing operations to | ||
43 | * succeed with reasonable probability. | ||
44 | */ | ||
45 | #define BUSY_TIMEOUT_MS 1000 | ||
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_SQLITE(db, level, cmd) do { GNUNET_log_from (level, \ | ||
53 | "peerstore-sqlite", _ ( \ | ||
54 | "`%s' failed at %s:%d with error: %s\n"), \ | ||
55 | cmd, \ | ||
56 | __FILE__, __LINE__, \ | ||
57 | sqlite3_errmsg ( \ | ||
58 | db->dbh)); \ | ||
59 | } while (0) | ||
60 | |||
61 | #define LOG(kind, ...) GNUNET_log_from (kind, "peerstore-sqlite", __VA_ARGS__) | ||
62 | |||
63 | /** | ||
64 | * Context for all functions in this plugin. | ||
65 | */ | ||
66 | struct Plugin | ||
67 | { | ||
68 | /** | ||
69 | * Configuration handle | ||
70 | */ | ||
71 | const struct GNUNET_CONFIGURATION_Handle *cfg; | ||
72 | |||
73 | /** | ||
74 | * Database filename. | ||
75 | */ | ||
76 | char *fn; | ||
77 | |||
78 | /** | ||
79 | * Native SQLite database handle. | ||
80 | */ | ||
81 | sqlite3 *dbh; | ||
82 | |||
83 | /** | ||
84 | * Precompiled SQL for inserting into peerstoredata | ||
85 | */ | ||
86 | sqlite3_stmt *insert_peerstoredata; | ||
87 | |||
88 | /** | ||
89 | * Precompiled SQL for selecting from peerstoredata | ||
90 | */ | ||
91 | sqlite3_stmt *select_peerstoredata; | ||
92 | |||
93 | /** | ||
94 | * Precompiled SQL for selecting from peerstoredata | ||
95 | */ | ||
96 | sqlite3_stmt *select_peerstoredata_by_pid; | ||
97 | |||
98 | /** | ||
99 | * Precompiled SQL for selecting from peerstoredata | ||
100 | */ | ||
101 | sqlite3_stmt *select_peerstoredata_by_key; | ||
102 | |||
103 | /** | ||
104 | * Precompiled SQL for selecting from peerstoredata | ||
105 | */ | ||
106 | sqlite3_stmt *select_peerstoredata_by_all; | ||
107 | |||
108 | /** | ||
109 | * Precompiled SQL for deleting expired | ||
110 | * records from peerstoredata | ||
111 | */ | ||
112 | sqlite3_stmt *expire_peerstoredata; | ||
113 | |||
114 | /** | ||
115 | * Precompiled SQL for deleting records | ||
116 | * with given key | ||
117 | */ | ||
118 | sqlite3_stmt *delete_peerstoredata; | ||
119 | }; | ||
120 | |||
121 | |||
122 | /** | ||
123 | * Delete records with the given key | ||
124 | * | ||
125 | * @param cls closure (internal context for the plugin) | ||
126 | * @param sub_system name of sub system | ||
127 | * @param peer Peer identity (can be NULL) | ||
128 | * @param key entry key string (can be NULL) | ||
129 | * @return number of deleted records, #GNUNE_SYSERR on error | ||
130 | */ | ||
131 | static int | ||
132 | peerstore_sqlite_delete_records (void *cls, | ||
133 | const char *sub_system, | ||
134 | const struct GNUNET_PeerIdentity *peer, | ||
135 | const char *key) | ||
136 | { | ||
137 | struct Plugin *plugin = cls; | ||
138 | sqlite3_stmt *stmt = plugin->delete_peerstoredata; | ||
139 | struct GNUNET_SQ_QueryParam params[] = { | ||
140 | GNUNET_SQ_query_param_string (sub_system), | ||
141 | GNUNET_SQ_query_param_auto_from_type (peer), | ||
142 | GNUNET_SQ_query_param_string (key), | ||
143 | GNUNET_SQ_query_param_end | ||
144 | }; | ||
145 | int ret; | ||
146 | |||
147 | if (GNUNET_OK != | ||
148 | GNUNET_SQ_bind (stmt, | ||
149 | params)) | ||
150 | { | ||
151 | LOG_SQLITE (plugin, | ||
152 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
153 | "sqlite3_bind"); | ||
154 | GNUNET_SQ_reset (plugin->dbh, | ||
155 | stmt); | ||
156 | return GNUNET_SYSERR; | ||
157 | } | ||
158 | if (SQLITE_DONE != | ||
159 | sqlite3_step (stmt)) | ||
160 | { | ||
161 | LOG_SQLITE (plugin, | ||
162 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
163 | "sqlite3_step"); | ||
164 | ret = GNUNET_SYSERR; | ||
165 | } | ||
166 | else | ||
167 | { | ||
168 | ret = sqlite3_changes (plugin->dbh); | ||
169 | } | ||
170 | GNUNET_SQ_reset (plugin->dbh, | ||
171 | stmt); | ||
172 | return ret; | ||
173 | } | ||
174 | |||
175 | |||
176 | /** | ||
177 | * Delete expired records (expiry < now) | ||
178 | * | ||
179 | * @param cls closure (internal context for the plugin) | ||
180 | * @param now time to use as reference | ||
181 | * @param cont continuation called with the number of records expired | ||
182 | * @param cont_cls continuation closure | ||
183 | * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not | ||
184 | * called | ||
185 | */ | ||
186 | static int | ||
187 | peerstore_sqlite_expire_records (void *cls, struct GNUNET_TIME_Absolute now, | ||
188 | GNUNET_PEERSTORE_Continuation cont, | ||
189 | void *cont_cls) | ||
190 | { | ||
191 | struct Plugin *plugin = cls; | ||
192 | sqlite3_stmt *stmt = plugin->expire_peerstoredata; | ||
193 | struct GNUNET_SQ_QueryParam params[] = { | ||
194 | GNUNET_SQ_query_param_absolute_time (&now), | ||
195 | GNUNET_SQ_query_param_end | ||
196 | }; | ||
197 | |||
198 | if (GNUNET_OK != | ||
199 | GNUNET_SQ_bind (stmt, | ||
200 | params)) | ||
201 | { | ||
202 | LOG_SQLITE (plugin, | ||
203 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
204 | "sqlite3_bind"); | ||
205 | GNUNET_SQ_reset (plugin->dbh, | ||
206 | stmt); | ||
207 | return GNUNET_SYSERR; | ||
208 | } | ||
209 | if (SQLITE_DONE != sqlite3_step (stmt)) | ||
210 | { | ||
211 | LOG_SQLITE (plugin, | ||
212 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
213 | "sqlite3_step"); | ||
214 | GNUNET_SQ_reset (plugin->dbh, | ||
215 | stmt); | ||
216 | return GNUNET_SYSERR; | ||
217 | } | ||
218 | if (NULL != cont) | ||
219 | cont (cont_cls, | ||
220 | sqlite3_changes (plugin->dbh)); | ||
221 | GNUNET_SQ_reset (plugin->dbh, | ||
222 | stmt); | ||
223 | return GNUNET_OK; | ||
224 | } | ||
225 | |||
226 | |||
227 | /** | ||
228 | * Iterate over the records given an optional peer id | ||
229 | * and/or key. | ||
230 | * | ||
231 | * @param cls closure (internal context for the plugin) | ||
232 | * @param sub_system name of sub system | ||
233 | * @param peer Peer identity (can be NULL) | ||
234 | * @param key entry key string (can be NULL) | ||
235 | * @param iter function to call asynchronously with the results, terminated | ||
236 | * by a NULL result | ||
237 | * @param iter_cls closure for @a iter | ||
238 | * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not | ||
239 | * called | ||
240 | */ | ||
241 | static int | ||
242 | peerstore_sqlite_iterate_records (void *cls, | ||
243 | const char *sub_system, | ||
244 | const struct GNUNET_PeerIdentity *peer, | ||
245 | const char *key, | ||
246 | uint64_t serial, | ||
247 | uint64_t limit, | ||
248 | GNUNET_PEERSTORE_PluginProcessor iter, | ||
249 | void *iter_cls) | ||
250 | { | ||
251 | struct Plugin *plugin = cls; | ||
252 | sqlite3_stmt *stmt; | ||
253 | int err = 0; | ||
254 | int sret; | ||
255 | int ret; | ||
256 | uint64_t seq; | ||
257 | struct GNUNET_PEERSTORE_Record rec; | ||
258 | |||
259 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
260 | "Executing iterate request on sqlite db.\n"); | ||
261 | if (NULL == peer) | ||
262 | { | ||
263 | if (NULL == key) | ||
264 | { | ||
265 | struct GNUNET_SQ_QueryParam params[] = { | ||
266 | GNUNET_SQ_query_param_string (sub_system), | ||
267 | GNUNET_SQ_query_param_uint64 (&serial), | ||
268 | GNUNET_SQ_query_param_uint64 (&limit), | ||
269 | GNUNET_SQ_query_param_end | ||
270 | }; | ||
271 | |||
272 | stmt = plugin->select_peerstoredata; | ||
273 | err = GNUNET_SQ_bind (stmt, | ||
274 | params); | ||
275 | } | ||
276 | else | ||
277 | { | ||
278 | struct GNUNET_SQ_QueryParam params[] = { | ||
279 | GNUNET_SQ_query_param_string (sub_system), | ||
280 | GNUNET_SQ_query_param_string (key), | ||
281 | GNUNET_SQ_query_param_uint64 (&serial), | ||
282 | GNUNET_SQ_query_param_uint64 (&limit), | ||
283 | GNUNET_SQ_query_param_end | ||
284 | }; | ||
285 | |||
286 | stmt = plugin->select_peerstoredata_by_key; | ||
287 | err = GNUNET_SQ_bind (stmt, | ||
288 | params); | ||
289 | } | ||
290 | } | ||
291 | else | ||
292 | { | ||
293 | if (NULL == key) | ||
294 | { | ||
295 | struct GNUNET_SQ_QueryParam params[] = { | ||
296 | GNUNET_SQ_query_param_string (sub_system), | ||
297 | GNUNET_SQ_query_param_auto_from_type (peer), | ||
298 | GNUNET_SQ_query_param_uint64 (&serial), | ||
299 | GNUNET_SQ_query_param_uint64 (&limit), | ||
300 | GNUNET_SQ_query_param_end | ||
301 | }; | ||
302 | |||
303 | stmt = plugin->select_peerstoredata_by_pid; | ||
304 | err = GNUNET_SQ_bind (stmt, | ||
305 | params); | ||
306 | } | ||
307 | else | ||
308 | { | ||
309 | struct GNUNET_SQ_QueryParam params[] = { | ||
310 | GNUNET_SQ_query_param_string (sub_system), | ||
311 | GNUNET_SQ_query_param_auto_from_type (peer), | ||
312 | GNUNET_SQ_query_param_string (key), | ||
313 | GNUNET_SQ_query_param_uint64 (&serial), | ||
314 | GNUNET_SQ_query_param_uint64 (&limit), | ||
315 | GNUNET_SQ_query_param_end | ||
316 | }; | ||
317 | |||
318 | stmt = plugin->select_peerstoredata_by_all; | ||
319 | err = GNUNET_SQ_bind (stmt, | ||
320 | params); | ||
321 | } | ||
322 | } | ||
323 | |||
324 | if (GNUNET_OK != err) | ||
325 | { | ||
326 | LOG_SQLITE (plugin, | ||
327 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
328 | "sqlite3_bind_XXXX"); | ||
329 | GNUNET_SQ_reset (plugin->dbh, | ||
330 | stmt); | ||
331 | return GNUNET_SYSERR; | ||
332 | } | ||
333 | |||
334 | err = 0; | ||
335 | ret = GNUNET_OK; | ||
336 | for (uint64_t i = 0; i < limit; i++) | ||
337 | { | ||
338 | sret = sqlite3_step (stmt); | ||
339 | if (SQLITE_DONE == sret) | ||
340 | { | ||
341 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
342 | "Iteration done (no results)\n"); | ||
343 | ret = GNUNET_NO; | ||
344 | break; | ||
345 | } | ||
346 | if (SQLITE_ROW != sret) | ||
347 | { | ||
348 | LOG_SQLITE (plugin, | ||
349 | GNUNET_ERROR_TYPE_ERROR, | ||
350 | "sqlite_step"); | ||
351 | ret = GNUNET_SYSERR; | ||
352 | break; | ||
353 | } | ||
354 | { | ||
355 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
356 | "Returning a matched record.\n"); | ||
357 | struct GNUNET_SQ_ResultSpec rs[] = { | ||
358 | GNUNET_SQ_result_spec_uint64 (&seq), | ||
359 | GNUNET_SQ_result_spec_string (&rec.sub_system), | ||
360 | GNUNET_SQ_result_spec_auto_from_type (&rec.peer), | ||
361 | GNUNET_SQ_result_spec_string (&rec.key), | ||
362 | GNUNET_SQ_result_spec_variable_size (&rec.value, &rec.value_size), | ||
363 | GNUNET_SQ_result_spec_absolute_time (&rec.expiry), | ||
364 | GNUNET_SQ_result_spec_end | ||
365 | }; | ||
366 | |||
367 | if (GNUNET_OK != | ||
368 | GNUNET_SQ_extract_result (stmt, | ||
369 | rs)) | ||
370 | { | ||
371 | GNUNET_break (0); | ||
372 | break; | ||
373 | } | ||
374 | if (NULL != iter) | ||
375 | iter (iter_cls, | ||
376 | seq, | ||
377 | &rec, | ||
378 | NULL); | ||
379 | GNUNET_SQ_cleanup_result (rs); | ||
380 | } | ||
381 | } | ||
382 | GNUNET_SQ_reset (plugin->dbh, | ||
383 | stmt); | ||
384 | return ret; | ||
385 | } | ||
386 | |||
387 | |||
388 | /** | ||
389 | * Store a record in the peerstore. | ||
390 | * Key is the combination of sub system and peer identity. | ||
391 | * One key can store multiple values. | ||
392 | * | ||
393 | * @param cls closure (internal context for the plugin) | ||
394 | * @param sub_system name of the GNUnet sub system responsible | ||
395 | * @param peer peer identity | ||
396 | * @param key record key string | ||
397 | * @param value value to be stored | ||
398 | * @param size size of value to be stored | ||
399 | * @param expiry absolute time after which the record is (possibly) deleted | ||
400 | * @param options options related to the store operation | ||
401 | * @param cont continuation called when record is stored | ||
402 | * @param cont_cls continuation closure | ||
403 | * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called | ||
404 | */ | ||
405 | static int | ||
406 | peerstore_sqlite_store_record (void *cls, | ||
407 | const char *sub_system, | ||
408 | const struct GNUNET_PeerIdentity *peer, | ||
409 | const char *key, | ||
410 | const void *value, | ||
411 | size_t size, | ||
412 | struct GNUNET_TIME_Absolute expiry, | ||
413 | enum GNUNET_PEERSTORE_StoreOption options, | ||
414 | GNUNET_PEERSTORE_Continuation cont, | ||
415 | void *cont_cls) | ||
416 | { | ||
417 | struct Plugin *plugin = cls; | ||
418 | sqlite3_stmt *stmt; | ||
419 | struct GNUNET_SQ_QueryParam params[] = { | ||
420 | GNUNET_SQ_query_param_string (sub_system), | ||
421 | GNUNET_SQ_query_param_auto_from_type (peer), | ||
422 | GNUNET_SQ_query_param_string (key), | ||
423 | GNUNET_SQ_query_param_fixed_size (value, size), | ||
424 | GNUNET_SQ_query_param_absolute_time (&expiry), | ||
425 | GNUNET_SQ_query_param_end | ||
426 | }; | ||
427 | |||
428 | if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options) | ||
429 | { | ||
430 | peerstore_sqlite_delete_records (cls, | ||
431 | sub_system, | ||
432 | peer, | ||
433 | key); | ||
434 | } | ||
435 | |||
436 | stmt = plugin->insert_peerstoredata; | ||
437 | if (GNUNET_OK != | ||
438 | GNUNET_SQ_bind (stmt, | ||
439 | params)) | ||
440 | { | ||
441 | LOG_SQLITE (plugin, | ||
442 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
443 | "sqlite3_bind"); | ||
444 | GNUNET_assert (0); | ||
445 | } | ||
446 | if (SQLITE_DONE != sqlite3_step (stmt)) | ||
447 | { | ||
448 | LOG_SQLITE (plugin, | ||
449 | GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, | ||
450 | "sqlite3_step"); | ||
451 | } | ||
452 | GNUNET_SQ_reset (plugin->dbh, | ||
453 | stmt); | ||
454 | if (NULL != cont) | ||
455 | cont (cont_cls, | ||
456 | GNUNET_OK); | ||
457 | return GNUNET_OK; | ||
458 | } | ||
459 | |||
460 | |||
461 | /** | ||
462 | * @brief Prepare a SQL statement | ||
463 | * | ||
464 | * @param dbh handle to the database | ||
465 | * @param sql SQL statement, UTF-8 encoded | ||
466 | * @return 0 on success | ||
467 | */ | ||
468 | static int | ||
469 | sql_exec (sqlite3 *dbh, | ||
470 | const char *sql) | ||
471 | { | ||
472 | int result; | ||
473 | |||
474 | result = sqlite3_exec (dbh, | ||
475 | sql, | ||
476 | NULL, | ||
477 | NULL, | ||
478 | NULL); | ||
479 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
480 | "Executed `%s' / %d\n", | ||
481 | sql, | ||
482 | result); | ||
483 | if (SQLITE_OK != result) | ||
484 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
485 | _ ("Error executing SQL query: %s\n %s\n"), | ||
486 | sqlite3_errmsg (dbh), | ||
487 | sql); | ||
488 | return result; | ||
489 | } | ||
490 | |||
491 | |||
492 | /** | ||
493 | * @brief Prepare a SQL statement | ||
494 | * | ||
495 | * @param dbh handle to the database | ||
496 | * @param sql SQL statement, UTF-8 encoded | ||
497 | * @param stmt set to the prepared statement | ||
498 | * @return 0 on success | ||
499 | */ | ||
500 | static int | ||
501 | sql_prepare (sqlite3 *dbh, | ||
502 | const char *sql, | ||
503 | sqlite3_stmt **stmt) | ||
504 | { | ||
505 | char *tail; | ||
506 | int result; | ||
507 | |||
508 | result = sqlite3_prepare_v2 (dbh, | ||
509 | sql, | ||
510 | strlen (sql), | ||
511 | stmt, | ||
512 | (const char **) &tail); | ||
513 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
514 | "Prepared `%s' / %p: %d\n", | ||
515 | sql, | ||
516 | *stmt, | ||
517 | result); | ||
518 | if (SQLITE_OK != result) | ||
519 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
520 | _ ("Error preparing SQL query: %s\n %s\n"), | ||
521 | sqlite3_errmsg (dbh), | ||
522 | sql); | ||
523 | return result; | ||
524 | } | ||
525 | |||
526 | |||
527 | /** | ||
528 | * Initialize the database connections and associated | ||
529 | * data structures (create tables and indices | ||
530 | * as needed as well). | ||
531 | * | ||
532 | * @param plugin the plugin context (state for this module) | ||
533 | * @return #GNUNET_OK on success | ||
534 | */ | ||
535 | static int | ||
536 | database_setup (struct Plugin *plugin) | ||
537 | { | ||
538 | char *filename; | ||
539 | |||
540 | if (GNUNET_OK != | ||
541 | GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, | ||
542 | "peerstore-sqlite", | ||
543 | "FILENAME", | ||
544 | &filename)) | ||
545 | { | ||
546 | GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, | ||
547 | "peerstore-sqlite", | ||
548 | "FILENAME"); | ||
549 | return GNUNET_SYSERR; | ||
550 | } | ||
551 | if (GNUNET_OK != GNUNET_DISK_file_test (filename)) | ||
552 | { | ||
553 | if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename)) | ||
554 | { | ||
555 | GNUNET_break (0); | ||
556 | GNUNET_free (filename); | ||
557 | return GNUNET_SYSERR; | ||
558 | } | ||
559 | } | ||
560 | /* filename should be UTF-8-encoded. If it isn't, it's a bug */ | ||
561 | plugin->fn = filename; | ||
562 | /* Open database and precompile statements */ | ||
563 | if (SQLITE_OK != sqlite3_open (plugin->fn, | ||
564 | &plugin->dbh)) | ||
565 | { | ||
566 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
567 | _ ("Unable to initialize SQLite: %s.\n"), | ||
568 | sqlite3_errmsg (plugin->dbh)); | ||
569 | return GNUNET_SYSERR; | ||
570 | } | ||
571 | sql_exec (plugin->dbh, | ||
572 | "PRAGMA temp_store=MEMORY"); | ||
573 | sql_exec (plugin->dbh, | ||
574 | "PRAGMA synchronous=OFF"); | ||
575 | sql_exec (plugin->dbh, | ||
576 | "PRAGMA legacy_file_format=OFF"); | ||
577 | sql_exec (plugin->dbh, | ||
578 | "PRAGMA auto_vacuum=INCREMENTAL"); | ||
579 | sql_exec (plugin->dbh, | ||
580 | "PRAGMA encoding=\"UTF-8\""); | ||
581 | sql_exec (plugin->dbh, | ||
582 | "PRAGMA page_size=4096"); | ||
583 | sqlite3_busy_timeout (plugin->dbh, | ||
584 | BUSY_TIMEOUT_MS); | ||
585 | /* Create tables */ | ||
586 | sql_exec (plugin->dbh, | ||
587 | "CREATE TABLE IF NOT EXISTS peerstoredata (\n" | ||
588 | " uid INTEGER PRIMARY KEY," | ||
589 | " sub_system TEXT NOT NULL,\n" | ||
590 | " peer_id BLOB NOT NULL,\n" | ||
591 | " key TEXT NOT NULL,\n" | ||
592 | " value BLOB NULL,\n" | ||
593 | " expiry INT8 NOT NULL" ");"); | ||
594 | /* Create Indices */ | ||
595 | if (SQLITE_OK != | ||
596 | sqlite3_exec (plugin->dbh, | ||
597 | "CREATE INDEX IF NOT EXISTS peerstoredata_key_index ON peerstoredata (sub_system, peer_id, key, uid)", | ||
598 | NULL, | ||
599 | NULL, | ||
600 | NULL)) | ||
601 | { | ||
602 | LOG (GNUNET_ERROR_TYPE_ERROR, | ||
603 | _ ("Unable to create indices: %s.\n"), | ||
604 | sqlite3_errmsg (plugin->dbh)); | ||
605 | return GNUNET_SYSERR; | ||
606 | } | ||
607 | /* Prepare statements */ | ||
608 | |||
609 | sql_prepare (plugin->dbh, | ||
610 | "INSERT INTO peerstoredata (sub_system, peer_id, key, value, expiry)" | ||
611 | " VALUES (?,?,?,?,?);", | ||
612 | &plugin->insert_peerstoredata); | ||
613 | sql_prepare (plugin->dbh, | ||
614 | "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata" | ||
615 | " WHERE sub_system = ?" | ||
616 | " AND uid > ?" | ||
617 | " ORDER BY uid ASC" | ||
618 | " LIMIT ?", | ||
619 | &plugin->select_peerstoredata); | ||
620 | sql_prepare (plugin->dbh, | ||
621 | "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata" | ||
622 | " WHERE sub_system = ?" | ||
623 | " AND peer_id = ?" | ||
624 | " AND uid > ?" | ||
625 | " ORDER BY uid ASC" | ||
626 | " LIMIT ?", | ||
627 | &plugin->select_peerstoredata_by_pid); | ||
628 | sql_prepare (plugin->dbh, | ||
629 | "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata" | ||
630 | " WHERE sub_system = ?" | ||
631 | " AND key = ?" | ||
632 | " AND uid > ?" | ||
633 | " ORDER BY uid ASC" | ||
634 | " LIMIT ?", | ||
635 | &plugin->select_peerstoredata_by_key); | ||
636 | sql_prepare (plugin->dbh, | ||
637 | "SELECT uid,sub_system,peer_id,key,value,expiry FROM peerstoredata" | ||
638 | " WHERE sub_system = ?" | ||
639 | " AND peer_id = ?" | ||
640 | " AND key = ?" | ||
641 | " AND uid > ?" | ||
642 | " ORDER BY uid ASC" | ||
643 | " LIMIT ?", | ||
644 | &plugin->select_peerstoredata_by_all); | ||
645 | sql_prepare (plugin->dbh, | ||
646 | "DELETE FROM peerstoredata" | ||
647 | " WHERE expiry < ?", | ||
648 | &plugin->expire_peerstoredata); | ||
649 | sql_prepare (plugin->dbh, | ||
650 | "DELETE FROM peerstoredata" | ||
651 | " WHERE sub_system = ?" | ||
652 | " AND peer_id = ?" " AND key = ?", | ||
653 | &plugin->delete_peerstoredata); | ||
654 | return GNUNET_OK; | ||
655 | } | ||
656 | |||
657 | |||
658 | /** | ||
659 | * Shutdown database connection and associate data | ||
660 | * structures. | ||
661 | * @param plugin the plugin context (state for this module) | ||
662 | */ | ||
663 | static void | ||
664 | database_shutdown (struct Plugin *plugin) | ||
665 | { | ||
666 | int result; | ||
667 | sqlite3_stmt *stmt; | ||
668 | |||
669 | while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh, | ||
670 | NULL))) | ||
671 | { | ||
672 | result = sqlite3_finalize (stmt); | ||
673 | if (SQLITE_OK != result) | ||
674 | LOG (GNUNET_ERROR_TYPE_WARNING, | ||
675 | "Failed to close statement %p: %d\n", | ||
676 | stmt, | ||
677 | result); | ||
678 | } | ||
679 | if (SQLITE_OK != sqlite3_close (plugin->dbh)) | ||
680 | LOG_SQLITE (plugin, | ||
681 | GNUNET_ERROR_TYPE_ERROR, | ||
682 | "sqlite3_close"); | ||
683 | GNUNET_free (plugin->fn); | ||
684 | } | ||
685 | |||
686 | |||
687 | /** | ||
688 | * Entry point for the plugin. | ||
689 | * | ||
690 | * @param cls The struct GNUNET_CONFIGURATION_Handle. | ||
691 | * @return NULL on error, otherwise the plugin context | ||
692 | */ | ||
693 | void * | ||
694 | libgnunet_plugin_peerstore_sqlite_init (void *cls) | ||
695 | { | ||
696 | static struct Plugin plugin; | ||
697 | const struct GNUNET_CONFIGURATION_Handle *cfg = cls; | ||
698 | struct GNUNET_PEERSTORE_PluginFunctions *api; | ||
699 | |||
700 | if (NULL != plugin.cfg) | ||
701 | return NULL; /* can only initialize once! */ | ||
702 | memset (&plugin, | ||
703 | 0, | ||
704 | sizeof(struct Plugin)); | ||
705 | plugin.cfg = cfg; | ||
706 | if (GNUNET_OK != database_setup (&plugin)) | ||
707 | { | ||
708 | database_shutdown (&plugin); | ||
709 | return NULL; | ||
710 | } | ||
711 | api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions); | ||
712 | api->cls = &plugin; | ||
713 | api->store_record = &peerstore_sqlite_store_record; | ||
714 | api->iterate_records = &peerstore_sqlite_iterate_records; | ||
715 | api->expire_records = &peerstore_sqlite_expire_records; | ||
716 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
717 | "Sqlite plugin is running\n"); | ||
718 | return api; | ||
719 | } | ||
720 | |||
721 | |||
722 | /** | ||
723 | * Exit point from the plugin. | ||
724 | * | ||
725 | * @param cls The plugin context (as returned by "init") | ||
726 | * @return Always NULL | ||
727 | */ | ||
728 | void * | ||
729 | libgnunet_plugin_peerstore_sqlite_done (void *cls) | ||
730 | { | ||
731 | struct GNUNET_PEERSTORE_PluginFunctions *api = cls; | ||
732 | struct Plugin *plugin = api->cls; | ||
733 | |||
734 | database_shutdown (plugin); | ||
735 | plugin->cfg = NULL; | ||
736 | GNUNET_free (api); | ||
737 | LOG (GNUNET_ERROR_TYPE_DEBUG, | ||
738 | "Sqlite plugin is finished\n"); | ||
739 | return NULL; | ||
740 | } | ||
741 | |||
742 | |||
743 | /* end of plugin_peerstore_sqlite.c */ | ||
diff --git a/src/plugin/peerstore/test_plugin_peerstore.c b/src/plugin/peerstore/test_plugin_peerstore.c new file mode 100644 index 000000000..1377845aa --- /dev/null +++ b/src/plugin/peerstore/test_plugin_peerstore.c | |||
@@ -0,0 +1,226 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2015 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 namestore/test_plugin_namestore.c | ||
22 | * @brief Test for the namestore plugins | ||
23 | * @author Christian Grothoff | ||
24 | */ | ||
25 | #include "platform.h" | ||
26 | #include "gnunet_util_lib.h" | ||
27 | #include "gnunet_peerstore_plugin.h" | ||
28 | #include "gnunet_testing_lib.h" | ||
29 | |||
30 | |||
31 | static int ok; | ||
32 | |||
33 | /** | ||
34 | * Name of plugin under test. | ||
35 | */ | ||
36 | static const char *plugin_name; | ||
37 | |||
38 | |||
39 | static struct GNUNET_PEERSTORE_PluginFunctions *psp; | ||
40 | |||
41 | static struct GNUNET_PeerIdentity p1; | ||
42 | |||
43 | |||
44 | /** | ||
45 | * Function called when the service shuts down. Unloads our namestore | ||
46 | * plugin. | ||
47 | * | ||
48 | * @param api api to unload | ||
49 | */ | ||
50 | static void | ||
51 | unload_plugin (struct GNUNET_PEERSTORE_PluginFunctions *api) | ||
52 | { | ||
53 | char *libname; | ||
54 | |||
55 | GNUNET_asprintf (&libname, | ||
56 | "libgnunet_plugin_peer_%s", | ||
57 | plugin_name); | ||
58 | GNUNET_break (NULL == | ||
59 | GNUNET_PLUGIN_unload (libname, | ||
60 | api)); | ||
61 | GNUNET_free (libname); | ||
62 | } | ||
63 | |||
64 | |||
65 | /** | ||
66 | * Load the namestore plugin. | ||
67 | * | ||
68 | * @param cfg configuration to pass | ||
69 | * @return NULL on error | ||
70 | */ | ||
71 | static struct GNUNET_PEERSTORE_PluginFunctions * | ||
72 | load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
73 | { | ||
74 | struct GNUNET_PEERSTORE_PluginFunctions *ret; | ||
75 | char *libname; | ||
76 | |||
77 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
78 | _ ("Loading `%s' peer plugin\n"), | ||
79 | plugin_name); | ||
80 | GNUNET_asprintf (&libname, | ||
81 | "libgnunet_plugin_peerstore_%s", | ||
82 | plugin_name); | ||
83 | if (NULL == (ret = GNUNET_PLUGIN_load (libname, | ||
84 | (void *) cfg))) | ||
85 | { | ||
86 | fprintf (stderr, | ||
87 | "Failed to load plugin `%s'!\n", | ||
88 | plugin_name); | ||
89 | GNUNET_free (libname); | ||
90 | return NULL; | ||
91 | } | ||
92 | GNUNET_free (libname); | ||
93 | return ret; | ||
94 | } | ||
95 | |||
96 | |||
97 | static void | ||
98 | test_record (void *cls, | ||
99 | uint64_t seq, | ||
100 | const struct GNUNET_PEERSTORE_Record *record, | ||
101 | const char *error) | ||
102 | { | ||
103 | const struct GNUNET_PeerIdentity *id = cls; | ||
104 | const char*testval = "test_val"; | ||
105 | |||
106 | if (NULL == record) | ||
107 | { | ||
108 | unload_plugin (psp); | ||
109 | return; | ||
110 | } | ||
111 | GNUNET_assert (0 == memcmp (&record->peer, | ||
112 | id, | ||
113 | sizeof(struct GNUNET_PeerIdentity))); | ||
114 | GNUNET_assert (0 == strcmp ("subsys", | ||
115 | record->sub_system)); | ||
116 | GNUNET_assert (0 == strcmp ("key", | ||
117 | record->key)); | ||
118 | GNUNET_assert (0 == memcmp (testval, | ||
119 | record->value, | ||
120 | strlen (testval))); | ||
121 | ok = 0; | ||
122 | } | ||
123 | |||
124 | |||
125 | static void | ||
126 | get_record (struct GNUNET_PEERSTORE_PluginFunctions *psp, | ||
127 | const struct GNUNET_PeerIdentity *identity) | ||
128 | { | ||
129 | GNUNET_assert (GNUNET_OK == | ||
130 | psp->iterate_records (psp->cls, | ||
131 | "subsys", | ||
132 | identity, | ||
133 | "key", | ||
134 | 0, 1, | ||
135 | &test_record, | ||
136 | (void *) identity)); | ||
137 | } | ||
138 | |||
139 | |||
140 | static void | ||
141 | store_cont (void *cls, | ||
142 | int status) | ||
143 | { | ||
144 | GNUNET_assert (GNUNET_OK == status); | ||
145 | get_record (psp, | ||
146 | &p1); | ||
147 | } | ||
148 | |||
149 | |||
150 | static void | ||
151 | put_record (struct GNUNET_PEERSTORE_PluginFunctions *psp, | ||
152 | const struct GNUNET_PeerIdentity *identity) | ||
153 | { | ||
154 | GNUNET_assert (GNUNET_OK == | ||
155 | psp->store_record (psp->cls, | ||
156 | "subsys", | ||
157 | identity, | ||
158 | "key", | ||
159 | "test_value", | ||
160 | strlen ("test_value"), | ||
161 | GNUNET_TIME_absolute_get (), | ||
162 | GNUNET_PEERSTORE_STOREOPTION_REPLACE, | ||
163 | &store_cont, | ||
164 | NULL)); | ||
165 | } | ||
166 | |||
167 | |||
168 | static void | ||
169 | run (void *cls, | ||
170 | char *const *args, | ||
171 | const char *cfgfile, | ||
172 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
173 | { | ||
174 | ok = 1; | ||
175 | psp = load_plugin (cfg); | ||
176 | if (NULL == psp) | ||
177 | { | ||
178 | fprintf (stderr, | ||
179 | "%s", | ||
180 | "Failed to initialize peerstore. Database likely not setup, skipping test.\n"); | ||
181 | return; | ||
182 | } | ||
183 | memset (&p1, 1, sizeof(p1)); | ||
184 | put_record (psp, | ||
185 | &p1); | ||
186 | } | ||
187 | |||
188 | |||
189 | int | ||
190 | main (int argc, char *argv[]) | ||
191 | { | ||
192 | char cfg_name[PATH_MAX]; | ||
193 | char *const xargv[] = { | ||
194 | "test-plugin-peerstore", | ||
195 | "-c", | ||
196 | cfg_name, | ||
197 | NULL | ||
198 | }; | ||
199 | struct GNUNET_GETOPT_CommandLineOption options[] = { | ||
200 | GNUNET_GETOPT_OPTION_END | ||
201 | }; | ||
202 | |||
203 | GNUNET_log_setup ("test-plugin-peerstore", | ||
204 | "WARNING", | ||
205 | NULL); | ||
206 | plugin_name = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]); | ||
207 | GNUNET_snprintf (cfg_name, | ||
208 | sizeof(cfg_name), | ||
209 | "test_plugin_peerstore_%s.conf", | ||
210 | plugin_name); | ||
211 | GNUNET_PROGRAM_run ((sizeof(xargv) / sizeof(char *)) - 1, | ||
212 | xargv, | ||
213 | "test-plugin-peerstore", | ||
214 | "nohelp", | ||
215 | options, | ||
216 | &run, | ||
217 | NULL); | ||
218 | if (ok != 0) | ||
219 | fprintf (stderr, | ||
220 | "Missed some testcases: %d\n", | ||
221 | ok); | ||
222 | return ok; | ||
223 | } | ||
224 | |||
225 | |||
226 | /* end of test_plugin_peerstore.c */ | ||
diff --git a/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf b/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf new file mode 100644 index 000000000..154491ff6 --- /dev/null +++ b/src/plugin/peerstore/test_plugin_peerstore_sqlite.conf | |||
@@ -0,0 +1,2 @@ | |||
1 | [peerstore-sqlite] | ||
2 | FILENAME = $GNUNET_TMP/gnunet-test-plugin-peerstore-sqlite/sqlite-ng.db | ||