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