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