c9523f2033578b250684d148234bf8336f7324eb
[oweals/gnunet.git] / src / util / helper.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011, 2012 Christian Grothoff
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 util/helper.c
23  * @brief API for dealing with (SUID) helper processes that communicate via
24  *          GNUNET_MessageHeaders on stdin/stdout
25  * @author Philipp Toelke
26  * @author Christian Grothoff
27  */
28 #include "platform.h"
29 #include "gnunet_util_lib.h"
30
31
32 /**
33  * Entry in the queue of messages we need to transmit to the helper.
34  */
35 struct GNUNET_HELPER_SendHandle
36 {
37
38   /**
39    * This is an entry in a DLL.
40    */
41   struct GNUNET_HELPER_SendHandle *next;
42
43   /**
44    * This is an entry in a DLL.
45    */
46   struct GNUNET_HELPER_SendHandle *prev;
47
48   /**
49    * Message to transmit (allocated at the end of this struct)
50    */
51   const struct GNUNET_MessageHeader *msg;
52  
53   /**
54    * The handle to a helper process.
55    */
56   struct GNUNET_HELPER_Handle *h;
57  
58   /**
59    * Function to call upon completion.
60    */
61   GNUNET_HELPER_Continuation cont;
62
63   /**
64    * Closure to 'cont'.
65    */
66   void *cont_cls;
67
68   /**
69    * Current write position.
70    */
71   unsigned int wpos;
72
73 };
74
75
76 /**
77  * The handle to a helper process.
78  */
79 struct GNUNET_HELPER_Handle
80 {
81
82   /**
83    * PipeHandle to receive data from the helper
84    */
85   struct GNUNET_DISK_PipeHandle *helper_in;
86   
87   /**
88    * PipeHandle to send data to the helper
89    */
90   struct GNUNET_DISK_PipeHandle *helper_out;
91   
92   /**
93    * FileHandle to receive data from the helper
94    */
95   const struct GNUNET_DISK_FileHandle *fh_from_helper;
96   
97   /**
98    * FileHandle to send data to the helper
99    */
100   const struct GNUNET_DISK_FileHandle *fh_to_helper;
101   
102   /**
103    * The process id of the helper
104    */
105   struct GNUNET_OS_Process *helper_proc;
106
107   /**
108    * The Message-Tokenizer that tokenizes the messages comming from the helper
109    */
110   struct GNUNET_SERVER_MessageStreamTokenizer *mst;
111
112   /**
113    * The exception callback
114    */
115   GNUNET_HELPER_ExceptionCallback exp_cb;
116
117   /**
118    * The closure for callbacks
119    */
120   void *cb_cls;
121
122   /**
123    * First message queued for transmission to helper.
124    */
125   struct GNUNET_HELPER_SendHandle *sh_head;
126
127   /**
128    * Last message queued for transmission to helper.
129    */
130   struct GNUNET_HELPER_SendHandle *sh_tail;
131
132   /**
133    * Binary to run.
134    */
135   char *binary_name;
136
137   /**
138    * NULL-terminated list of command-line arguments.
139    */
140   char **binary_argv;
141                     
142   /**
143    * Task to read from the helper.
144    */
145   GNUNET_SCHEDULER_TaskIdentifier read_task;
146
147   /**
148    * Task to read from the helper.
149    */
150   GNUNET_SCHEDULER_TaskIdentifier write_task;
151
152   /**
153    * Restart task.
154    */
155   GNUNET_SCHEDULER_TaskIdentifier restart_task;
156
157   /**
158    * Does the helper support the use of a control pipe for signalling?
159    */
160   int with_control_pipe;
161
162 };
163
164
165 /**
166  * Sends termination signal to the helper process.  The helper process is not
167  * reaped; call GNUNET_HELPER_wait() for reaping the dead helper process.
168  *
169  * @param h the helper handle
170  * @param soft_kill if GNUNET_YES, signals termination by closing the helper's
171  *          stdin; GNUNET_NO to signal termination by sending SIGTERM to helper
172  * @return GNUNET_OK on success; GNUNET_SYSERR on error
173  */
174 int
175 GNUNET_HELPER_kill (struct GNUNET_HELPER_Handle *h, int soft_kill)
176 {
177   struct GNUNET_HELPER_SendHandle *sh;
178   int ret;
179
180   while (NULL != (sh = h->sh_head))
181   {
182     GNUNET_CONTAINER_DLL_remove (h->sh_head,
183                                  h->sh_tail,
184                                  sh);
185     if (NULL != sh->cont)
186       sh->cont (sh->cont_cls, GNUNET_NO);
187     GNUNET_free (sh);
188   }
189   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
190   {
191     GNUNET_SCHEDULER_cancel (h->restart_task);
192     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
193   }
194   if (NULL == h->helper_proc)
195     return GNUNET_SYSERR;
196   if (GNUNET_YES == soft_kill)
197   { 
198     /* soft-kill only possible with pipes */
199     GNUNET_assert (NULL != h->helper_in);
200     ret = GNUNET_DISK_pipe_close (h->helper_in);
201     h->helper_in = NULL;
202     h->fh_to_helper = NULL;
203     return ret;
204   }
205   if (0 != GNUNET_OS_process_kill (h->helper_proc, SIGTERM))
206     return GNUNET_SYSERR;
207   return GNUNET_OK;
208 }
209
210
211 /**
212  * Reap the helper process.  This call is blocking(!).  The helper process
213  * should either be sent a termination signal before or should be dead before
214  * calling this function
215  *
216  * @param h the helper handle
217  * @return GNUNET_OK on success; GNUNET_SYSERR on error
218  */
219 int
220 GNUNET_HELPER_wait (struct GNUNET_HELPER_Handle *h)
221 {
222   struct GNUNET_HELPER_SendHandle *sh;
223   int ret;
224
225   ret = GNUNET_SYSERR;
226   if (NULL != h->helper_proc)
227   {
228     ret = GNUNET_OS_process_wait (h->helper_proc);
229     GNUNET_OS_process_destroy (h->helper_proc);
230     h->helper_proc = NULL;
231   }
232   if (GNUNET_SCHEDULER_NO_TASK != h->read_task)
233   {
234     GNUNET_SCHEDULER_cancel (h->read_task);
235     h->read_task = GNUNET_SCHEDULER_NO_TASK;
236   }
237   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
238   {
239     GNUNET_SCHEDULER_cancel (h->write_task);
240     h->write_task = GNUNET_SCHEDULER_NO_TASK;
241   }
242   if (NULL != h->helper_in)
243   {
244     GNUNET_DISK_pipe_close (h->helper_in);
245     h->helper_in = NULL;
246     h->fh_to_helper = NULL;
247   }
248   if (NULL != h->helper_out)
249   {
250     GNUNET_DISK_pipe_close (h->helper_out);
251     h->helper_out = NULL;
252     h->fh_from_helper = NULL;
253   }
254   while (NULL != (sh = h->sh_head))
255   {
256     GNUNET_CONTAINER_DLL_remove (h->sh_head,
257                                  h->sh_tail,
258                                  sh);
259     if (NULL != sh->cont)
260       sh->cont (sh->cont_cls, GNUNET_NO);
261     GNUNET_free (sh);
262   }
263   /* purge MST buffer */
264   (void) GNUNET_SERVER_mst_receive (h->mst, NULL, NULL, 0, GNUNET_YES, GNUNET_NO);
265   return ret;
266 }
267
268
269 /**
270  * Stop the helper process, we're closing down or had an error.
271  *
272  * @param h handle to the helper process
273  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
274  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
275  */
276 static void
277 stop_helper (struct GNUNET_HELPER_Handle *h, int soft_kill)
278 {
279   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
280   {
281     GNUNET_SCHEDULER_cancel (h->restart_task);
282     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
283   }
284   else
285   {
286     GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
287     GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
288   }
289 }
290
291
292 /**
293  * Restart the helper process.
294  *
295  * @param cls handle to the helper process
296  * @param tc scheduler context
297  */
298 static void
299 restart_task (void *cls,
300               const struct GNUNET_SCHEDULER_TaskContext *tc);
301
302
303 /**
304  * Read from the helper-process
305  *
306  * @param cls handle to the helper process
307  * @param tc scheduler context
308  */
309 static void
310 helper_read (void *cls,
311              const struct GNUNET_SCHEDULER_TaskContext *tc)
312 {
313   struct GNUNET_HELPER_Handle *h = cls;
314   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
315   ssize_t t;
316
317   h->read_task = GNUNET_SCHEDULER_NO_TASK;
318   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
319   {
320     /* try again */
321     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
322                                                    h->fh_from_helper, &helper_read, h);
323     return;
324   }
325   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof (buf));
326   if (t < 0)
327   {
328     /* On read-error, restart the helper */
329     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
330                 _("Error reading from `%s': %s\n"),
331                 h->binary_name,
332                 STRERROR (errno));
333     if (NULL != h->exp_cb)
334     {
335       h->exp_cb (h->cb_cls);
336       GNUNET_HELPER_stop (h, GNUNET_NO);
337       return;
338     }
339     stop_helper (h, GNUNET_NO);
340     /* Restart the helper */
341     h->restart_task =
342         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &restart_task, h);
343     return;
344   }
345   if (0 == t)
346   {
347     /* this happens if the helper is shut down via a 
348        signal, so it is not a "hard" error */
349     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
350                 "Got 0 bytes from helper `%s' (EOF)\n",
351                 h->binary_name);
352     if (NULL != h->exp_cb)
353     {
354       h->exp_cb (h->cb_cls);
355       GNUNET_HELPER_stop (h, GNUNET_NO);
356       return;
357     }
358     stop_helper (h, GNUNET_NO);
359     /* Restart the helper */
360     h->restart_task =
361       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
362                                     &restart_task, h);
363     return;
364   }
365   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
366               "Got %u bytes from helper `%s'\n",
367               (unsigned int) t,
368               h->binary_name);
369   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
370                                                  h->fh_from_helper, &helper_read, h);
371   if (GNUNET_SYSERR ==
372       GNUNET_SERVER_mst_receive (h->mst, NULL, buf, t, GNUNET_NO, GNUNET_NO))
373   {
374     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
375                 _("Failed to parse inbound message from helper `%s'\n"),
376                 h->binary_name);
377     if (NULL != h->exp_cb)
378     {
379       h->exp_cb (h->cb_cls);
380       GNUNET_HELPER_stop (h, GNUNET_NO);
381       return;
382     }     
383     stop_helper (h, GNUNET_NO);
384     /* Restart the helper */
385     h->restart_task =
386         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
387                                       &restart_task, h);
388     return;
389   }
390 }
391
392
393 /**
394  * Start the helper process.
395  *
396  * @param h handle to the helper process
397  */
398 static void
399 start_helper (struct GNUNET_HELPER_Handle *h)
400 {
401   h->helper_in = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
402   h->helper_out = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
403   if ( (h->helper_in == NULL) || (h->helper_out == NULL))
404   {
405     /* out of file descriptors? try again later... */
406     stop_helper (h, GNUNET_NO);
407     h->restart_task =
408       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
409                                     &restart_task, h);    
410     return;
411   }
412   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
413               "Starting HELPER process `%s'\n",
414               h->binary_name);
415   h->fh_from_helper =
416       GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
417   h->fh_to_helper =
418       GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
419   h->helper_proc =
420     GNUNET_OS_start_process_vap (h->with_control_pipe, GNUNET_OS_INHERIT_STD_ERR, 
421                                  h->helper_in, h->helper_out,
422                                  h->binary_name,
423                                  h->binary_argv);
424   if (NULL == h->helper_proc)
425   {
426     /* failed to start process? try again later... */
427     stop_helper (h, GNUNET_NO);
428     h->restart_task =
429       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
430                                     &restart_task, h);    
431     return;
432   }
433   GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
434   GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
435   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
436                                                  h->fh_from_helper, 
437                                                  &helper_read, 
438                                                  h);
439 }
440
441
442 /**
443  * Restart the helper process.
444  *
445  * @param cls handle to the helper process
446  * @param tc scheduler context
447  */
448 static void
449 restart_task (void *cls,
450               const struct GNUNET_SCHEDULER_TaskContext *tc)
451 {
452   struct GNUNET_HELPER_Handle*h = cls;
453
454   h->restart_task = GNUNET_SCHEDULER_NO_TASK;
455   start_helper (h);
456 }
457
458
459 /**
460  * Starts a helper and begins reading from it. The helper process is
461  * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
462  * or when the exp_cb callback is not NULL.
463  *
464  * @param with_control_pipe does the helper support the use of a control pipe for signalling?
465  * @param binary_name name of the binary to run
466  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
467  *                    argument must not be modified by the client for
468  *                     the lifetime of the helper handle)
469  * @param cb function to call if we get messages from the helper
470  * @param exp_cb the exception callback to call. Set this to NULL if the helper
471  *          process has to be restarted automatically when it dies/crashes
472  * @param cb_cls closure for the above callback
473  * @return the new Handle, NULL on error
474  */
475 struct GNUNET_HELPER_Handle *
476 GNUNET_HELPER_start (int with_control_pipe,
477                      const char *binary_name,
478                      char *const binary_argv[],
479                      GNUNET_SERVER_MessageTokenizerCallback cb,
480                      GNUNET_HELPER_ExceptionCallback exp_cb,
481                      void *cb_cls)
482 {
483   struct GNUNET_HELPER_Handle *h;
484   unsigned int c;
485
486   h = GNUNET_malloc (sizeof (struct GNUNET_HELPER_Handle));
487   h->with_control_pipe = with_control_pipe;
488   /* Lookup in libexec path only if we are starting gnunet helpers */
489   if (NULL != strstr (binary_name, "gnunet"))
490     h->binary_name = GNUNET_OS_get_libexec_binary_path (binary_name);
491   else
492     h->binary_name = strdup (binary_name);
493   for (c = 0; NULL != binary_argv[c]; c++);
494   h->binary_argv = GNUNET_malloc (sizeof (char *) * (c + 1));
495   for (c = 0; NULL != binary_argv[c]; c++)
496     h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
497   h->binary_argv[c] = NULL;
498   h->cb_cls = cb_cls;
499   h->mst = GNUNET_SERVER_mst_create (cb, h->cb_cls);
500   h->exp_cb = exp_cb;
501   start_helper (h);
502   return h;
503 }
504
505
506 /**
507  * Free's the resources occupied by the helper handle
508  *
509  * @param h the helper handle to free
510  */
511 void
512 GNUNET_HELPER_destroy (struct GNUNET_HELPER_Handle *h)
513 {
514   unsigned int c;
515   struct GNUNET_HELPER_SendHandle *sh;
516
517   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
518   {
519     GNUNET_SCHEDULER_cancel (h->write_task);
520     h->write_task = GNUNET_SCHEDULER_NO_TASK;
521   }
522   while (NULL != (sh = h->sh_head))
523   {
524     GNUNET_CONTAINER_DLL_remove (h->sh_head,
525                                  h->sh_tail,
526                                  sh);
527     if (NULL != sh->cont)
528       sh->cont (sh->cont_cls, GNUNET_SYSERR);
529     GNUNET_free (sh);
530   }
531   GNUNET_SERVER_mst_destroy (h->mst);
532   GNUNET_free (h->binary_name);
533   for (c = 0; h->binary_argv[c] != NULL; c++)
534     GNUNET_free (h->binary_argv[c]);
535   GNUNET_free (h->binary_argv);
536   GNUNET_free (h);
537 }
538
539
540 /**
541  * Kills the helper, closes the pipe and frees the handle
542  *
543  * @param h handle to helper to stop
544  * @param soft_kill if GNUNET_YES, signals termination by closing the helper's
545  *          stdin; GNUNET_NO to signal termination by sending SIGTERM to helper
546  */
547 void
548 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h, int soft_kill)
549 {
550   h->exp_cb = NULL;
551   stop_helper (h, soft_kill);
552   GNUNET_HELPER_destroy (h);
553 }
554
555
556 /**
557  * Write to the helper-process
558  *
559  * @param cls handle to the helper process
560  * @param tc scheduler context
561  */
562 static void
563 helper_write (void *cls,
564              const struct GNUNET_SCHEDULER_TaskContext *tc)
565 {
566   struct GNUNET_HELPER_Handle *h = cls;
567   struct GNUNET_HELPER_SendHandle *sh;
568   const char *buf;
569   ssize_t t;
570
571   h->write_task = GNUNET_SCHEDULER_NO_TASK;
572   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
573   {
574     /* try again */
575     h->write_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
576                                                     h->fh_to_helper, &helper_write, h);
577     return;
578   }  
579   if (NULL == (sh = h->sh_head))
580     return; /* how did this happen? */
581   buf = (const char*) sh->msg;
582   t = GNUNET_DISK_file_write (h->fh_to_helper, &buf[sh->wpos], ntohs (sh->msg->size) - sh->wpos);
583   if (t <= 0)
584   {
585     /* On write-error, restart the helper */
586     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
587                 _("Error writing to `%s': %s\n"),
588                 h->binary_name,
589                 STRERROR (errno));
590     if (NULL != h->exp_cb)
591     {
592       h->exp_cb (h->cb_cls);
593       GNUNET_HELPER_stop (h, GNUNET_NO);
594       return;
595     }
596     stop_helper (h, GNUNET_NO);
597     /* Restart the helper */
598     h->restart_task =
599       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
600                                     &restart_task, h);
601     return;
602   }
603   sh->wpos += t;
604   if (sh->wpos == ntohs (sh->msg->size))
605   {
606     GNUNET_CONTAINER_DLL_remove (h->sh_head,
607                                  h->sh_tail,
608                                  sh);
609     if (NULL != sh->cont)
610       sh->cont (sh->cont_cls, GNUNET_YES);
611     GNUNET_free (sh);
612   }
613   if (NULL != h->sh_head)
614     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
615                                                      h->fh_to_helper, 
616                                                      &helper_write, 
617                                                      h);
618 }
619
620
621 /**
622  * Send an message to the helper.
623  *
624  * @param h helper to send message to
625  * @param msg message to send
626  * @param can_drop can the message be dropped if there is already one in the queue?
627  * @param cont continuation to run once the message is out (PREREQ_DONE on succees, CANCEL
628  *             if the helper process died, NULL during GNUNET_HELPER_stop).
629  * @param cont_cls closure for 'cont'
630  * @return NULL if the message was dropped, 
631  *         otherwise handle to cancel *cont* (actual transmission may
632  *         not be abortable)
633  */
634 struct GNUNET_HELPER_SendHandle *
635 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h, 
636                     const struct GNUNET_MessageHeader *msg,
637                     int can_drop,
638                     GNUNET_HELPER_Continuation cont,
639                     void *cont_cls)
640 {
641   struct GNUNET_HELPER_SendHandle *sh;
642   uint16_t mlen;
643
644   if (NULL == h->fh_to_helper)
645     return NULL;
646   if ( (GNUNET_YES == can_drop) &&
647        (NULL != h->sh_head) )
648     return NULL;
649   mlen = ntohs (msg->size);
650   sh = GNUNET_malloc (sizeof (struct GNUNET_HELPER_SendHandle) + mlen);
651   sh->msg = (const struct GNUNET_MessageHeader*) &sh[1];
652   memcpy (&sh[1], msg, mlen);
653   sh->h = h;
654   sh->cont = cont;
655   sh->cont_cls = cont_cls;
656   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head,
657                                     h->sh_tail,
658                                     sh);
659   if (GNUNET_SCHEDULER_NO_TASK == h->write_task)
660     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
661                                                      h->fh_to_helper, 
662                                                      &helper_write, 
663                                                      h);
664     
665   return sh;
666 }
667
668 /**
669  * Cancel a 'send' operation.  If possible, transmitting the
670  * message is also aborted, but at least 'cont' won't be
671  * called.
672  *
673  * @param sh operation to cancel
674  */
675 void
676 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
677 {
678   struct GNUNET_HELPER_Handle *h = sh->h;
679
680   sh->cont = NULL;
681   sh->cont_cls = NULL;
682   if (0 == sh->wpos)
683   {
684     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
685     GNUNET_free (sh);
686     if (NULL == h->sh_head)
687     {
688       GNUNET_SCHEDULER_cancel (h->write_task);
689       h->write_task = GNUNET_SCHEDULER_NO_TASK;
690     }
691   }
692 }
693
694
695 /* end of helper.c */