make clang static analysis happy
[oweals/gnunet.git] / src / fs / gnunet-service-fs_drq.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009, 2010 Christian Grothoff (and other contributing authors)
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 2, 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 fs/gnunet-service-fs_drq.c
23  * @brief queueing of requests to the datastore service
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet-service-fs_drq.h"
28
29 #define DEBUG_DRQ GNUNET_NO
30
31 /**
32  * Signature of a function that is called whenever a datastore
33  * request can be processed (or an entry put on the queue times out).
34  *
35  * @param cls closure
36  * @param ok GNUNET_OK if DS is ready, GNUNET_SYSERR on timeout
37  */
38 typedef void (*RequestFunction)(void *cls,
39                                 int ok);
40
41
42 /**
43  * Doubly-linked list of our requests for the datastore.
44  */
45 struct DatastoreRequestQueue
46 {
47
48   /**
49    * This is a doubly-linked list.
50    */
51   struct DatastoreRequestQueue *next;
52
53   /**
54    * This is a doubly-linked list.
55    */
56   struct DatastoreRequestQueue *prev;
57
58   /**
59    * Function to call for each entry.
60    */
61   GNUNET_DATASTORE_Iterator iter;
62
63   /**
64    * Closure for iter.
65    */
66   void *iter_cls;
67
68   /**
69    * Key we are doing the 'get' for.
70    */
71   GNUNET_HashCode key;
72
73   /**
74    * Timeout for this operation.
75    */
76   struct GNUNET_TIME_Absolute timeout;
77     
78   /**
79    * ID of task used for signaling timeout.
80    */
81   GNUNET_SCHEDULER_TaskIdentifier task;
82
83   /**
84    * Datastore entry type we are doing the 'get' for.
85    */
86   uint32_t type;
87
88   /**
89    * Is this request at the head of the queue irrespective of its
90    * timeout value?
91    */
92   int forced_head;
93
94 };
95
96 /**
97  * Our scheduler.
98  */
99 static struct GNUNET_SCHEDULER_Handle *sched;
100
101 /**
102  * Our configuration.
103  */
104 static const struct GNUNET_CONFIGURATION_Handle *cfg;
105
106 /**
107  * Head of request queue for the datastore, sorted by timeout.
108  */
109 static struct DatastoreRequestQueue *drq_head;
110
111 /**
112  * Tail of request queue for the datastore.
113  */
114 static struct DatastoreRequestQueue *drq_tail;
115
116 /**
117  * Our connection to the datastore.
118  */
119 static struct GNUNET_DATASTORE_Handle *dsh;
120
121 /**
122  * Pointer to the currently actively running request,
123  * NULL if none is running.
124  */
125 static struct DatastoreRequestQueue *drq_running;
126
127
128 /**
129  * Run the next DS request in our queue, we're done with the current
130  * one.
131  */
132 static void
133 next_ds_request ();
134
135
136 /**
137  * Wrapper for the datastore get operation.  Makes sure to trigger the
138  * next datastore operation in the queue once the operation is
139  * complete.
140  *
141  * @param cls our 'struct DatastoreRequestQueue*'
142  * @param key key for the content
143  * @param size number of bytes in data
144  * @param data content stored
145  * @param type type of the content
146  * @param priority priority of the content
147  * @param anonymity anonymity-level for the content
148  * @param expiration expiration time for the content
149  * @param uid unique identifier for the datum;
150  *        maybe 0 if no unique identifier is available
151  */
152 static void
153 get_iterator (void *cls,
154               const GNUNET_HashCode * key,
155               uint32_t size,
156               const void *data,
157               uint32_t type,
158               uint32_t priority,
159               uint32_t anonymity,
160               struct GNUNET_TIME_Absolute
161               expiration, 
162               uint64_t uid)
163 {
164   struct DatastoreRequestQueue *gc = cls;
165
166   if (gc->iter == NULL) 
167     {
168       /* stop the iteration */
169 #if DEBUG_DRQ
170       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
171                   "Iteration terminated\n");
172 #endif
173       if (key != NULL)
174         GNUNET_DATASTORE_get_next (dsh, GNUNET_NO);
175     }
176   else
177     {
178 #if DEBUG_DRQ
179       if (key != NULL)
180         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
181                     "Iteration produced %u-byte result for `%s'\n",
182                     size,
183                     GNUNET_h2s (key));
184 #endif
185       gc->iter (gc->iter_cls,
186                 key, size, data, type,
187                 priority, anonymity, expiration, uid);
188     }
189   if (key == NULL)
190     {
191 #if DEBUG_DRQ
192       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
193                   "Iteration completed\n");
194 #endif
195       GNUNET_assert (gc == drq_running);
196       GNUNET_free (gc);
197       drq_running = NULL;
198       next_ds_request ();
199     }
200 }
201
202
203 /**
204  * A datastore request can be run right now.  Run it.
205  *
206  * @param cls closure (of type "struct DatastoreRequestQueue*")
207  * @param tc task context, unused
208  */
209 static void
210 run_next_request (void *cls,
211                   const struct GNUNET_SCHEDULER_TaskContext *tc)
212 {
213   struct DatastoreRequestQueue *gc = cls;
214
215   gc->task = GNUNET_SCHEDULER_NO_TASK;
216 #if DEBUG_DRQ
217   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
218               "Running datastore request for `%s' of type %u\n",
219               GNUNET_h2s (&gc->key),
220               gc->type);
221 #endif
222   GNUNET_DATASTORE_get (dsh, 
223                         &gc->key,
224                         gc->type, 
225                         &get_iterator,
226                         gc,
227                         GNUNET_TIME_absolute_get_remaining(gc->timeout));
228 }
229
230
231 /**
232  * Run the next DS request in our queue, we're done with the current
233  * one.
234  */
235 static void
236 next_ds_request ()
237 {
238   struct DatastoreRequestQueue *e;
239
240   GNUNET_free_non_null (drq_running);
241   drq_running = NULL;
242   e = drq_head;
243   if (e == NULL)
244     return;
245   GNUNET_CONTAINER_DLL_remove (drq_head, drq_tail, e);
246   drq_running = e;
247   GNUNET_SCHEDULER_cancel (sched, e->task);
248   e->task = GNUNET_SCHEDULER_add_now (sched,
249                                       &run_next_request,
250                                       e);
251 }
252
253
254 /**
255  * A datastore request had to be timed out. 
256  *
257  * @param cls closure (unused)
258  * @param tc task context, unused
259  */
260 static void
261 timeout_ds_request (void *cls,
262                     const struct GNUNET_SCHEDULER_TaskContext *tc)
263 {
264   struct DatastoreRequestQueue *e = cls;
265
266 #if DEBUG_DRQ
267   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
268               "Datastore request timed out in queue before transmission\n");
269 #endif
270   e->task = GNUNET_SCHEDULER_NO_TASK;
271   GNUNET_CONTAINER_DLL_remove (drq_head, drq_tail, e);
272   if (e->iter != NULL)
273     e->iter (e->iter_cls,
274              NULL, 0, NULL, 0, 0, 0, 
275              GNUNET_TIME_UNIT_ZERO_ABS, 0);
276   GNUNET_free (e);  
277 }
278
279
280 /**
281  * Task run during shutdown.
282  *
283  * @param cls unused
284  * @param tc unused
285  */
286 static void
287 shutdown_task (void *cls,
288                const struct GNUNET_SCHEDULER_TaskContext *tc)
289 {
290   struct DatastoreRequestQueue *drq;
291
292 #if DEBUG_DRQ
293   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
294               "DRQ shutdown initiated\n");
295 #endif
296   GNUNET_assert (NULL != dsh);
297   GNUNET_DATASTORE_disconnect (dsh,
298                                GNUNET_NO);
299   dsh = NULL;
300   while (NULL != (drq = drq_head))
301     {
302       drq_head = drq->next;
303       GNUNET_SCHEDULER_cancel (sched, drq->task);
304       if (drq->iter != NULL)
305         drq->iter (drq->iter_cls,
306                    NULL, 0, NULL, 0, 0, 0, 
307                    GNUNET_TIME_UNIT_ZERO_ABS, 0);
308       GNUNET_free (drq);
309     }
310   drq_tail = NULL;
311   if (drq_running != NULL)
312     {
313       if (drq_running->task != GNUNET_SCHEDULER_NO_TASK)
314         {
315           GNUNET_SCHEDULER_cancel (sched,
316                                    drq_running->task);
317         }
318       if (drq_running->iter != NULL)
319         {
320           drq_running->iter (drq_running->iter_cls,
321                              NULL, 0, NULL, 0, 0, 0, 
322                              GNUNET_TIME_UNIT_ZERO_ABS, 0);
323         }
324       GNUNET_free (drq_running);
325       drq_running = NULL;
326     }
327 }
328
329
330 /**
331  * Iterate over the results for a particular key
332  * in the datastore.  The iterator will only be called
333  * once initially; if the first call did contain a
334  * result, further results can be obtained by calling
335  * "GNUNET_DATASTORE_get_next" with the given argument.
336  *
337  * @param key maybe NULL (to match all entries)
338  * @param type desired type, 0 for any
339  * @param iter function to call on each matching value;
340  *        will be called once with a NULL value at the end
341  * @param iter_cls closure for iter
342  * @param timeout how long to wait at most for a response
343  * @param immediate should this be queued immediately at
344  *        the head of the queue (irrespecitive of the timeout)?
345  */
346 struct DatastoreRequestQueue *
347 GNUNET_FS_drq_get (const GNUNET_HashCode * key,
348                    uint32_t type,
349                    GNUNET_DATASTORE_Iterator iter, 
350                    void *iter_cls,
351                    struct GNUNET_TIME_Relative timeout,
352                    int immediate)
353 {
354   struct DatastoreRequestQueue *e;
355   struct DatastoreRequestQueue *bef;
356
357 #if DEBUG_DRQ
358   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
359               "DRQ receives request for `%s' of type %u\n",
360               GNUNET_h2s (key),
361               type);
362 #endif
363   e = GNUNET_malloc (sizeof (struct DatastoreRequestQueue));
364   e->timeout = GNUNET_TIME_relative_to_absolute (timeout);
365   e->forced_head = immediate;
366   e->key = *key;
367   e->type = type;
368   e->iter = iter;
369   e->iter_cls = iter_cls;
370   e->timeout = GNUNET_TIME_relative_to_absolute (timeout);
371   if (GNUNET_YES == immediate)
372     {
373       /* local request, highest prio, put at head of queue
374          regardless of deadline */
375       bef = NULL;
376     }
377   else
378     {
379       bef = drq_tail;
380       while ( (NULL != bef) &&
381               (e->timeout.value < bef->timeout.value) &&
382               (GNUNET_YES != e->forced_head) )
383         bef = bef->prev;
384     }
385   GNUNET_CONTAINER_DLL_insert_after (drq_head, drq_tail, bef, e);
386   e->task = GNUNET_SCHEDULER_add_delayed (sched,
387                                           timeout,
388                                           &timeout_ds_request,
389                                           e);
390   if (drq_running == NULL)
391     next_ds_request ();
392   return e;                                    
393 }
394
395
396 /**
397  * Cancel the given operation.
398  *
399  * @param drq the queued operation (must not have been
400  *        triggered so far)
401  */
402 void
403 GNUNET_FS_drq_get_cancel (struct DatastoreRequestQueue *drq)
404 {
405 #if DEBUG_DRQ
406   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
407               "DRQ receives request cancellation request\n");
408 #endif
409   if (drq == drq_running)
410     {
411       /* 'DATASTORE_get' has already been started (and this call might
412          actually be be legal since it is possible that the client has
413          not yet received any calls to its the iterator; so we need to
414          cancel somehow; we do this by zeroing the 'iter' field, which
415          stops the iteration */
416       drq_running->iter = NULL;
417     }
418   else
419     {
420       GNUNET_CONTAINER_DLL_remove (drq_head, drq_tail, drq);
421       GNUNET_SCHEDULER_cancel (sched, drq->task);
422       GNUNET_free (drq);
423     }
424 }
425
426
427 /**
428  * Function called to trigger obtaining the next result
429  * from the datastore.
430  * 
431  * @param more GNUNET_YES to get more results, GNUNET_NO to abort
432  *        iteration (with a final call to "iter" with key/data == NULL).
433  */
434 void
435 GNUNET_FS_drq_get_next (int more)
436 {
437 #if DEBUG_DRQ
438   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
439               "DRQ receives request for next result (more is %d)\n",
440               more);
441 #endif
442   GNUNET_DATASTORE_get_next (dsh, more);
443 }
444
445
446 /**
447  * Closure for 'drq_remove_cont'.
448  */
449 struct RemoveContext
450 {
451   struct GNUNET_DATASTORE_Handle *rmdsh; 
452   GNUNET_DATASTORE_ContinuationWithStatus cont;
453   void *cont_cls;
454 };
455
456
457 static void 
458 drq_remove_cont (void *cls,
459                  int success,
460                  const char *msg)
461 {
462   struct RemoveContext *rc = cls;
463
464   rc->cont (rc->cont_cls,
465             success,
466             msg);
467   GNUNET_DATASTORE_disconnect (rc->rmdsh, GNUNET_NO);
468   GNUNET_free (rc);
469 }
470
471
472 /**
473  * Explicitly remove some content from the database.
474  * The "cont"inuation will be called with status
475  * "GNUNET_OK" if content was removed, "GNUNET_NO"
476  * if no matching entry was found and "GNUNET_SYSERR"
477  * on all other types of errors.
478  *
479  * @param key key for the value
480  * @param size number of bytes in data
481  * @param data content stored
482  * @param cont continuation to call when done
483  * @param cont_cls closure for cont
484  * @param timeout how long to wait at most for a response
485  */
486 void
487 GNUNET_FS_drq_remove (const GNUNET_HashCode *key,
488                       uint32_t size, const void *data,
489                       GNUNET_DATASTORE_ContinuationWithStatus cont,
490                       void *cont_cls,
491                       struct GNUNET_TIME_Relative timeout)
492 {
493   struct GNUNET_DATASTORE_Handle *rmdsh; 
494   struct RemoveContext *rc;
495
496   rmdsh = GNUNET_DATASTORE_connect (cfg,
497                                     sched);
498   if (rmdsh == NULL)
499     {
500       GNUNET_break (0);
501       cont (cont_cls,
502             GNUNET_SYSERR,
503             _("Failed to connect to datastore"));
504       return;
505     }
506   rc = GNUNET_malloc (sizeof (struct RemoveContext));
507   rc->cont = cont;
508   rc->cont_cls = cont_cls;
509   rc->rmdsh = rmdsh;
510   GNUNET_DATASTORE_remove (rmdsh, key, size, data,
511                            &drq_remove_cont, 
512                            rc, timeout);
513 }
514
515
516 /**
517  * Setup datastore request queues.
518  * 
519  * @param s scheduler to use
520  * @param c configuration to use
521  * @return GNUNET_OK on success
522  */
523 int 
524 GNUNET_FS_drq_init (struct GNUNET_SCHEDULER_Handle *s,
525                     const struct GNUNET_CONFIGURATION_Handle *c)
526 {
527   sched = s;
528   cfg = c;
529   dsh = GNUNET_DATASTORE_connect (cfg,
530                                   sched);
531   if (NULL == dsh)
532     {
533       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
534                   _("Failed to connect to `%s' service.\n"),
535                   "datastore");
536       return GNUNET_SYSERR;
537     }
538   GNUNET_SCHEDULER_add_delayed (sched,
539                                 GNUNET_TIME_UNIT_FOREVER_REL,
540                                 &shutdown_task,
541                                 NULL);
542   return GNUNET_OK;
543 }
544
545
546 /* end of gnunet-service-fs_drq.c */