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