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