9faf2ee50c1ae2cb67d126a06e6b813844baa0be
[oweals/gnunet.git] / src / util / helper.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011, 2012 Christian Grothoff
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file util/helper.c
23  * @brief API for dealing with (SUID) helper processes that communicate via GNUNET_MessageHeaders on stdin/stdout
24  * @author Philipp Toelke
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include "gnunet_util_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_SERVER_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   const char *binary_name;
135
136   /**
137    * NULL-terminated list of command-line arguments.
138    */
139   char *const *binary_argv;
140                     
141   /**
142    * Task to read from the helper.
143    */
144   GNUNET_SCHEDULER_TaskIdentifier read_task;
145
146   /**
147    * Task to read from the helper.
148    */
149   GNUNET_SCHEDULER_TaskIdentifier write_task;
150
151   /**
152    * Restart task.
153    */
154   GNUNET_SCHEDULER_TaskIdentifier 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
163
164 /**
165  * Stop the helper process, we're closing down or had an error.
166  *
167  * @param h handle to the helper process
168  */
169 static void
170 stop_helper (struct GNUNET_HELPER_Handle *h)
171 {
172   struct GNUNET_HELPER_SendHandle *sh;
173
174   if (NULL != h->helper_proc)
175   {
176     GNUNET_break (0 == GNUNET_OS_process_kill (h->helper_proc, SIGTERM));
177     GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (h->helper_proc));
178     GNUNET_OS_process_destroy (h->helper_proc);
179     h->helper_proc = NULL;
180   }
181   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
182   {
183     GNUNET_SCHEDULER_cancel (h->restart_task);
184     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
185   }
186   if (GNUNET_SCHEDULER_NO_TASK != h->read_task)
187   {
188     GNUNET_SCHEDULER_cancel (h->read_task);
189     h->read_task = GNUNET_SCHEDULER_NO_TASK;
190   }
191   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
192   {
193     GNUNET_SCHEDULER_cancel (h->write_task);
194     h->write_task = GNUNET_SCHEDULER_NO_TASK;
195   }
196   if (NULL != h->helper_in)
197   {
198     GNUNET_DISK_pipe_close (h->helper_in);
199     h->helper_in = NULL;
200     h->fh_to_helper = NULL;
201   }
202   if (NULL != h->helper_out)
203   {
204     GNUNET_DISK_pipe_close (h->helper_out);
205     h->helper_out = NULL;
206     h->fh_from_helper = NULL;
207   }
208   while (NULL != (sh = h->sh_head))
209   {
210     GNUNET_CONTAINER_DLL_remove (h->sh_head,
211                                  h->sh_tail,
212                                  sh);
213     if (NULL != sh->cont)
214       sh->cont (sh->cont_cls, GNUNET_NO);
215     GNUNET_free (sh);
216   }
217   /* purge MST buffer */
218   (void) GNUNET_SERVER_mst_receive (h->mst, NULL, NULL, 0, GNUNET_YES, GNUNET_NO);
219 }
220
221
222 /**
223  * Restart the helper process.
224  *
225  * @param cls handle to the helper process
226  * @param tc scheduler context
227  */
228 static void
229 restart_task (void *cls,
230               const struct GNUNET_SCHEDULER_TaskContext *tc);
231
232
233 /**
234  * Read from the helper-process
235  *
236  * @param cls handle to the helper process
237  * @param tc scheduler context
238  */
239 static void
240 helper_read (void *cls,
241              const struct GNUNET_SCHEDULER_TaskContext *tc)
242 {
243   struct GNUNET_HELPER_Handle *h = cls;
244   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
245   ssize_t t;
246
247   h->read_task = GNUNET_SCHEDULER_NO_TASK;
248   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
249   {
250     /* try again */
251     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
252                                                    h->fh_from_helper, &helper_read, h);
253     return;
254   }
255   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof (buf));
256   if (t < 0)
257   {
258     /* On read-error, restart the helper */
259     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
260                 _("Error reading from `%s': %s\n"),
261                 h->binary_name,
262                 STRERROR (errno));
263     if (NULL != h->exp_cb)
264     {
265       h->exp_cb (h->cb_cls);
266       GNUNET_HELPER_stop (h);
267       return;
268     }
269     stop_helper (h);
270     /* Restart the helper */
271     h->restart_task =
272         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &restart_task, h);
273     return;
274   }
275   if (0 == t)
276   {
277     /* this happens if the helper is shut down via a 
278        signal, so it is not a "hard" error */
279     GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
280                 _("Got 0 bytes from helper `%s' (EOF)\n"),
281                 h->binary_name);
282     if (NULL != h->exp_cb)
283     {
284       h->exp_cb (h->cb_cls);
285       GNUNET_HELPER_stop (h);
286       return;
287     }
288     stop_helper (h);
289     /* Restart the helper */
290     h->restart_task =
291       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
292                                     &restart_task, h);
293     return;
294   }
295   GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
296               _("Got %u bytes from helper `%s'\n"),
297               (unsigned int) t,
298               h->binary_name);
299   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
300                                                  h->fh_from_helper, &helper_read, h);
301   if (GNUNET_SYSERR ==
302       GNUNET_SERVER_mst_receive (h->mst, NULL, buf, t, GNUNET_NO, GNUNET_NO))
303   {
304     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
305                 _("Failed to parse inbound message from helper `%s'\n"),
306                 h->binary_name);
307     if (NULL != h->exp_cb)
308     {
309       h->exp_cb (h->cb_cls);
310       GNUNET_HELPER_stop (h);
311       return;
312     }     
313     stop_helper (h);
314     /* Restart the helper */
315     h->restart_task =
316         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
317                                       &restart_task, h);
318     return;
319   }
320 }
321
322
323 /**
324  * Start the helper process.
325  *
326  * @param h handle to the helper process
327  */
328 static void
329 start_helper (struct GNUNET_HELPER_Handle *h)
330 {
331   h->helper_in = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
332   h->helper_out = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
333   if ( (h->helper_in == NULL) || (h->helper_out == NULL))
334   {
335     /* out of file descriptors? try again later... */
336     stop_helper (h);
337     h->restart_task =
338       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
339                                     &restart_task, h);    
340     return;
341   }
342   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
343               _("Starting HELPER process `%s'\n"),
344               h->binary_name);
345   h->fh_from_helper =
346       GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
347   h->fh_to_helper =
348       GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
349   h->helper_proc =
350     GNUNET_OS_start_process_vap (h->with_control_pipe, GNUNET_OS_INHERIT_STD_ERR, 
351                                  h->helper_in, h->helper_out,
352                                  h->binary_name,
353                                  h->binary_argv);
354   if (NULL == h->helper_proc)
355   {
356     /* failed to start process? try again later... */
357     stop_helper (h);
358     h->restart_task =
359       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
360                                     &restart_task, h);    
361     return;
362   }
363   GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
364   GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
365   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
366                                                  h->fh_from_helper, 
367                                                  &helper_read, 
368                                                  h);
369 }
370
371
372 /**
373  * Restart the helper process.
374  *
375  * @param cls handle to the helper process
376  * @param tc scheduler context
377  */
378 static void
379 restart_task (void *cls,
380               const struct GNUNET_SCHEDULER_TaskContext *tc)
381 {
382   struct GNUNET_HELPER_Handle*h = cls;
383
384   h->restart_task = GNUNET_SCHEDULER_NO_TASK;
385   start_helper (h);
386 }
387
388
389 /**
390  * Starts a helper and begins reading from it. The helper process is
391  * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
392  * or when the exp_cb callback is not NULL.
393  *
394  * @param with_control_pipe does the helper support the use of a control pipe for signalling?
395  * @param binary_name name of the binary to run
396  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
397  *                    argument must not be modified by the client for
398  *                     the lifetime of the helper handle)
399  * @param cb function to call if we get messages from the helper
400  * @param exp_cb the exception callback to call. Set this to NULL if the helper
401  *          process has to be restarted automatically when it dies/crashes
402  * @param cb_cls closure for the above callback
403  * @return the new Handle, NULL on error
404  */
405 struct GNUNET_HELPER_Handle *
406 GNUNET_HELPER_start (int with_control_pipe,
407                      const char *binary_name,
408                      char *const binary_argv[],
409                      GNUNET_SERVER_MessageTokenizerCallback cb,
410                      GNUNET_HELPER_ExceptionCallback exp_cb,
411                      void *cb_cls)
412 {
413   struct GNUNET_HELPER_Handle*h;
414
415   h =  GNUNET_malloc (sizeof (struct GNUNET_HELPER_Handle));
416   h->with_control_pipe = with_control_pipe;
417   h->binary_name = binary_name;
418   h->binary_argv = binary_argv;
419   h->cb_cls = cb_cls;
420   h->mst = GNUNET_SERVER_mst_create (cb, h->cb_cls);
421   h->exp_cb = exp_cb;
422   start_helper (h);
423   return h;
424 }
425
426
427 /**
428  * @brief Kills the helper, closes the pipe and frees the h
429  *
430  * @param h h to helper to stop
431  */
432 void
433 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h)
434 {
435   struct GNUNET_HELPER_SendHandle *sh;
436
437   h->exp_cb = NULL;
438   /* signal pending writes that we were stopped */
439   while (NULL != (sh = h->sh_head))
440   {
441     GNUNET_CONTAINER_DLL_remove (h->sh_head,
442                                  h->sh_tail,
443                                  sh);
444     if (NULL != sh->cont)
445       sh->cont (sh->cont_cls, GNUNET_SYSERR);
446     GNUNET_free (sh);
447   }
448   stop_helper (h);
449   GNUNET_SERVER_mst_destroy (h->mst);
450   GNUNET_free (h);
451 }
452
453
454 /**
455  * Write to the helper-process
456  *
457  * @param cls handle to the helper process
458  * @param tc scheduler context
459  */
460 static void
461 helper_write (void *cls,
462              const struct GNUNET_SCHEDULER_TaskContext *tc)
463 {
464   struct GNUNET_HELPER_Handle *h = cls;
465   struct GNUNET_HELPER_SendHandle *sh;
466   const char *buf;
467   ssize_t t;
468
469   h->write_task = GNUNET_SCHEDULER_NO_TASK;
470   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
471   {
472     /* try again */
473     h->write_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
474                                                     h->fh_to_helper, &helper_write, h);
475     return;
476   }  
477   if (NULL == (sh = h->sh_head))
478     return; /* how did this happen? */
479   buf = (const char*) sh->msg;
480   t = GNUNET_DISK_file_write (h->fh_to_helper, &buf[sh->wpos], ntohs (sh->msg->size) - sh->wpos);
481   if (t <= 0)
482   {
483     /* On write-error, restart the helper */
484     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
485                 _("Error writing to `%s': %s\n"),
486                 h->binary_name,
487                 STRERROR (errno));
488     stop_helper (h);
489     /* Restart the helper */
490     h->restart_task =
491       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
492                                     &restart_task, h);
493     return;
494   }
495   sh->wpos += t;
496   if (sh->wpos == ntohs (sh->msg->size))
497   {
498     GNUNET_CONTAINER_DLL_remove (h->sh_head,
499                                  h->sh_tail,
500                                  sh);
501     if (NULL != sh->cont)
502       sh->cont (sh->cont_cls, GNUNET_YES);
503     GNUNET_free (sh);
504   }
505   if (NULL != h->sh_head)
506     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
507                                                      h->fh_to_helper, 
508                                                      &helper_write, 
509                                                      h);
510 }
511
512
513 /**
514  * Send an message to the helper.
515  *
516  * @param h helper to send message to
517  * @param msg message to send
518  * @param can_drop can the message be dropped if there is already one in the queue?
519  * @param cont continuation to run once the message is out (PREREQ_DONE on succees, CANCEL
520  *             if the helper process died, NULL during GNUNET_HELPER_stop).
521  * @param cont_cls closure for 'cont'
522  * @return NULL if the message was dropped, 
523  *         otherwise handle to cancel *cont* (actual transmission may
524  *         not be abortable)
525  */
526 struct GNUNET_HELPER_SendHandle *
527 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h, 
528                     const struct GNUNET_MessageHeader *msg,
529                     int can_drop,
530                     GNUNET_HELPER_Continuation cont,
531                     void *cont_cls)
532 {
533   struct GNUNET_HELPER_SendHandle *sh;
534   uint16_t mlen;
535
536   if (NULL == h->fh_to_helper)
537     return NULL;
538   if ( (GNUNET_YES == can_drop) &&
539        (NULL != h->sh_head) )
540     return NULL;
541   mlen = ntohs (msg->size);
542   sh = GNUNET_malloc (sizeof (struct GNUNET_HELPER_SendHandle) + mlen);
543   sh->msg = (const struct GNUNET_MessageHeader*) &sh[1];
544   memcpy (&sh[1], msg, mlen);
545   sh->h = h;
546   sh->cont = cont;
547   sh->cont_cls = cont_cls;
548   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head,
549                                     h->sh_tail,
550                                     sh);
551   if (GNUNET_SCHEDULER_NO_TASK == h->write_task)
552     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
553                                                      h->fh_to_helper, 
554                                                      &helper_write, 
555                                                      h);
556     
557   return sh;
558 }
559
560 /**
561  * Cancel a 'send' operation.  If possible, transmitting the
562  * message is also aborted, but at least 'cont' won't be
563  * called.
564  *
565  * @param sh operation to cancel
566  */
567 void
568 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
569 {
570   struct GNUNET_HELPER_Handle *h = sh->h;
571
572   sh->cont = NULL;
573   sh->cont_cls = NULL;
574   if (0 == sh->wpos)
575   {
576     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
577     if (NULL == h->sh_head)
578     {
579       GNUNET_SCHEDULER_cancel (h->write_task);
580       h->write_task = GNUNET_SCHEDULER_NO_TASK;
581     }
582     GNUNET_free (sh);
583   }
584 }
585
586
587 /* end of helper.c */