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