error handling
[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    * 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  * The handle to a helper process.
77  */
78 struct GNUNET_HELPER_Handle
79 {
80   /**
81    * PipeHandle to receive data from the helper
82    */
83   struct GNUNET_DISK_PipeHandle *helper_in;
84
85   /**
86    * PipeHandle to send data to the helper
87    */
88   struct GNUNET_DISK_PipeHandle *helper_out;
89
90   /**
91    * FileHandle to receive data from the helper
92    */
93   const struct GNUNET_DISK_FileHandle *fh_from_helper;
94
95   /**
96    * FileHandle to send data to the helper
97    */
98   const struct GNUNET_DISK_FileHandle *fh_to_helper;
99
100   /**
101    * The process id of the helper
102    */
103   struct GNUNET_OS_Process *helper_proc;
104
105   /**
106    * The Message-Tokenizer that tokenizes the messages comming from the helper
107    */
108   struct GNUNET_MessageStreamTokenizer *mst;
109
110   /**
111    * The exception callback
112    */
113   GNUNET_HELPER_ExceptionCallback exp_cb;
114
115   /**
116    * The closure for callbacks
117    */
118   void *cb_cls;
119
120   /**
121    * First message queued for transmission to helper.
122    */
123   struct GNUNET_HELPER_SendHandle *sh_head;
124
125   /**
126    * Last message queued for transmission to helper.
127    */
128   struct GNUNET_HELPER_SendHandle *sh_tail;
129
130   /**
131    * Binary to run.
132    */
133   char *binary_name;
134
135   /**
136    * NULL-terminated list of command-line arguments.
137    */
138   char **binary_argv;
139
140   /**
141    * Task to read from the helper.
142    */
143   struct GNUNET_SCHEDULER_Task *read_task;
144
145   /**
146    * Task to read from the helper.
147    */
148   struct GNUNET_SCHEDULER_Task *write_task;
149
150   /**
151    * Restart task.
152    */
153   struct GNUNET_SCHEDULER_Task *restart_task;
154
155   /**
156    * Does the helper support the use of a control pipe for signalling?
157    */
158   int with_control_pipe;
159
160   /**
161    * Count start attempts to increase linear back off
162    */
163   unsigned int retry_back_off;
164 };
165
166
167 /**
168  * Sends termination signal to the helper process.  The helper process is not
169  * reaped; call GNUNET_HELPER_wait() for reaping the dead helper process.
170  *
171  * @param h the helper handle
172  * @param soft_kill if GNUNET_YES, signals termination by closing the helper's
173  *          stdin; GNUNET_NO to signal termination by sending SIGTERM to helper
174  * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
175  */
176 int
177 GNUNET_HELPER_kill (struct GNUNET_HELPER_Handle *h, int soft_kill)
178 {
179   struct GNUNET_HELPER_SendHandle *sh;
180   int ret;
181
182   while (NULL != (sh = h->sh_head))
183   {
184     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
185     if (NULL != sh->cont)
186       sh->cont (sh->cont_cls, GNUNET_NO);
187     GNUNET_free (sh);
188   }
189   if (NULL != h->restart_task)
190   {
191     GNUNET_SCHEDULER_cancel (h->restart_task);
192     h->restart_task = NULL;
193   }
194   if (NULL != h->read_task)
195   {
196     GNUNET_SCHEDULER_cancel (h->read_task);
197     h->read_task = NULL;
198   }
199   if (NULL == h->helper_proc)
200     return GNUNET_SYSERR;
201   if (GNUNET_YES == soft_kill)
202   {
203     /* soft-kill only possible with pipes */
204     GNUNET_assert (NULL != h->helper_in);
205     ret = GNUNET_DISK_pipe_close (h->helper_in);
206     h->helper_in = NULL;
207     h->fh_to_helper = NULL;
208     return ret;
209   }
210   if (0 != GNUNET_OS_process_kill (h->helper_proc, GNUNET_TERM_SIG))
211     return GNUNET_SYSERR;
212   return GNUNET_OK;
213 }
214
215
216 /**
217  * Reap the helper process.  This call is blocking(!).  The helper process
218  * should either be sent a termination signal before or should be dead before
219  * calling this function
220  *
221  * @param h the helper handle
222  * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
223  */
224 int
225 GNUNET_HELPER_wait (struct GNUNET_HELPER_Handle *h)
226 {
227   struct GNUNET_HELPER_SendHandle *sh;
228   int ret;
229
230   ret = GNUNET_SYSERR;
231   if (NULL != h->helper_proc)
232   {
233     ret = GNUNET_OS_process_wait (h->helper_proc);
234     GNUNET_OS_process_destroy (h->helper_proc);
235     h->helper_proc = NULL;
236   }
237   if (NULL != h->read_task)
238   {
239     GNUNET_SCHEDULER_cancel (h->read_task);
240     h->read_task = NULL;
241   }
242   if (NULL != h->write_task)
243   {
244     GNUNET_SCHEDULER_cancel (h->write_task);
245     h->write_task = NULL;
246   }
247   if (NULL != h->helper_in)
248   {
249     GNUNET_DISK_pipe_close (h->helper_in);
250     h->helper_in = NULL;
251     h->fh_to_helper = NULL;
252   }
253   if (NULL != h->helper_out)
254   {
255     GNUNET_DISK_pipe_close (h->helper_out);
256     h->helper_out = NULL;
257     h->fh_from_helper = NULL;
258   }
259   while (NULL != (sh = h->sh_head))
260   {
261     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
262     if (NULL != sh->cont)
263       sh->cont (sh->cont_cls, GNUNET_NO);
264     GNUNET_free (sh);
265   }
266   /* purge MST buffer */
267   if (NULL != h->mst)
268     (void) GNUNET_MST_from_buffer (h->mst, NULL, 0, GNUNET_YES, GNUNET_NO);
269   return ret;
270 }
271
272
273 /**
274  * Stop the helper process, we're closing down or had an error.
275  *
276  * @param h handle to the helper process
277  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
278  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
279  */
280 static void
281 stop_helper (struct GNUNET_HELPER_Handle *h, int soft_kill)
282 {
283   if (NULL != h->restart_task)
284   {
285     GNUNET_SCHEDULER_cancel (h->restart_task);
286     h->restart_task = NULL;
287   }
288   else
289   {
290     GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
291     GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
292   }
293 }
294
295
296 /**
297  * Restart the helper process.
298  *
299  * @param cls handle to the helper process
300  */
301 static void
302 restart_task (void *cls);
303
304
305 /**
306  * Read from the helper-process
307  *
308  * @param cls handle to the helper process
309  */
310 static void
311 helper_read (void *cls)
312 {
313   struct GNUNET_HELPER_Handle *h = cls;
314   char buf[GNUNET_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
315   ssize_t t;
316
317   h->read_task = NULL;
318   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof(buf));
319   if (t < 0)
320   {
321     /* On read-error, restart the helper */
322     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
323                 _ ("Error reading from `%s': %s\n"),
324                 h->binary_name,
325                 strerror (errno));
326     if (NULL != h->exp_cb)
327     {
328       h->exp_cb (h->cb_cls);
329       GNUNET_HELPER_stop (h, GNUNET_NO);
330       return;
331     }
332     stop_helper (h, GNUNET_NO);
333     /* Restart the helper */
334     h->restart_task = GNUNET_SCHEDULER_add_delayed (
335       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
336                                      h->retry_back_off),
337       &restart_task,
338       h);
339     return;
340   }
341   if (0 == t)
342   {
343     /* this happens if the helper is shut down via a
344        signal, so it is not a "hard" error */
345     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
346                 "Got 0 bytes from helper `%s' (EOF)\n",
347                 h->binary_name);
348     if (NULL != h->exp_cb)
349     {
350       h->exp_cb (h->cb_cls);
351       GNUNET_HELPER_stop (h, GNUNET_NO);
352       return;
353     }
354     stop_helper (h, GNUNET_NO);
355     /* Restart the helper */
356     h->restart_task = GNUNET_SCHEDULER_add_delayed (
357       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
358                                      h->retry_back_off),
359       &restart_task,
360       h);
361     return;
362   }
363   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
364               "Got %u bytes from helper `%s'\n",
365               (unsigned int) t,
366               h->binary_name);
367   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
368                                                  h->fh_from_helper,
369                                                  &helper_read,
370                                                  h);
371   if (GNUNET_SYSERR ==
372       GNUNET_MST_from_buffer (h->mst, 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 = GNUNET_SCHEDULER_add_delayed (
386       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
387                                      h->retry_back_off),
388       &restart_task,
389       h);
390     return;
391   }
392 }
393
394
395 /**
396  * Start the helper process.
397  *
398  * @param h handle to the helper process
399  */
400 static void
401 start_helper (struct GNUNET_HELPER_Handle *h)
402 {
403   h->helper_in =
404     GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
405   h->helper_out =
406     GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
407   if ((h->helper_in == NULL) || (h->helper_out == NULL))
408   {
409     /* out of file descriptors? try again later... */
410     stop_helper (h, GNUNET_NO);
411     h->restart_task = GNUNET_SCHEDULER_add_delayed (
412       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
413                                      h->retry_back_off),
414       &restart_task,
415       h);
416     return;
417   }
418   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
419               "Starting HELPER process `%s'\n",
420               h->binary_name);
421   h->fh_from_helper =
422     GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
423   h->fh_to_helper =
424     GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
425   h->helper_proc = GNUNET_OS_start_process_vap (h->with_control_pipe,
426                                                 GNUNET_OS_INHERIT_STD_ERR,
427                                                 h->helper_in,
428                                                 h->helper_out,
429                                                 NULL,
430                                                 h->binary_name,
431                                                 h->binary_argv);
432   if (NULL == h->helper_proc)
433   {
434     /* failed to start process? try again later... */
435     stop_helper (h, GNUNET_NO);
436     h->restart_task = GNUNET_SCHEDULER_add_delayed (
437       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
438                                      h->retry_back_off),
439       &restart_task,
440       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     ;
508   h->binary_argv = GNUNET_malloc (sizeof(char *) * (c + 1));
509   for (c = 0; NULL != binary_argv[c]; c++)
510     h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
511   h->binary_argv[c] = NULL;
512   h->cb_cls = cb_cls;
513   if (NULL != cb)
514     h->mst = GNUNET_MST_create (cb, 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, h->sh_tail, sh);
543     if (NULL != sh->cont)
544       sh->cont (sh->cont_cls, GNUNET_SYSERR);
545     GNUNET_free (sh);
546   }
547   if (NULL != h->mst)
548     GNUNET_MST_destroy (h->mst);
549   GNUNET_free (h->binary_name);
550   for (c = 0; h->binary_argv[c] != NULL; c++)
551     GNUNET_free (h->binary_argv[c]);
552   GNUNET_free (h->binary_argv);
553   GNUNET_free (h);
554 }
555
556
557 /**
558  * Kills the helper, closes the pipe and frees the handle
559  *
560  * @param h handle to helper to stop
561  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
562  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
563  */
564 void
565 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h, int soft_kill)
566 {
567   h->exp_cb = NULL;
568   stop_helper (h, soft_kill);
569   GNUNET_HELPER_destroy (h);
570 }
571
572
573 /**
574  * Write to the helper-process
575  *
576  * @param cls handle to the helper process
577  */
578 static void
579 helper_write (void *cls)
580 {
581   struct GNUNET_HELPER_Handle *h = cls;
582   struct GNUNET_HELPER_SendHandle *sh;
583   const char *buf;
584   ssize_t t;
585
586   h->write_task = NULL;
587   if (NULL == (sh = h->sh_head))
588   {
589     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Helper write had no work!\n");
590     return;   /* how did this happen? */
591   }
592   buf = (const char *) sh->msg;
593   t = GNUNET_DISK_file_write (h->fh_to_helper,
594                               &buf[sh->wpos],
595                               ntohs (sh->msg->size) - sh->wpos);
596   if (-1 == t)
597   {
598     /* On write-error, restart the helper */
599     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
600                 _ ("Error writing to `%s': %s\n"),
601                 h->binary_name,
602                 strerror (errno));
603     if (NULL != h->exp_cb)
604     {
605       h->exp_cb (h->cb_cls);
606       GNUNET_HELPER_stop (h, GNUNET_NO);
607       return;
608     }
609     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
610                 "Stopping and restarting helper task!\n");
611     stop_helper (h, GNUNET_NO);
612     /* Restart the helper */
613     h->restart_task = GNUNET_SCHEDULER_add_delayed (
614       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
615                                      h->retry_back_off),
616       &restart_task,
617       h);
618     return;
619   }
620   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
621               "Transmitted %u bytes to %s\n",
622               (unsigned int) t,
623               h->binary_name);
624   sh->wpos += t;
625   if (sh->wpos == ntohs (sh->msg->size))
626   {
627     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
628     if (NULL != sh->cont)
629       sh->cont (sh->cont_cls, GNUNET_YES);
630     GNUNET_free (sh);
631   }
632   if (NULL != h->sh_head)
633     h->write_task =
634       GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
635                                        h->fh_to_helper,
636                                        &helper_write,
637                                        h);
638 }
639
640
641 /**
642  * Send an message to the helper.
643  *
644  * @param h helper to send message to
645  * @param msg message to send
646  * @param can_drop can the message be dropped if there is already one in the queue?
647  * @param cont continuation to run once the message is out (#GNUNET_OK on succees, #GNUNET_NO
648  *             if the helper process died, #GNUNET_SYSERR during #GNUNET_HELPER_destroy).
649  * @param cont_cls closure for @a cont
650  * @return NULL if the message was dropped,
651  *         otherwise handle to cancel *cont* (actual transmission may
652  *         not be abortable)
653  */
654 struct GNUNET_HELPER_SendHandle *
655 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h,
656                     const struct GNUNET_MessageHeader *msg,
657                     int can_drop,
658                     GNUNET_HELPER_Continuation cont,
659                     void *cont_cls)
660 {
661   struct GNUNET_HELPER_SendHandle *sh;
662   uint16_t mlen;
663
664   if (NULL == h->fh_to_helper)
665     return NULL;
666   if ((GNUNET_YES == can_drop) && (NULL != h->sh_head))
667     return NULL;
668   mlen = ntohs (msg->size);
669   sh = GNUNET_malloc (sizeof(struct GNUNET_HELPER_SendHandle) + mlen);
670   sh->msg = (const struct GNUNET_MessageHeader *) &sh[1];
671   GNUNET_memcpy (&sh[1], msg, mlen);
672   sh->h = h;
673   sh->cont = cont;
674   sh->cont_cls = cont_cls;
675   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head, h->sh_tail, sh);
676   if (NULL == h->write_task)
677     h->write_task =
678       GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
679                                        h->fh_to_helper,
680                                        &helper_write,
681                                        h);
682
683   return sh;
684 }
685
686
687 /**
688  * Cancel a #GNUNET_HELPER_send operation.  If possible, transmitting the
689  * message is also aborted, but at least 'cont' won't be
690  * called.
691  *
692  * @param sh operation to cancel
693  */
694 void
695 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
696 {
697   struct GNUNET_HELPER_Handle *h = sh->h;
698
699   sh->cont = NULL;
700   sh->cont_cls = NULL;
701   if (0 == sh->wpos)
702   {
703     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
704     GNUNET_free (sh);
705     if (NULL == h->sh_head)
706     {
707       GNUNET_SCHEDULER_cancel (h->write_task);
708       h->write_task = NULL;
709     }
710   }
711 }
712
713
714 /* end of helper.c */