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