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