-do use non-blocking opening of the pipe FD, and try again if it fails
[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 HelperMessageQueueEntry
35 {
36
37   /**
38    * This is an entry in a DLL.
39    */
40   struct HelperMessageQueueEntry *next;
41
42   /**
43    * This is an entry in a DLL.
44    */
45   struct HelperMessageQueueEntry *prev;
46
47   /**
48    * Message to transmit (allocated at the end of this struct)
49    */
50   const struct GNUNET_MessageHeader *msg;
51   
52   /**
53    * Function to call upon completion.
54    */
55   GNUNET_HELPER_Continuation cont;
56
57   /**
58    * Closure to 'cont'.
59    */
60   void *cont_cls;
61
62   /**
63    * Current write position.
64    */
65   unsigned int wpos;
66
67 };
68
69
70 /**
71  * The handle to a helper process.
72  */
73 struct GNUNET_HELPER_Handle
74 {
75
76   /**
77    * PipeHandle to receive data from the helper
78    */
79   struct GNUNET_DISK_PipeHandle *helper_in;
80   
81   /**
82    * PipeHandle to send data to the helper
83    */
84   struct GNUNET_DISK_PipeHandle *helper_out;
85   
86   /**
87    * FileHandle to receive data from the helper
88    */
89   const struct GNUNET_DISK_FileHandle *fh_from_helper;
90   
91   /**
92    * FileHandle to send data to the helper
93    */
94   const struct GNUNET_DISK_FileHandle *fh_to_helper;
95   
96   /**
97    * The process id of the helper
98    */
99   struct GNUNET_OS_Process *helper_proc;
100
101   /**
102    * The Message-Tokenizer that tokenizes the messages comming from the helper
103    */
104   struct GNUNET_SERVER_MessageStreamTokenizer *mst;
105
106   /**
107    * First message queued for transmission to helper.
108    */
109   struct HelperMessageQueueEntry *mq_head;
110
111   /**
112    * Last message queued for transmission to helper.
113    */
114   struct HelperMessageQueueEntry *mq_tail;
115
116   /**
117    * Binary to run.
118    */
119   const char *binary_name;
120
121   /**
122    * NULL-terminated list of command-line arguments.
123    */
124   char *const *binary_argv;
125                     
126   /**
127    * Task to read from the helper.
128    */
129   GNUNET_SCHEDULER_TaskIdentifier read_task;
130
131   /**
132    * Task to read from the helper.
133    */
134   GNUNET_SCHEDULER_TaskIdentifier write_task;
135
136   /**
137    * Restart task.
138    */
139   GNUNET_SCHEDULER_TaskIdentifier restart_task;
140 };
141
142
143 /**
144  * Stop the helper process, we're closing down or had an error.
145  *
146  * @param h handle to the helper process
147  */
148 static void
149 stop_helper (struct GNUNET_HELPER_Handle *h)
150 {
151   struct HelperMessageQueueEntry *qe;
152
153   if (NULL != h->helper_proc)
154   {
155     GNUNET_break (0 == GNUNET_OS_process_kill (h->helper_proc, SIGTERM));
156     GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (h->helper_proc));
157     GNUNET_OS_process_close (h->helper_proc);
158     h->helper_proc = NULL;
159   }
160   if (GNUNET_SCHEDULER_NO_TASK != h->restart_task)
161   {
162     GNUNET_SCHEDULER_cancel (h->restart_task);
163     h->restart_task = GNUNET_SCHEDULER_NO_TASK;
164   }
165   if (GNUNET_SCHEDULER_NO_TASK != h->read_task)
166   {
167     GNUNET_SCHEDULER_cancel (h->read_task);
168     h->read_task = GNUNET_SCHEDULER_NO_TASK;
169   }
170   if (GNUNET_SCHEDULER_NO_TASK != h->write_task)
171   {
172     GNUNET_SCHEDULER_cancel (h->write_task);
173     h->write_task = GNUNET_SCHEDULER_NO_TASK;
174   }
175   if (NULL != h->helper_in)
176   {
177     GNUNET_DISK_pipe_close (h->helper_in);
178     h->helper_in = NULL;
179     h->fh_to_helper = NULL;
180   }
181   if (NULL != h->helper_out)
182   {
183     GNUNET_DISK_pipe_close (h->helper_out);
184     h->helper_out = NULL;
185     h->fh_from_helper = NULL;
186   }
187   while (NULL != (qe = h->mq_head))
188   {
189     GNUNET_CONTAINER_DLL_remove (h->mq_head,
190                                  h->mq_tail,
191                                  qe);
192     if (NULL != qe->cont)
193       qe->cont (qe->cont_cls, GNUNET_NO);
194     GNUNET_free (qe);
195   }
196   /* purge MST buffer */
197   (void) GNUNET_SERVER_mst_receive (h->mst, NULL, NULL, 0, GNUNET_YES, GNUNET_NO);
198 }
199
200
201 /**
202  * Restart the helper process.
203  *
204  * @param cls handle to the helper process
205  * @param tc scheduler context
206  */
207 static void
208 restart_task (void *cls,
209               const struct GNUNET_SCHEDULER_TaskContext *tc);
210
211
212 /**
213  * Read from the helper-process
214  *
215  * @param cls handle to the helper process
216  * @param tc scheduler context
217  */
218 static void
219 helper_read (void *cls,
220              const struct GNUNET_SCHEDULER_TaskContext *tc)
221 {
222   struct GNUNET_HELPER_Handle *h = cls;
223   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE];
224   ssize_t t;
225
226   h->read_task = GNUNET_SCHEDULER_NO_TASK;
227   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
228   {
229     /* try again */
230     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
231                                                    h->fh_from_helper, &helper_read, h);
232     return;
233   }
234   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof (buf));
235   if (t < 0)
236   {
237     /* On read-error, restart the helper */
238     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
239                 _("Error reading from `%s': %s\n"),
240                 h->binary_name,
241                 STRERROR (errno));
242     stop_helper (h);
243     /* Restart the helper */
244     h->restart_task =
245       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
246                                     &restart_task, h);
247     return;
248   }
249   if (0 == t)
250   {
251     /* this happens if the helper is shut down via a 
252        signal, so it is not a "hard" error */
253     GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
254                 _("Got 0 bytes from helper `%s' (EOF)\n"),
255                 h->binary_name);
256     stop_helper (h);
257     /* Restart the helper */
258     h->restart_task =
259       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
260                                     &restart_task, h);
261     return;
262   }
263   GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
264               _("Got %u bytes from helper `%s'\n"),
265               (unsigned int) t,
266               h->binary_name);
267   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
268                                                  h->fh_from_helper, &helper_read, h);
269   if (GNUNET_SYSERR ==
270       GNUNET_SERVER_mst_receive (h->mst, NULL, buf, t, GNUNET_NO, GNUNET_NO))
271   {
272     GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 
273                 _("Failed to parse inbound message from helper `%s'\n"),
274                 h->binary_name);
275     stop_helper (h);
276     /* Restart the helper */
277     h->restart_task =
278         GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
279                                       &restart_task, h);
280     return;
281   }
282 }
283
284
285 /**
286  * Start the helper process.
287  *
288  * @param h handle to the helper process
289  */
290 static void
291 start_helper (struct GNUNET_HELPER_Handle *h)
292 {
293   h->helper_in = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
294   h->helper_out = GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
295   if ( (h->helper_in == NULL) || (h->helper_out == NULL))
296   {
297     /* out of file descriptors? try again later... */
298     stop_helper (h);
299     h->restart_task =
300       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
301                                     &restart_task, h);    
302     return;
303   }
304   h->fh_from_helper =
305       GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
306   h->fh_to_helper =
307       GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
308   h->helper_proc =
309       GNUNET_OS_start_process_vap (GNUNET_NO,
310                                    h->helper_in, h->helper_out,
311                                    h->binary_name,
312                                    h->binary_argv);
313   if (NULL == h->helper_proc)
314   {
315     /* failed to start process? try again later... */
316     stop_helper (h);
317     h->restart_task =
318       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
319                                     &restart_task, h);    
320     return;
321   }
322   GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
323   GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
324   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
325                                                  h->fh_from_helper, 
326                                                  &helper_read, 
327                                                  h);
328 }
329
330
331 /**
332  * Restart the helper process.
333  *
334  * @param cls handle to the helper process
335  * @param tc scheduler context
336  */
337 static void
338 restart_task (void *cls,
339               const struct GNUNET_SCHEDULER_TaskContext *tc)
340 {
341   struct GNUNET_HELPER_Handle*h = cls;
342
343   h->restart_task = GNUNET_SCHEDULER_NO_TASK;
344   start_helper (h);
345 }
346
347
348 /**
349  * @brief Starts a helper and begins reading from it
350  *
351  * @param binary_name name of the binary to run
352  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
353  *                    argument must not be modified by the client for
354  *                     the lifetime of the helper h)
355  * @param cb function to call if we get messages from the helper
356  * @param cb_cls Closure for the callback
357  * @return the new H, NULL on error
358  */
359 struct GNUNET_HELPER_Handle*
360 GNUNET_HELPER_start (const char *binary_name,
361                      char *const binary_argv[],
362                      GNUNET_SERVER_MessageTokenizerCallback cb, void *cb_cls)
363 {
364   struct GNUNET_HELPER_Handle*h;
365
366   h =  GNUNET_malloc (sizeof (struct GNUNET_HELPER_Handle));
367   h->binary_name = binary_name;
368   h->binary_argv = binary_argv;
369   h->mst = GNUNET_SERVER_mst_create (cb, cb_cls);
370   start_helper (h);
371   return h;
372 }
373
374
375 /**
376  * @brief Kills the helper, closes the pipe and frees the h
377  *
378  * @param h h to helper to stop
379  */
380 void
381 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h)
382 {
383   struct HelperMessageQueueEntry *qe;
384
385   /* signal pending writes that we were stopped */
386   while (NULL != (qe = h->mq_head))
387   {
388     GNUNET_CONTAINER_DLL_remove (h->mq_head,
389                                  h->mq_tail,
390                                  qe);
391     if (NULL != qe->cont)
392       qe->cont (qe->cont_cls, GNUNET_SYSERR);
393     GNUNET_free (qe);
394   }
395   stop_helper (h);
396   GNUNET_SERVER_mst_destroy (h->mst);
397   GNUNET_free (h);
398 }
399
400
401 /**
402  * Write to the helper-process
403  *
404  * @param cls handle to the helper process
405  * @param tc scheduler context
406  */
407 static void
408 helper_write (void *cls,
409              const struct GNUNET_SCHEDULER_TaskContext *tc)
410 {
411   struct GNUNET_HELPER_Handle *h = cls;
412   struct HelperMessageQueueEntry *qe;
413   const char *buf;
414   ssize_t t;
415
416   h->write_task = GNUNET_SCHEDULER_NO_TASK;
417   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
418   {
419     /* try again */
420     h->write_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
421                                                     h->fh_to_helper, &helper_write, h);
422     return;
423   }  
424   if (NULL == (qe = h->mq_head))
425     return; /* how did this happen? */
426   buf = (const char*) qe->msg;
427   t = GNUNET_DISK_file_write (h->fh_to_helper, &buf[qe->wpos], ntohs (qe->msg->size) - qe->wpos);
428   if (t <= 0)
429   {
430     /* On write-error, restart the helper */
431     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
432                 _("Error writing to `%s': %s\n"),
433                 h->binary_name,
434                 STRERROR (errno));
435     stop_helper (h);
436     /* Restart the helper */
437     h->restart_task =
438       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
439                                     &restart_task, h);
440     return;
441   }
442   qe->wpos += t;
443   if (qe->wpos == ntohs (qe->msg->size))
444   {
445     GNUNET_CONTAINER_DLL_remove (h->mq_head,
446                                  h->mq_tail,
447                                  qe);
448     if (NULL != qe->cont)
449       qe->cont (qe->cont_cls, GNUNET_YES);
450     GNUNET_free (qe);
451   }
452   if (NULL != h->mq_head)
453     h->write_task = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
454                                                      h->fh_to_helper, 
455                                                      &helper_write, 
456                                                      h);
457 }
458
459
460 /**
461  * Send an message to the helper.
462  *
463  * @param h helper to send message to
464  * @param msg message to send
465  * @param can_drop can the message be dropped if there is already one in the queue?
466  * @param cont continuation to run once the message is out (PREREQ_DONE on succees, CANCEL
467  *             if the helper process died, NULL during GNUNET_HELPER_stop).
468  * @param cont_cls closure for 'cont'
469  * @return GNUNET_YES if the message will be sent
470  *         GNUNET_NO if the message was dropped
471  */
472 int
473 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h, 
474                     const struct GNUNET_MessageHeader *msg,
475                     int can_drop,
476                     GNUNET_HELPER_Continuation cont,
477                     void *cont_cls)
478 {
479   struct HelperMessageQueueEntry *qe;
480   uint16_t mlen;
481
482   if (NULL == h->fh_to_helper)
483     return GNUNET_NO;
484   if ( (GNUNET_YES == can_drop) &&
485        (h->mq_head != NULL) )
486     return GNUNET_NO;
487   mlen = ntohs (msg->size);
488   qe = GNUNET_malloc (sizeof (struct HelperMessageQueueEntry) + mlen);
489   qe->msg = (const struct GNUNET_MessageHeader*) &qe[1];
490   memcpy (&qe[1], msg, mlen);
491   qe->cont = cont;
492   qe->cont_cls = cont_cls;
493   GNUNET_CONTAINER_DLL_insert_tail (h->mq_head,
494                                     h->mq_tail,
495                                     qe);
496   if (GNUNET_SCHEDULER_NO_TASK == h->write_task)
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   return GNUNET_YES;
503 }
504
505
506 /* end of helper.c */