048201c776ab5ac9cfcdf2d8ce6ccbdcc34e1edd
[oweals/gnunet.git] / src / peerstore / gnunet-service-peerstore.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2014, 2015 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file peerstore/gnunet-service-peerstore.c
23  * @brief peerstore service implementation
24  * @author Omar Tarabai
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "peerstore.h"
29 #include "gnunet_peerstore_plugin.h"
30 #include "peerstore_common.h"
31
32 /**
33  * Connected client entry
34  */
35 struct ClientEntry
36 {
37   /**
38    * DLL.
39    */
40   struct ClientEntry *next;
41
42   /**
43    * DLL.
44    */
45   struct ClientEntry *prev;
46
47   /**
48    * Corresponding server handle.
49    */
50   struct GNUNET_SERVER_Client *client;
51 };
52
53 /**
54  * Interval for expired records cleanup (in seconds)
55  */
56 #define EXPIRED_RECORDS_CLEANUP_INTERVAL 300    /* 5mins */
57
58 /**
59  * Our configuration.
60  */
61 static const struct GNUNET_CONFIGURATION_Handle *cfg;
62
63 /**
64  * Database plugin library name
65  */
66 static char *db_lib_name;
67
68 /**
69  * Database handle
70  */
71 static struct GNUNET_PEERSTORE_PluginFunctions *db;
72
73 /**
74  * Hashmap with all watch requests
75  */
76 static struct GNUNET_CONTAINER_MultiHashMap *watchers;
77
78 /**
79  * Our notification context.
80  */
81 static struct GNUNET_SERVER_NotificationContext *nc;
82
83 /**
84  * Head of linked list of connected clients
85  */
86 static struct ClientEntry *client_head;
87
88 /**
89  * Tail of linked list of connected clients
90  */
91 static struct ClientEntry *client_tail;
92
93 /**
94  * Task run to clean up expired records.
95  */
96 static struct GNUNET_SCHEDULER_Task *expire_task;
97
98 /**
99  * Are we in the process of shutting down the service? #GNUNET_YES / #GNUNET_NO
100  */
101 static int in_shutdown;
102
103 /**
104  * Perform the actual shutdown operations
105  */
106 static void
107 do_shutdown ()
108 {
109   if (NULL != db_lib_name)
110   {
111     GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, db));
112     GNUNET_free (db_lib_name);
113     db_lib_name = NULL;
114   }
115   if (NULL != nc)
116   {
117     GNUNET_SERVER_notification_context_destroy (nc);
118     nc = NULL;
119   }
120   if (NULL != watchers)
121   {
122     GNUNET_CONTAINER_multihashmap_destroy (watchers);
123     watchers = NULL;
124   }
125   if (NULL != expire_task)
126   {
127     GNUNET_SCHEDULER_cancel (expire_task);
128     expire_task = NULL;
129   }
130   GNUNET_SCHEDULER_shutdown ();
131 }
132
133
134 /**
135  * Task run during shutdown.
136  *
137  * @param cls unused
138  */
139 static void
140 shutdown_task (void *cls)
141 {
142   in_shutdown = GNUNET_YES;
143   if (NULL == client_head)      /* Only when no connected clients. */
144     do_shutdown ();
145 }
146
147
148 /* Forward declaration */
149 static void
150 expire_records_continuation (void *cls, int success);
151
152
153 /**
154  * Deletes any expired records from storage
155  */
156 static void
157 cleanup_expired_records (void *cls)
158 {
159   int ret;
160
161   expire_task = NULL;
162   GNUNET_assert (NULL != db);
163   ret = db->expire_records (db->cls, GNUNET_TIME_absolute_get (),
164                             &expire_records_continuation, NULL);
165   if (GNUNET_OK != ret)
166   {
167     GNUNET_assert (NULL == expire_task);
168     expire_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
169                                                 (GNUNET_TIME_UNIT_SECONDS,
170                                                  EXPIRED_RECORDS_CLEANUP_INTERVAL),
171                                                 &cleanup_expired_records, NULL);
172   }
173 }
174
175
176 /**
177  * Continuation to expire_records called by the peerstore plugin
178  *
179  * @param cls unused
180  * @param success count of records deleted or #GNUNET_SYSERR
181  */
182 static void
183 expire_records_continuation (void *cls,
184                              int success)
185 {
186   if (success > 0)
187     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
188                 "%d records expired.\n",
189                 success);
190   GNUNET_assert (NULL == expire_task);
191   expire_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
192                                               (GNUNET_TIME_UNIT_SECONDS,
193                                                EXPIRED_RECORDS_CLEANUP_INTERVAL),
194                                               &cleanup_expired_records, NULL);
195 }
196
197
198 /**
199  * Search for a disconnected client and remove it
200  *
201  * @param cls closuer, a 'struct GNUNET_PEERSTORE_Record *'
202  * @param key hash of record key
203  * @param value the watcher client, a 'struct GNUNET_SERVER_Client *'
204  * @return #GNUNET_OK to continue iterating
205  */
206 static int
207 client_disconnect_it (void *cls, const struct GNUNET_HashCode *key, void *value)
208 {
209   if (cls == value)
210     GNUNET_CONTAINER_multihashmap_remove (watchers, key, value);
211   return GNUNET_OK;
212 }
213
214
215 /**
216  * A client disconnected.  Remove all of its data structure entries.
217  *
218  * @param cls closure, NULL
219  * @param client identification of the client
220  */
221 static void
222 handle_client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
223 {
224   struct ClientEntry *ce;
225
226   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "A client disconnected, cleaning up.\n");
227   if (NULL != watchers)
228     GNUNET_CONTAINER_multihashmap_iterate (watchers, &client_disconnect_it,
229                                            client);
230   ce = client_head;
231   while (ce != NULL)
232   {
233     if (ce->client == client)
234     {
235       GNUNET_CONTAINER_DLL_remove (client_head, client_tail, ce);
236       GNUNET_free (ce);
237       break;
238     }
239     ce = ce->next;
240   }
241   if (NULL == client_head && in_shutdown)
242     do_shutdown ();
243 }
244
245
246 /**
247  * Function called by for each matching record.
248  *
249  * @param cls closure
250  * @param record peerstore record found
251  * @param emsg error message or NULL if no errors
252  * @return #GNUNET_YES to continue iteration
253  */
254 static int
255 record_iterator (void *cls, const struct GNUNET_PEERSTORE_Record *record,
256                  const char *emsg)
257 {
258   struct GNUNET_PEERSTORE_Record *cls_record = cls;
259   struct StoreRecordMessage *srm;
260
261   if (NULL == record)
262   {
263     /* No more records */
264     struct GNUNET_MessageHeader endmsg;
265
266     endmsg.size = htons (sizeof (struct GNUNET_MessageHeader));
267     endmsg.type = htons (GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_END);
268     GNUNET_SERVER_notification_context_unicast (nc, cls_record->client, &endmsg,
269                                                 GNUNET_NO);
270     GNUNET_SERVER_receive_done (cls_record->client,
271                                 NULL == emsg ? GNUNET_OK : GNUNET_SYSERR);
272     PEERSTORE_destroy_record (cls_record);
273     return GNUNET_NO;
274   }
275
276   srm =
277       PEERSTORE_create_record_message (record->sub_system, record->peer,
278                                        record->key, record->value,
279                                        record->value_size, record->expiry,
280                                        GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_RECORD);
281   GNUNET_SERVER_notification_context_unicast (nc, cls_record->client,
282                                               (struct GNUNET_MessageHeader *)
283                                               srm, GNUNET_NO);
284   GNUNET_free (srm);
285   return GNUNET_YES;
286 }
287
288
289 /**
290  * Iterator over all watcher clients
291  * to notify them of a new record
292  *
293  * @param cls closuer, a 'struct GNUNET_PEERSTORE_Record *'
294  * @param key hash of record key
295  * @param value the watcher client, a 'struct GNUNET_SERVER_Client *'
296  * @return #GNUNET_YES to continue iterating
297  */
298 static int
299 watch_notifier_it (void *cls, const struct GNUNET_HashCode *key, void *value)
300 {
301   struct GNUNET_PEERSTORE_Record *record = cls;
302   struct GNUNET_SERVER_Client *client = value;
303   struct StoreRecordMessage *srm;
304
305   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found a watcher to update.\n");
306   srm =
307       PEERSTORE_create_record_message (record->sub_system, record->peer,
308                                        record->key, record->value,
309                                        record->value_size, record->expiry,
310                                        GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_RECORD);
311   GNUNET_SERVER_notification_context_unicast (nc, client,
312                                               (const struct GNUNET_MessageHeader
313                                                *) srm, GNUNET_NO);
314   GNUNET_free (srm);
315   return GNUNET_YES;
316 }
317
318
319 /**
320  * Given a new record, notifies watchers
321  *
322  * @param record changed record to update watchers with
323  */
324 static void
325 watch_notifier (struct GNUNET_PEERSTORE_Record *record)
326 {
327   struct GNUNET_HashCode keyhash;
328
329   PEERSTORE_hash_key (record->sub_system, record->peer, record->key, &keyhash);
330   GNUNET_CONTAINER_multihashmap_get_multiple (watchers, &keyhash,
331                                               &watch_notifier_it, record);
332 }
333
334
335 /**
336  * Handle a watch cancel request from client
337  *
338  * @param cls unused
339  * @param client identification of the client
340  * @param message the actual message
341  */
342 static void
343 handle_watch_cancel (void *cls, struct GNUNET_SERVER_Client *client,
344                      const struct GNUNET_MessageHeader *message)
345 {
346   struct StoreKeyHashMessage *hm;
347
348   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received a watch cancel request.\n");
349   hm = (struct StoreKeyHashMessage *) message;
350   GNUNET_CONTAINER_multihashmap_remove (watchers, &hm->keyhash, client);
351   GNUNET_SERVER_receive_done (client, GNUNET_OK);
352 }
353
354
355 /**
356  * Handle a watch request from client
357  *
358  * @param cls unused
359  * @param client identification of the client
360  * @param message the actual message
361  */
362 static void
363 handle_watch (void *cls, struct GNUNET_SERVER_Client *client,
364               const struct GNUNET_MessageHeader *message)
365 {
366   struct StoreKeyHashMessage *hm;
367
368   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received a watch request.\n");
369   hm = (struct StoreKeyHashMessage *) message;
370   GNUNET_SERVER_client_mark_monitor (client);
371   GNUNET_SERVER_notification_context_add (nc, client);
372   GNUNET_CONTAINER_multihashmap_put (watchers, &hm->keyhash, client,
373                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
374   GNUNET_SERVER_receive_done (client, GNUNET_OK);
375 }
376
377
378 /**
379  * Handle an iterate request from client
380  *
381  * @param cls unused
382  * @param client identification of the client
383  * @param message the actual message
384  */
385 static void
386 handle_iterate (void *cls, struct GNUNET_SERVER_Client *client,
387                 const struct GNUNET_MessageHeader *message)
388 {
389   struct GNUNET_PEERSTORE_Record *record;
390
391   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received an iterate request.\n");
392   record = PEERSTORE_parse_record_message (message);
393   if (NULL == record)
394   {
395     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Malformed iterate request.\n"));
396     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
397     return;
398   }
399   if (NULL == record->sub_system)
400   {
401     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
402                 _("Sub system not supplied in client iterate request.\n"));
403     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
404     PEERSTORE_destroy_record (record);
405     return;
406   }
407   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
408               "Iterate request: ss `%s', peer `%s', key `%s'\n",
409               record->sub_system,
410               (NULL == record->peer) ? "NULL" : GNUNET_i2s (record->peer),
411               (NULL == record->key) ? "NULL" : record->key);
412   GNUNET_SERVER_notification_context_add (nc, client);
413   record->client = client;
414   if (GNUNET_OK !=
415       db->iterate_records (db->cls, record->sub_system, record->peer,
416                            record->key, &record_iterator, record))
417   {
418     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
419     PEERSTORE_destroy_record (record);
420   }
421 }
422
423
424 /**
425  * Continuation of store_record called by the peerstore plugin
426  *
427  * @param cls closure
428  * @param success result
429  */
430 static void
431 store_record_continuation (void *cls, int success)
432 {
433   struct GNUNET_PEERSTORE_Record *record = cls;
434
435   GNUNET_SERVER_receive_done (record->client, success);
436   if (GNUNET_OK == success)
437   {
438     watch_notifier (record);
439   }
440   PEERSTORE_destroy_record (record);
441 }
442
443
444 /**
445  * Handle a store request from client
446  *
447  * @param cls unused
448  * @param client identification of the client
449  * @param message the actual message
450  */
451 static void
452 handle_store (void *cls, struct GNUNET_SERVER_Client *client,
453               const struct GNUNET_MessageHeader *message)
454 {
455   struct GNUNET_PEERSTORE_Record *record;
456   struct StoreRecordMessage *srm;
457
458   record = PEERSTORE_parse_record_message (message);
459   if (NULL == record)
460   {
461     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
462                 _("Malformed store request from client\n"));
463     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
464     return;
465   }
466   srm = (struct StoreRecordMessage *) message;
467   if (NULL == record->sub_system || NULL == record->peer || NULL == record->key)
468   {
469     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
470                 _("Full key not supplied in client store request\n"));
471     PEERSTORE_destroy_record (record);
472     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
473     return;
474   }
475   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
476               "Received a store request (size: %lu).\n" " Sub system `%s'\n"
477               " Peer `%s'\n" " Key `%s'\n" " Value size %lu\n"
478               " Options: %d.\n", record->value_size, record->sub_system,
479               GNUNET_i2s (record->peer), record->key, record->value_size,
480               ntohl (srm->options));
481   record->client = client;
482   if (GNUNET_OK !=
483       db->store_record (db->cls, record->sub_system, record->peer, record->key,
484                         record->value, record->value_size, *record->expiry,
485                         ntohl (srm->options), store_record_continuation,
486                         record))
487   {
488     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
489                 _("Failed to store requested value, database error."));
490     PEERSTORE_destroy_record (record);
491     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
492     return;
493   }
494 }
495
496
497 /**
498  * Creates an entry for a new client or returns it if it already exists.
499  *
500  * @param client Client handle
501  * @return Client entry struct
502  */
503 static struct ClientEntry *
504 make_client_entry (struct GNUNET_SERVER_Client *client)
505 {
506   struct ClientEntry *ce;
507
508   ce = client_head;
509   while (NULL != ce)
510   {
511     if (ce->client == client)
512       return ce;
513     ce = ce->next;
514   }
515   if (GNUNET_YES == in_shutdown)
516   {
517     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
518     return NULL;
519   }
520   ce = GNUNET_new (struct ClientEntry);
521   ce->client = client;
522   GNUNET_CONTAINER_DLL_insert (client_head, client_tail, ce);
523   return ce;
524 }
525
526
527 /**
528  * Callback on a new client connection
529  *
530  * @param cls closure (unused)
531  * @param client identification of the client
532  */
533 static void
534 handle_client_connect (void *cls, struct GNUNET_SERVER_Client *client)
535 {
536   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New client connection created.\n");
537   make_client_entry (client);
538 }
539
540
541 /**
542  * Peerstore service runner.
543  *
544  * @param cls closure
545  * @param server the initialized server
546  * @param c configuration to use
547  */
548 static void
549 run (void *cls, struct GNUNET_SERVER_Handle *server,
550      const struct GNUNET_CONFIGURATION_Handle *c)
551 {
552   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
553     {&handle_store, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_STORE, 0},
554     {&handle_iterate, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE, 0},
555     {&handle_watch, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH,
556      sizeof (struct StoreKeyHashMessage)},
557     {&handle_watch_cancel, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_CANCEL,
558      sizeof (struct StoreKeyHashMessage)},
559     {NULL, NULL, 0, 0}
560   };
561   char *database;
562
563   in_shutdown = GNUNET_NO;
564   cfg = c;
565   if (GNUNET_OK !=
566       GNUNET_CONFIGURATION_get_value_string (cfg, "peerstore", "DATABASE",
567                                              &database))
568     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("No database backend configured\n"));
569
570   else
571   {
572     GNUNET_asprintf (&db_lib_name, "libgnunet_plugin_peerstore_%s", database);
573     db = GNUNET_PLUGIN_load (db_lib_name, (void *) cfg);
574     GNUNET_free (database);
575   }
576   if (NULL == db)
577   {
578     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
579                 _("Could not load database backend `%s'\n"),
580                 db_lib_name);
581     GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
582     return;
583   }
584   nc = GNUNET_SERVER_notification_context_create (server, 16);
585   watchers = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
586   expire_task = GNUNET_SCHEDULER_add_now (&cleanup_expired_records,
587                                           NULL);
588   GNUNET_SERVER_add_handlers (server, handlers);
589   GNUNET_SERVER_connect_notify (server, &handle_client_connect, NULL);
590   GNUNET_SERVER_disconnect_notify (server, &handle_client_disconnect, NULL);
591   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
592                                  NULL);
593 }
594
595
596 /**
597  * The main function for the peerstore service.
598  *
599  * @param argc number of arguments from the command line
600  * @param argv command line arguments
601  * @return 0 ok, 1 on error
602  */
603 int
604 main (int argc, char *const *argv)
605 {
606   return (GNUNET_OK ==
607           GNUNET_SERVICE_run (argc, argv, "peerstore",
608                               GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN, &run,
609                               NULL)) ? 0 : 1;
610 }
611
612 /* end of gnunet-service-peerstore.c */