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