9037660a8c47f107ba6cbf2a256a0b85715ad00a
[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, 
176                     int soft_kill)
177 {
178   struct GNUNET_HELPER_SendHandle *sh;
179   int ret;
180
181   while (NULL != (sh = h->sh_head))
182   {
183     GNUNET_CONTAINER_DLL_remove (h->sh_head,
184                                  h->sh_tail,
185                                  sh);
186     if (NULL != sh->cont)
187       sh->cont (sh->cont_cls, GNUNET_NO);
188     GNUNET_free (sh);
189   }
190   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
191   {
192     GNUNET_SCHEDULER_cancel (h->restart_task);
193     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
194   }
195   if (GNUNET_SCHEDULER_NO_TASK != h->read_task)
196   {
197     GNUNET_SCHEDULER_cancel (h->read_task);
198     h->read_task = GNUNET_SCHEDULER_NO_TASK;
199   }
200   if (NULL == h->helper_proc)
201     return GNUNET_SYSERR;
202   if (GNUNET_YES == soft_kill)
203   { 
204     /* soft-kill only possible with pipes */
205     GNUNET_assert (NULL != h->helper_in);
206     ret = GNUNET_DISK_pipe_close (h->helper_in);
207     h->helper_in = NULL;
208     h->fh_to_helper = NULL;
209     return ret;
210   }
211   if (0 != GNUNET_OS_process_kill (h->helper_proc, SIGTERM))
212     return GNUNET_SYSERR;
213   return GNUNET_OK;
214 }
215
216
217 /**
218  * Reap the helper process.  This call is blocking(!).  The helper process
219  * should either be sent a termination signal before or should be dead before
220  * calling this function
221  *
222  * @param h the helper handle
223  * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
224  */
225 int
226 GNUNET_HELPER_wait (struct GNUNET_HELPER_Handle *h)
227 {
228   struct GNUNET_HELPER_SendHandle *sh;
229   int ret;
230
231   ret = GNUNET_SYSERR;
232   if (NULL != h->helper_proc)
233   {
234     ret = GNUNET_OS_process_wait (h->helper_proc);
235     GNUNET_OS_process_destroy (h->helper_proc);
236     h->helper_proc = NULL;
237   }
238   if (GNUNET_SCHEDULER_NO_TASK != h->read_task)
239   {
240     GNUNET_SCHEDULER_cancel (h->read_task);
241     h->read_task = GNUNET_SCHEDULER_NO_TASK;
242   }
243   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
244   {
245     GNUNET_SCHEDULER_cancel (h->write_task);
246     h->write_task = GNUNET_SCHEDULER_NO_TASK;
247   }
248   if (NULL != h->helper_in)
249   {
250     GNUNET_DISK_pipe_close (h->helper_in);
251     h->helper_in = NULL;
252     h->fh_to_helper = NULL;
253   }
254   if (NULL != h->helper_out)
255   {
256     GNUNET_DISK_pipe_close (h->helper_out);
257     h->helper_out = NULL;
258     h->fh_from_helper = NULL;
259   }
260   while (NULL != (sh = h->sh_head))
261   {
262     GNUNET_CONTAINER_DLL_remove (h->sh_head,
263                                  h->sh_tail,
264                                  sh);
265     if (NULL != sh->cont)
266       sh->cont (sh->cont_cls, GNUNET_NO);
267     GNUNET_free (sh);
268   }
269   /* purge MST buffer */
270   (void) GNUNET_SERVER_mst_receive (h->mst, NULL, NULL, 0, GNUNET_YES, GNUNET_NO);
271   return ret;
272 }
273
274
275 /**
276  * Stop the helper process, we're closing down or had an error.
277  *
278  * @param h handle to the helper process
279  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
280  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
281  */
282 static void
283 stop_helper (struct GNUNET_HELPER_Handle *h, 
284              int soft_kill)
285 {
286   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
287   {
288     GNUNET_SCHEDULER_cancel (h->restart_task);
289     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
290   }
291   else
292   {
293     GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
294     GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
295   }
296 }
297
298
299 /**
300  * Restart the helper process.
301  *
302  * @param cls handle to the helper process
303  * @param tc scheduler context
304  */
305 static void
306 restart_task (void *cls,
307               const struct GNUNET_SCHEDULER_TaskContext *tc);
308
309
310 /**
311  * Read from the helper-process
312  *
313  * @param cls handle to the helper process
314  * @param tc scheduler context
315  */
316 static void
317 helper_read (void *cls,
318              const struct GNUNET_SCHEDULER_TaskContext *tc)
319 {
320   struct GNUNET_HELPER_Handle *h = cls;
321   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
322   ssize_t t;
323
324   h->read_task = GNUNET_SCHEDULER_NO_TASK;
325   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
326   {
327     /* try again */
328     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
329                                                    h->fh_from_helper, &helper_read, h);
330     return;
331   }
332   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof (buf));
333   if (t < 0)
334   {
335     /* On read-error, restart the helper */
336     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
337                 _("Error reading from `%s': %s\n"),
338                 h->binary_name,
339                 STRERROR (errno));
340     if (NULL != h->exp_cb)
341     {
342       h->exp_cb (h->cb_cls);
343       GNUNET_HELPER_stop (h, GNUNET_NO);
344       return;
345     }
346     stop_helper (h, GNUNET_NO);
347     /* Restart the helper */
348     h->restart_task =
349         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &restart_task, h);
350     return;
351   }
352   if (0 == t)
353   {
354     /* this happens if the helper is shut down via a 
355        signal, so it is not a "hard" error */
356     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
357                 "Got 0 bytes from helper `%s' (EOF)\n",
358                 h->binary_name);
359     if (NULL != h->exp_cb)
360     {
361       h->exp_cb (h->cb_cls);
362       GNUNET_HELPER_stop (h, GNUNET_NO);
363       return;
364     }
365     stop_helper (h, GNUNET_NO);
366     /* Restart the helper */
367     h->restart_task =
368       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
369                                     &restart_task, h);
370     return;
371   }
372   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
373               "Got %u bytes from helper `%s'\n",
374               (unsigned int) t,
375               h->binary_name);
376   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
377                                                  h->fh_from_helper, &helper_read, h);
378   if (GNUNET_SYSERR ==
379       GNUNET_SERVER_mst_receive (h->mst, NULL, buf, t, GNUNET_NO, GNUNET_NO))
380   {
381     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
382                 _("Failed to parse inbound message from helper `%s'\n"),
383                 h->binary_name);
384     if (NULL != h->exp_cb)
385     {
386       h->exp_cb (h->cb_cls);
387       GNUNET_HELPER_stop (h, GNUNET_NO);
388       return;
389     }     
390     stop_helper (h, GNUNET_NO);
391     /* Restart the helper */
392     h->restart_task =
393         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
394                                       &restart_task, h);
395     return;
396   }
397 }
398
399
400 /**
401  * Start the helper process.
402  *
403  * @param h handle to the helper process
404  */
405 static void
406 start_helper (struct GNUNET_HELPER_Handle *h)
407 {
408   h->helper_in = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
409   h->helper_out = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
410   if ( (h->helper_in == NULL) || (h->helper_out == NULL))
411   {
412     /* out of file descriptors? try again later... */
413     stop_helper (h, GNUNET_NO);
414     h->restart_task =
415       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
416                                     &restart_task, h);    
417     return;
418   }
419   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
420               "Starting HELPER process `%s'\n",
421               h->binary_name);
422   h->fh_from_helper =
423       GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
424   h->fh_to_helper =
425       GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
426   h->helper_proc =
427     GNUNET_OS_start_process_vap (h->with_control_pipe, GNUNET_OS_INHERIT_STD_ERR, 
428                                  h->helper_in, h->helper_out,
429                                  h->binary_name,
430                                  h->binary_argv);
431   if (NULL == h->helper_proc)
432   {
433     /* failed to start process? try again later... */
434     stop_helper (h, GNUNET_NO);
435     h->restart_task =
436       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
437                                     &restart_task, h);    
438     return;
439   }
440   GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
441   GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
442   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
443                                                  h->fh_from_helper, 
444                                                  &helper_read, 
445                                                  h);
446 }
447
448
449 /**
450  * Restart the helper process.
451  *
452  * @param cls handle to the helper process
453  * @param tc scheduler context
454  */
455 static void
456 restart_task (void *cls,
457               const struct GNUNET_SCHEDULER_TaskContext *tc)
458 {
459   struct GNUNET_HELPER_Handle*h = cls;
460
461   h->restart_task = GNUNET_SCHEDULER_NO_TASK;
462   start_helper (h);
463 }
464
465
466 /**
467  * Starts a helper and begins reading from it. The helper process is
468  * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
469  * or when the exp_cb callback is not NULL.
470  *
471  * @param with_control_pipe does the helper support the use of a control pipe for signalling?
472  * @param binary_name name of the binary to run
473  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
474  *                    argument must not be modified by the client for
475  *                     the lifetime of the helper handle)
476  * @param cb function to call if we get messages from the helper
477  * @param exp_cb the exception callback to call. Set this to NULL if the helper
478  *          process has to be restarted automatically when it dies/crashes
479  * @param cb_cls closure for the above callback
480  * @return the new Handle, NULL on error
481  */
482 struct GNUNET_HELPER_Handle *
483 GNUNET_HELPER_start (int with_control_pipe,
484                      const char *binary_name,
485                      char *const binary_argv[],
486                      GNUNET_SERVER_MessageTokenizerCallback cb,
487                      GNUNET_HELPER_ExceptionCallback exp_cb,
488                      void *cb_cls)
489 {
490   struct GNUNET_HELPER_Handle *h;
491   unsigned int c;
492
493   h = GNUNET_malloc (sizeof (struct GNUNET_HELPER_Handle));
494   h->with_control_pipe = with_control_pipe;
495   /* Lookup in libexec path only if we are starting gnunet helpers */
496   if (NULL != strstr (binary_name, "gnunet"))
497     h->binary_name = GNUNET_OS_get_libexec_binary_path (binary_name);
498   else
499     h->binary_name = strdup (binary_name);
500   for (c = 0; NULL != binary_argv[c]; c++);
501   h->binary_argv = GNUNET_malloc (sizeof (char *) * (c + 1));
502   for (c = 0; NULL != binary_argv[c]; c++)
503     h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
504   h->binary_argv[c] = NULL;
505   h->cb_cls = cb_cls;
506   h->mst = GNUNET_SERVER_mst_create (cb, h->cb_cls);
507   h->exp_cb = exp_cb;
508   start_helper (h);
509   return h;
510 }
511
512
513 /**
514  * Free's the resources occupied by the helper handle
515  *
516  * @param h the helper handle to free
517  */
518 void
519 GNUNET_HELPER_destroy (struct GNUNET_HELPER_Handle *h)
520 {
521   unsigned int c;
522   struct GNUNET_HELPER_SendHandle *sh;
523
524   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
525   {
526     GNUNET_SCHEDULER_cancel (h->write_task);
527     h->write_task = GNUNET_SCHEDULER_NO_TASK;
528   }
529   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->read_task);
530   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == h->restart_task);
531   while (NULL != (sh = h->sh_head))
532   {
533     GNUNET_CONTAINER_DLL_remove (h->sh_head,
534                                  h->sh_tail,
535                                  sh);
536     if (NULL != sh->cont)
537       sh->cont (sh->cont_cls, GNUNET_SYSERR);
538     GNUNET_free (sh);
539   }
540   GNUNET_SERVER_mst_destroy (h->mst);
541   GNUNET_free (h->binary_name);
542   for (c = 0; h->binary_argv[c] != NULL; c++)
543     GNUNET_free (h->binary_argv[c]);
544   GNUNET_free (h->binary_argv);
545   GNUNET_free (h);
546 }
547
548
549 /**
550  * Kills the helper, closes the pipe and frees the handle
551  *
552  * @param h handle to helper to stop
553  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
554  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
555  */
556 void
557 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h, 
558                     int soft_kill)
559 {
560   h->exp_cb = NULL;
561   stop_helper (h, soft_kill);
562   GNUNET_HELPER_destroy (h);
563 }
564
565
566 /**
567  * Write to the helper-process
568  *
569  * @param cls handle to the helper process
570  * @param tc scheduler context
571  */
572 static void
573 helper_write (void *cls,
574              const struct GNUNET_SCHEDULER_TaskContext *tc)
575 {
576   struct GNUNET_HELPER_Handle *h = cls;
577   struct GNUNET_HELPER_SendHandle *sh;
578   const char *buf;
579   ssize_t t;
580
581   h->write_task = GNUNET_SCHEDULER_NO_TASK;
582   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
583   {
584     /* try again */
585     h->write_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
586                                                     h->fh_to_helper, &helper_write, h);
587     return;
588   }  
589   if (NULL == (sh = h->sh_head))
590     return; /* how did this happen? */
591   buf = (const char*) sh->msg;
592   t = GNUNET_DISK_file_write (h->fh_to_helper, &buf[sh->wpos], ntohs (sh->msg->size) - sh->wpos);
593   if (t <= 0)
594   {
595     /* On write-error, restart the helper */
596     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
597                 _("Error writing to `%s': %s\n"),
598                 h->binary_name,
599                 STRERROR (errno));
600     if (NULL != h->exp_cb)
601     {
602       h->exp_cb (h->cb_cls);
603       GNUNET_HELPER_stop (h, GNUNET_NO);
604       return;
605     }
606     stop_helper (h, GNUNET_NO);
607     /* Restart the helper */
608     h->restart_task =
609       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
610                                     &restart_task, h);
611     return;
612   }
613   sh->wpos += t;
614   if (sh->wpos == ntohs (sh->msg->size))
615   {
616     GNUNET_CONTAINER_DLL_remove (h->sh_head,
617                                  h->sh_tail,
618                                  sh);
619     if (NULL != sh->cont)
620       sh->cont (sh->cont_cls, GNUNET_YES);
621     GNUNET_free (sh);
622   }
623   if (NULL != h->sh_head)
624     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
625                                                      h->fh_to_helper, 
626                                                      &helper_write, 
627                                                      h);
628 }
629
630
631 /**
632  * Send an message to the helper.
633  *
634  * @param h helper to send message to
635  * @param msg message to send
636  * @param can_drop can the message be dropped if there is already one in the queue?
637  * @param cont continuation to run once the message is out (#GNUNET_OK on succees, #GNUNET_NO
638  *             if the helper process died, #GNUNET_SYSERR during #GNUNET_HELPER_destroy).
639  * @param cont_cls closure for @a cont
640  * @return NULL if the message was dropped, 
641  *         otherwise handle to cancel *cont* (actual transmission may
642  *         not be abortable)
643  */
644 struct GNUNET_HELPER_SendHandle *
645 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h, 
646                     const struct GNUNET_MessageHeader *msg,
647                     int can_drop,
648                     GNUNET_HELPER_Continuation cont,
649                     void *cont_cls)
650 {
651   struct GNUNET_HELPER_SendHandle *sh;
652   uint16_t mlen;
653
654   if (NULL == h->fh_to_helper)
655     return NULL;
656   if ( (GNUNET_YES == can_drop) &&
657        (NULL != h->sh_head) )
658     return NULL;
659   mlen = ntohs (msg->size);
660   sh = GNUNET_malloc (sizeof (struct GNUNET_HELPER_SendHandle) + mlen);
661   sh->msg = (const struct GNUNET_MessageHeader*) &sh[1];
662   memcpy (&sh[1], msg, mlen);
663   sh->h = h;
664   sh->cont = cont;
665   sh->cont_cls = cont_cls;
666   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head,
667                                     h->sh_tail,
668                                     sh);
669   if (GNUNET_SCHEDULER_NO_TASK == h->write_task)
670     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
671                                                      h->fh_to_helper, 
672                                                      &helper_write, 
673                                                      h);
674     
675   return sh;
676 }
677
678 /**
679  * Cancel a #GNUNET_HELPER_send operation.  If possible, transmitting the
680  * message is also aborted, but at least 'cont' won't be
681  * called.
682  *
683  * @param sh operation to cancel
684  */
685 void
686 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
687 {
688   struct GNUNET_HELPER_Handle *h = sh->h;
689
690   sh->cont = NULL;
691   sh->cont_cls = NULL;
692   if (0 == sh->wpos)
693   {
694     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
695     GNUNET_free (sh);
696     if (NULL == h->sh_head)
697     {
698       GNUNET_SCHEDULER_cancel (h->write_task);
699       h->write_task = GNUNET_SCHEDULER_NO_TASK;
700     }
701   }
702 }
703
704
705 /* end of helper.c */