c9ddafa10ae1030722a03c4306867911363990d2
[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  * Interval for expired records cleanup (in seconds)
34  */
35 #define EXPIRED_RECORDS_CLEANUP_INTERVAL 300 /* 5mins */
36
37 /**
38  * Our configuration.
39  */
40 static const struct GNUNET_CONFIGURATION_Handle *cfg;
41
42 /**
43  * Database plugin library name
44  */
45 static char *db_lib_name;
46
47 /**
48  * Database handle
49  */
50 static struct GNUNET_PEERSTORE_PluginFunctions *db;
51
52 /**
53  * Hashmap with all watch requests
54  */
55 static struct GNUNET_CONTAINER_MultiHashMap *watchers;
56
57 /**
58  * Our notification context.
59  */
60 static struct GNUNET_SERVER_NotificationContext *nc;
61
62 /**
63  * Task run during shutdown.
64  *
65  * @param cls unused
66  * @param tc unused
67  */
68 static void
69 shutdown_task (void *cls,
70                const struct GNUNET_SCHEDULER_TaskContext *tc)
71 {
72   if(NULL != db_lib_name)
73   {
74     GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, db));
75     GNUNET_free (db_lib_name);
76     db_lib_name = NULL;
77   }
78   if(NULL != nc)
79   {
80     GNUNET_SERVER_notification_context_destroy(nc);
81     nc = NULL;
82   }
83   if(NULL != watchers)
84   {
85     GNUNET_CONTAINER_multihashmap_destroy(watchers);
86     watchers = NULL;
87   }
88   GNUNET_SCHEDULER_shutdown();
89 }
90
91 /**
92  * Deletes any expired records from storage
93  */
94 static void
95 cleanup_expired_records(void *cls,
96     const struct GNUNET_SCHEDULER_TaskContext *tc)
97 {
98   int deleted;
99
100   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
101     return;
102   GNUNET_assert(NULL != db);
103   deleted = db->expire_records(db->cls, GNUNET_TIME_absolute_get());
104   GNUNET_log(GNUNET_ERROR_TYPE_INFO, "%d records expired.\n", deleted);
105   GNUNET_SCHEDULER_add_delayed(
106       GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, EXPIRED_RECORDS_CLEANUP_INTERVAL),
107       &cleanup_expired_records, NULL);
108 }
109
110 /**
111  * Search for a disconnected client and remove it
112  *
113  * @param cls closuer, a 'struct GNUNET_PEERSTORE_Record *'
114  * @param key hash of record key
115  * @param value the watcher client, a 'struct GNUNET_SERVER_Client *'
116  * @return #GNUNET_OK to continue iterating
117  */
118 int client_disconnect_it(void *cls,
119     const struct GNUNET_HashCode *key,
120     void *value)
121 {
122   if(cls == value)
123     GNUNET_CONTAINER_multihashmap_remove(watchers, key, value);
124   return GNUNET_OK;
125 }
126
127 /**
128  * A client disconnected.  Remove all of its data structure entries.
129  *
130  * @param cls closure, NULL
131  * @param client identification of the client
132  */
133 static void
134 handle_client_disconnect (void *cls,
135                           struct GNUNET_SERVER_Client
136                           * client)
137 {
138   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "A client was disconnected, cleaning up.\n");
139   if(NULL != watchers)
140     GNUNET_CONTAINER_multihashmap_iterate(watchers,
141         &client_disconnect_it, client);
142 }
143
144 /**
145  * Function called by for each matching record.
146  *
147  * @param cls closure
148  * @param peer peer identity
149  * @param sub_system name of the GNUnet sub system responsible
150  * @param value stored value
151  * @param size size of stored value
152  */
153 int record_iterator(void *cls,
154     struct GNUNET_PEERSTORE_Record *record,
155     char *emsg)
156 {
157   struct GNUNET_SERVER_Client *client = cls;
158   struct StoreRecordMessage *srm;
159
160   srm = PEERSTORE_create_record_message(record->sub_system,
161       record->peer,
162       record->key,
163       record->value,
164       record->value_size,
165       record->expiry,
166       GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_RECORD);
167   GNUNET_SERVER_notification_context_unicast(nc, client, (struct GNUNET_MessageHeader *)srm, GNUNET_NO);
168   GNUNET_free(srm);
169   return GNUNET_YES;
170 }
171
172 /**
173  * Iterator over all watcher clients
174  * to notify them of a new record
175  *
176  * @param cls closuer, a 'struct GNUNET_PEERSTORE_Record *'
177  * @param key hash of record key
178  * @param value the watcher client, a 'struct GNUNET_SERVER_Client *'
179  * @return #GNUNET_YES to continue iterating
180  */
181 int watch_notifier_it(void *cls,
182     const struct GNUNET_HashCode *key,
183     void *value)
184 {
185   struct GNUNET_PEERSTORE_Record *record = cls;
186   struct GNUNET_SERVER_Client *client = value;
187   struct StoreRecordMessage *srm;
188
189   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Found a watcher to update.\n");
190   srm = PEERSTORE_create_record_message(record->sub_system,
191       record->peer,
192       record->key,
193       record->value,
194       record->value_size,
195       record->expiry,
196       GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_RECORD);
197   GNUNET_SERVER_notification_context_unicast(nc, client,
198       (const struct GNUNET_MessageHeader *)srm, GNUNET_NO);
199   GNUNET_free(srm);
200   return GNUNET_YES;
201 }
202
203 /**
204  * Given a new record, notifies watchers
205  *
206  * @param record changed record to update watchers with
207  */
208 void watch_notifier (struct GNUNET_PEERSTORE_Record *record)
209 {
210   struct GNUNET_HashCode keyhash;
211
212   PEERSTORE_hash_key(record->sub_system,
213       record->peer,
214       record->key,
215       &keyhash);
216   GNUNET_CONTAINER_multihashmap_get_multiple(watchers, &keyhash, &watch_notifier_it, record);
217 }
218
219 /**
220  * Handle a watch cancel request from client
221  *
222  * @param cls unused
223  * @param client identification of the client
224  * @param message the actual message
225  */
226 void handle_watch_cancel (void *cls,
227     struct GNUNET_SERVER_Client *client,
228     const struct GNUNET_MessageHeader *message)
229 {
230   struct StoreKeyHashMessage *hm;
231
232   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Received a watch cancel request from client.\n");
233   hm = (struct StoreKeyHashMessage *) message;
234   GNUNET_CONTAINER_multihashmap_remove(watchers, &hm->keyhash, client);
235   GNUNET_SERVER_receive_done(client, GNUNET_OK);
236 }
237
238 /**
239  * Handle a watch request from client
240  *
241  * @param cls unused
242  * @param client identification of the client
243  * @param message the actual message
244  */
245 void handle_watch (void *cls,
246     struct GNUNET_SERVER_Client *client,
247     const struct GNUNET_MessageHeader *message)
248 {
249   struct StoreKeyHashMessage *hm;
250
251   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Received a watch request from client.\n");
252   hm = (struct StoreKeyHashMessage *) message;
253   GNUNET_SERVER_client_mark_monitor(client);
254   GNUNET_SERVER_notification_context_add(nc, client);
255   GNUNET_CONTAINER_multihashmap_put(watchers, &hm->keyhash,
256      client, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
257   GNUNET_SERVER_receive_done(client, GNUNET_OK);
258 }
259
260 /**
261  * Handle an iterate request from client
262  *
263  * @param cls unused
264  * @param client identification of the client
265  * @param message the actual message
266  */
267 void handle_iterate (void *cls,
268     struct GNUNET_SERVER_Client *client,
269     const struct GNUNET_MessageHeader *message)
270 {
271   struct GNUNET_PEERSTORE_Record *record;
272   struct GNUNET_MessageHeader *endmsg;
273
274   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Received an iterate request from client.\n");
275   record = PEERSTORE_parse_record_message(message);
276   if(NULL == record)
277   {
278     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Malformed iterate request from client\n"));
279     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
280     return;
281   }
282   if(NULL == record->sub_system)
283   {
284     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Sub system not supplied in client iterate request\n"));
285     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
286     return;
287   }
288   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Iterate request: ss `%s', peer `%s', key `%s'\n",
289       record->sub_system,
290       (NULL == record->peer) ? "NULL" : GNUNET_i2s(record->peer),
291       (NULL == record->key) ? "NULL" : record->key);
292   GNUNET_SERVER_notification_context_add(nc, client);
293   if(GNUNET_OK == db->iterate_records(db->cls,
294       record->sub_system,
295       record->peer,
296       record->key,
297       &record_iterator,
298       client))
299   {
300     endmsg = GNUNET_new(struct GNUNET_MessageHeader);
301     endmsg->size = htons(sizeof(struct GNUNET_MessageHeader));
302     endmsg->type = htons(GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_END);
303     GNUNET_SERVER_notification_context_unicast(nc, client, endmsg, GNUNET_NO);
304     GNUNET_free(endmsg);
305     GNUNET_SERVER_receive_done(client, GNUNET_OK);
306   }
307   else
308   {
309     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
310   }
311   PEERSTORE_destroy_record(record);
312 }
313
314 /**
315  * Handle a store request from client
316  *
317  * @param cls unused
318  * @param client identification of the client
319  * @param message the actual message
320  */
321 void handle_store (void *cls,
322     struct GNUNET_SERVER_Client *client,
323     const struct GNUNET_MessageHeader *message)
324 {
325   struct GNUNET_PEERSTORE_Record *record;
326   struct StoreRecordMessage *srm;
327
328   record = PEERSTORE_parse_record_message(message);
329   if(NULL == record)
330   {
331     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Malformed store request from client\n"));
332     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
333     return;
334   }
335   srm = (struct StoreRecordMessage *)message;
336   if(NULL == record->sub_system
337       || NULL == record->peer
338       || NULL == record->key)
339   {
340     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Full key not supplied in client store request\n"));
341     PEERSTORE_destroy_record(record);
342     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
343     return;
344   }
345   GNUNET_log(GNUNET_ERROR_TYPE_INFO, "Received a store request (size: %lu) for sub system `%s', peer `%s', key `%s'\n",
346       record->value_size,
347       record->sub_system,
348       GNUNET_i2s (record->peer),
349       record->key);
350   if(GNUNET_OK != db->store_record(db->cls,
351       record->sub_system,
352       record->peer,
353       record->key,
354       record->value,
355       record->value_size,
356       *record->expiry,
357       srm->options))
358   {
359     GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Failed to store requested value, sqlite database error."));
360     PEERSTORE_destroy_record(record);
361     GNUNET_SERVER_receive_done(client, GNUNET_SYSERR);
362     return;
363   }
364   GNUNET_SERVER_receive_done(client, GNUNET_OK);
365   watch_notifier(record);
366   PEERSTORE_destroy_record(record);
367 }
368
369 /**
370  * Peerstore service runner.
371  *
372  * @param cls closure
373  * @param server the initialized server
374  * @param c configuration to use
375  */
376 static void
377 run (void *cls,
378      struct GNUNET_SERVER_Handle *server,
379      const struct GNUNET_CONFIGURATION_Handle *c)
380 {
381   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
382       {&handle_store, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_STORE, 0},
383       {&handle_iterate, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE, 0},
384       {&handle_watch, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH, sizeof(struct StoreKeyHashMessage)},
385       {&handle_watch_cancel, NULL, GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_CANCEL, sizeof(struct StoreKeyHashMessage)},
386       {NULL, NULL, 0, 0}
387   };
388   char *database;
389
390   cfg = c;
391   if (GNUNET_OK !=
392         GNUNET_CONFIGURATION_get_value_string (cfg, "peerstore", "DATABASE",
393                                                &database))
394     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("No database backend configured\n"));
395
396   else
397   {
398     GNUNET_asprintf (&db_lib_name, "libgnunet_plugin_peerstore_%s", database);
399     db = GNUNET_PLUGIN_load(db_lib_name, (void *) cfg);
400     GNUNET_free(database);
401   }
402   if(NULL == db)
403   {
404           GNUNET_log(GNUNET_ERROR_TYPE_ERROR, _("Could not load database backend `%s'\n"), db_lib_name);
405           GNUNET_SCHEDULER_add_now (&shutdown_task, NULL);
406           return;
407   }
408   else
409   {
410     nc = GNUNET_SERVER_notification_context_create (server, 16);
411     watchers = GNUNET_CONTAINER_multihashmap_create(10, GNUNET_NO);
412     GNUNET_SCHEDULER_add_now(&cleanup_expired_records, NULL);
413     GNUNET_SERVER_add_handlers (server, handlers);
414     GNUNET_SERVER_disconnect_notify (server,
415              &handle_client_disconnect,
416              NULL);
417   }
418   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
419                                 &shutdown_task,
420                                 NULL);
421 }
422
423
424 /**
425  * The main function for the peerstore service.
426  *
427  * @param argc number of arguments from the command line
428  * @param argv command line arguments
429  * @return 0 ok, 1 on error
430  */
431 int
432 main (int argc, char *const *argv)
433 {
434   return (GNUNET_OK ==
435           GNUNET_SERVICE_run (argc,
436                               argv,
437                               "peerstore",
438                               GNUNET_SERVICE_OPTION_NONE,
439                               &run, NULL)) ? 0 : 1;
440 }
441
442 /* end of gnunet-service-peerstore.c */