glitch in the license text detected by hyazinthe, thank you!
[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
16 /**
17  * @file util/helper.c
18  * @brief API for dealing with (SUID) helper processes that communicate via
19  *          GNUNET_MessageHeaders on stdin/stdout
20  * @author Philipp Toelke
21  * @author Christian Grothoff
22  */
23 #include "platform.h"
24 #include "gnunet_util_lib.h"
25 #include "gnunet_mst_lib.h"
26
27
28 /**
29  * Entry in the queue of messages we need to transmit to the helper.
30  */
31 struct GNUNET_HELPER_SendHandle
32 {
33
34   /**
35    * This is an entry in a DLL.
36    */
37   struct GNUNET_HELPER_SendHandle *next;
38
39   /**
40    * This is an entry in a DLL.
41    */
42   struct GNUNET_HELPER_SendHandle *prev;
43
44   /**
45    * Message to transmit (allocated at the end of this struct)
46    */
47   const struct GNUNET_MessageHeader *msg;
48
49   /**
50    * The handle to a helper process.
51    */
52   struct GNUNET_HELPER_Handle *h;
53
54   /**
55    * Function to call upon completion.
56    */
57   GNUNET_HELPER_Continuation cont;
58
59   /**
60    * Closure to 'cont'.
61    */
62   void *cont_cls;
63
64   /**
65    * Current write position.
66    */
67   unsigned int wpos;
68
69 };
70
71
72 /**
73  * The handle to a helper process.
74  */
75 struct GNUNET_HELPER_Handle
76 {
77
78   /**
79    * PipeHandle to receive data from the helper
80    */
81   struct GNUNET_DISK_PipeHandle *helper_in;
82
83   /**
84    * PipeHandle to send data to the helper
85    */
86   struct GNUNET_DISK_PipeHandle *helper_out;
87
88   /**
89    * FileHandle to receive data from the helper
90    */
91   const struct GNUNET_DISK_FileHandle *fh_from_helper;
92
93   /**
94    * FileHandle to send data to the helper
95    */
96   const struct GNUNET_DISK_FileHandle *fh_to_helper;
97
98   /**
99    * The process id of the helper
100    */
101   struct GNUNET_OS_Process *helper_proc;
102
103   /**
104    * The Message-Tokenizer that tokenizes the messages comming from the helper
105    */
106   struct GNUNET_MessageStreamTokenizer *mst;
107
108   /**
109    * The exception callback
110    */
111   GNUNET_HELPER_ExceptionCallback exp_cb;
112
113   /**
114    * The closure for callbacks
115    */
116   void *cb_cls;
117
118   /**
119    * First message queued for transmission to helper.
120    */
121   struct GNUNET_HELPER_SendHandle *sh_head;
122
123   /**
124    * Last message queued for transmission to helper.
125    */
126   struct GNUNET_HELPER_SendHandle *sh_tail;
127
128   /**
129    * Binary to run.
130    */
131   char *binary_name;
132
133   /**
134    * NULL-terminated list of command-line arguments.
135    */
136   char **binary_argv;
137
138   /**
139    * Task to read from the helper.
140    */
141   struct GNUNET_SCHEDULER_Task *read_task;
142
143   /**
144    * Task to read from the helper.
145    */
146   struct GNUNET_SCHEDULER_Task *write_task;
147
148   /**
149    * Restart task.
150    */
151   struct GNUNET_SCHEDULER_Task *restart_task;
152
153   /**
154    * Does the helper support the use of a control pipe for signalling?
155    */
156   int with_control_pipe;
157
158   /**
159    * Count start attempts to increase linear back off
160    */
161   unsigned int retry_back_off;
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 (NULL != h->restart_task)
191   {
192     GNUNET_SCHEDULER_cancel (h->restart_task);
193     h->restart_task = NULL;
194   }
195   if (NULL != h->read_task)
196   {
197     GNUNET_SCHEDULER_cancel (h->read_task);
198     h->read_task = NULL;
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, GNUNET_TERM_SIG))
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 (NULL != h->read_task)
239   {
240     GNUNET_SCHEDULER_cancel (h->read_task);
241     h->read_task = NULL;
242   }
243   if (NULL != h->write_task)
244   {
245     GNUNET_SCHEDULER_cancel (h->write_task);
246     h->write_task = NULL;
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   if (NULL != h->mst)
271     (void) GNUNET_MST_from_buffer (h->mst,
272                                    NULL, 0,
273                                    GNUNET_YES,
274                                    GNUNET_NO);
275   return ret;
276 }
277
278
279 /**
280  * Stop the helper process, we're closing down or had an error.
281  *
282  * @param h handle to the helper process
283  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
284  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
285  */
286 static void
287 stop_helper (struct GNUNET_HELPER_Handle *h,
288              int soft_kill)
289 {
290   if (NULL != h->restart_task)
291   {
292     GNUNET_SCHEDULER_cancel (h->restart_task);
293     h->restart_task = NULL;
294   }
295   else
296   {
297     GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
298     GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
299   }
300 }
301
302
303 /**
304  * Restart the helper process.
305  *
306  * @param cls handle to the helper process
307  */
308 static void
309 restart_task (void *cls);
310
311
312 /**
313  * Read from the helper-process
314  *
315  * @param cls handle to the helper process
316  */
317 static void
318 helper_read (void *cls)
319 {
320   struct GNUNET_HELPER_Handle *h = cls;
321   char buf[GNUNET_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
322   ssize_t t;
323
324   h->read_task = NULL;
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 = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
342                                                                                    h->retry_back_off),
343                                                     &restart_task, h);
344     return;
345   }
346   if (0 == t)
347   {
348     /* this happens if the helper is shut down via a
349        signal, so it is not a "hard" error */
350     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
351                 "Got 0 bytes from helper `%s' (EOF)\n",
352                 h->binary_name);
353     if (NULL != h->exp_cb)
354     {
355       h->exp_cb (h->cb_cls);
356       GNUNET_HELPER_stop (h, GNUNET_NO);
357       return;
358     }
359     stop_helper (h, GNUNET_NO);
360     /* Restart the helper */
361     h->restart_task
362       = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
363                                                                    h->retry_back_off),
364                                      &restart_task, h);
365     return;
366   }
367   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
368               "Got %u bytes from helper `%s'\n",
369               (unsigned int) t,
370               h->binary_name);
371   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
372                                                  h->fh_from_helper,
373                                                  &helper_read, h);
374   if (GNUNET_SYSERR ==
375       GNUNET_MST_from_buffer (h->mst,
376                               buf, t,
377                               GNUNET_NO,
378                               GNUNET_NO))
379   {
380     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
381                 _("Failed to parse inbound message from helper `%s'\n"),
382                 h->binary_name);
383     if (NULL != h->exp_cb)
384     {
385       h->exp_cb (h->cb_cls);
386       GNUNET_HELPER_stop (h, GNUNET_NO);
387       return;
388     }
389     stop_helper (h, GNUNET_NO);
390     /* Restart the helper */
391     h->restart_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
392                                                                                   h->retry_back_off),
393                                                     &restart_task, h);
394     return;
395   }
396 }
397
398
399 /**
400  * Start the helper process.
401  *
402  * @param h handle to the helper process
403  */
404 static void
405 start_helper (struct GNUNET_HELPER_Handle *h)
406 {
407   h->helper_in = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
408   h->helper_out = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
409   if ( (h->helper_in == NULL) || (h->helper_out == NULL))
410   {
411     /* out of file descriptors? try again later... */
412     stop_helper (h, GNUNET_NO);
413     h->restart_task =
414       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
415                                                                   h->retry_back_off),
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, NULL,
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 = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
436                                                                                   h->retry_back_off),
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   if (NULL != h->mst)
443     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
444                                                    h->fh_from_helper,
445                                                    &helper_read,
446                                                    h);
447 }
448
449
450 /**
451  * Restart the helper process.
452  *
453  * @param cls handle to the helper process
454  */
455 static void
456 restart_task (void *cls)
457 {
458   struct GNUNET_HELPER_Handle*h = cls;
459
460   h->restart_task = NULL;
461   h->retry_back_off++;
462   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
463               "Restarting helper with back-off %u\n",
464               h->retry_back_off);
465   start_helper (h);
466 }
467
468
469 /**
470  * Starts a helper and begins reading from it. The helper process is
471  * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
472  * or when the exp_cb callback is not NULL.
473  *
474  * @param with_control_pipe does the helper support the use of a control pipe for signalling?
475  * @param binary_name name of the binary to run
476  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
477  *                    argument must not be modified by the client for
478  *                     the lifetime of the helper handle)
479  * @param cb function to call if we get messages from the helper
480  * @param exp_cb the exception callback to call. Set this to NULL if the helper
481  *          process has to be restarted automatically when it dies/crashes
482  * @param cb_cls closure for the above callback
483  * @return the new Handle, NULL on error
484  */
485 struct GNUNET_HELPER_Handle *
486 GNUNET_HELPER_start (int with_control_pipe,
487                      const char *binary_name,
488                      char *const binary_argv[],
489                      GNUNET_MessageTokenizerCallback cb,
490                      GNUNET_HELPER_ExceptionCallback exp_cb,
491                      void *cb_cls)
492 {
493   struct GNUNET_HELPER_Handle *h;
494   unsigned int c;
495
496   h = GNUNET_new (struct GNUNET_HELPER_Handle);
497   h->with_control_pipe = with_control_pipe;
498   /* Lookup in libexec path only if we are starting gnunet helpers */
499   if (NULL != strstr (binary_name, "gnunet"))
500     h->binary_name = GNUNET_OS_get_libexec_binary_path (binary_name);
501   else
502     h->binary_name = GNUNET_strdup (binary_name);
503   for (c = 0; NULL != binary_argv[c]; c++);
504   h->binary_argv = GNUNET_malloc (sizeof (char *) * (c + 1));
505   for (c = 0; NULL != binary_argv[c]; c++)
506     h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
507   h->binary_argv[c] = NULL;
508   h->cb_cls = cb_cls;
509   if (NULL != cb)
510     h->mst = GNUNET_MST_create (cb,
511                                 h->cb_cls);
512   h->exp_cb = exp_cb;
513   h->retry_back_off = 0;
514   start_helper (h);
515   return h;
516 }
517
518
519 /**
520  * Free's the resources occupied by the helper handle
521  *
522  * @param h the helper handle to free
523  */
524 void
525 GNUNET_HELPER_destroy (struct GNUNET_HELPER_Handle *h)
526 {
527   unsigned int c;
528   struct GNUNET_HELPER_SendHandle *sh;
529
530   if (NULL != h->write_task)
531   {
532     GNUNET_SCHEDULER_cancel (h->write_task);
533     h->write_task = NULL;
534   }
535   GNUNET_assert (NULL == h->read_task);
536   GNUNET_assert (NULL == h->restart_task);
537   while (NULL != (sh = h->sh_head))
538   {
539     GNUNET_CONTAINER_DLL_remove (h->sh_head,
540                                  h->sh_tail,
541                                  sh);
542     if (NULL != sh->cont)
543       sh->cont (sh->cont_cls, GNUNET_SYSERR);
544     GNUNET_free (sh);
545   }
546   if (NULL != h->mst)
547     GNUNET_MST_destroy (h->mst);
548   GNUNET_free (h->binary_name);
549   for (c = 0; h->binary_argv[c] != NULL; c++)
550     GNUNET_free (h->binary_argv[c]);
551   GNUNET_free (h->binary_argv);
552   GNUNET_free (h);
553 }
554
555
556 /**
557  * Kills the helper, closes the pipe and frees the handle
558  *
559  * @param h handle to helper to stop
560  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
561  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
562  */
563 void
564 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h,
565                     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,
590                 "Helper write had no work!\n");
591     return; /* how did this happen? */
592   }
593   buf = (const char*) sh->msg;
594   t = GNUNET_DISK_file_write (h->fh_to_helper,
595                               &buf[sh->wpos],
596                               ntohs (sh->msg->size) - sh->wpos);
597   if (-1 == t)
598   {
599     /* On write-error, restart the helper */
600     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
601                 _("Error writing to `%s': %s\n"),
602                 h->binary_name,
603                 STRERROR (errno));
604     if (NULL != h->exp_cb)
605     {
606       h->exp_cb (h->cb_cls);
607       GNUNET_HELPER_stop (h, GNUNET_NO);
608       return;
609     }
610     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
611                 "Stopping and restarting helper task!\n");
612     stop_helper (h, GNUNET_NO);
613     /* Restart the helper */
614     h->restart_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS,
615                                                                                   h->retry_back_off),
616                                                     &restart_task, h);
617     return;
618   }
619   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
620               "Transmitted %u bytes to %s\n",
621               (unsigned int) t,
622               h->binary_name);
623   sh->wpos += t;
624   if (sh->wpos == ntohs (sh->msg->size))
625   {
626     GNUNET_CONTAINER_DLL_remove (h->sh_head,
627                                  h->sh_tail,
628                                  sh);
629     if (NULL != sh->cont)
630       sh->cont (sh->cont_cls, GNUNET_YES);
631     GNUNET_free (sh);
632   }
633   if (NULL != h->sh_head)
634     h->write_task = 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) &&
667        (NULL != h->sh_head) )
668     return NULL;
669   mlen = ntohs (msg->size);
670   sh = GNUNET_malloc (sizeof (struct GNUNET_HELPER_SendHandle) + mlen);
671   sh->msg = (const struct GNUNET_MessageHeader*) &sh[1];
672   GNUNET_memcpy (&sh[1], msg, mlen);
673   sh->h = h;
674   sh->cont = cont;
675   sh->cont_cls = cont_cls;
676   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head,
677                                     h->sh_tail,
678                                     sh);
679   if (NULL == h->write_task)
680     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
681                                                      h->fh_to_helper,
682                                                      &helper_write,
683                                                      h);
684
685   return sh;
686 }
687
688 /**
689  * Cancel a #GNUNET_HELPER_send operation.  If possible, transmitting the
690  * message is also aborted, but at least 'cont' won't be
691  * called.
692  *
693  * @param sh operation to cancel
694  */
695 void
696 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
697 {
698   struct GNUNET_HELPER_Handle *h = sh->h;
699
700   sh->cont = NULL;
701   sh->cont_cls = NULL;
702   if (0 == sh->wpos)
703   {
704     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
705     GNUNET_free (sh);
706     if (NULL == h->sh_head)
707     {
708       GNUNET_SCHEDULER_cancel (h->write_task);
709       h->write_task = NULL;
710     }
711   }
712 }
713
714
715 /* end of helper.c */