first step to remove plibc
[oweals/gnunet.git] / src / util / helper.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011, 2012 Christian Grothoff
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21 /**
22  * @file util/helper.c
23  * @brief API for dealing with (SUID) helper processes that communicate via
24  *          GNUNET_MessageHeaders on stdin/stdout
25  * @author Philipp Toelke
26  * @author Christian Grothoff
27  */
28 #include "platform.h"
29 #include "gnunet_util_lib.h"
30 #include "gnunet_mst_lib.h"
31
32
33 /**
34  * Entry in the queue of messages we need to transmit to the helper.
35  */
36 struct GNUNET_HELPER_SendHandle
37 {
38
39   /**
40    * This is an entry in a DLL.
41    */
42   struct GNUNET_HELPER_SendHandle *next;
43
44   /**
45    * This is an entry in a DLL.
46    */
47   struct GNUNET_HELPER_SendHandle *prev;
48
49   /**
50    * Message to transmit (allocated at the end of this struct)
51    */
52   const struct GNUNET_MessageHeader *msg;
53
54   /**
55    * The handle to a helper process.
56    */
57   struct GNUNET_HELPER_Handle *h;
58
59   /**
60    * Function to call upon completion.
61    */
62   GNUNET_HELPER_Continuation cont;
63
64   /**
65    * Closure to 'cont'.
66    */
67   void *cont_cls;
68
69   /**
70    * Current write position.
71    */
72   unsigned int wpos;
73 };
74
75
76 /**
77  * The handle to a helper process.
78  */
79 struct GNUNET_HELPER_Handle
80 {
81
82   /**
83    * PipeHandle to receive data from the helper
84    */
85   struct GNUNET_DISK_PipeHandle *helper_in;
86
87   /**
88    * PipeHandle to send data to the helper
89    */
90   struct GNUNET_DISK_PipeHandle *helper_out;
91
92   /**
93    * FileHandle to receive data from the helper
94    */
95   const struct GNUNET_DISK_FileHandle *fh_from_helper;
96
97   /**
98    * FileHandle to send data to the helper
99    */
100   const struct GNUNET_DISK_FileHandle *fh_to_helper;
101
102   /**
103    * The process id of the helper
104    */
105   struct GNUNET_OS_Process *helper_proc;
106
107   /**
108    * The Message-Tokenizer that tokenizes the messages comming from the helper
109    */
110   struct GNUNET_MessageStreamTokenizer *mst;
111
112   /**
113    * The exception callback
114    */
115   GNUNET_HELPER_ExceptionCallback exp_cb;
116
117   /**
118    * The closure for callbacks
119    */
120   void *cb_cls;
121
122   /**
123    * First message queued for transmission to helper.
124    */
125   struct GNUNET_HELPER_SendHandle *sh_head;
126
127   /**
128    * Last message queued for transmission to helper.
129    */
130   struct GNUNET_HELPER_SendHandle *sh_tail;
131
132   /**
133    * Binary to run.
134    */
135   char *binary_name;
136
137   /**
138    * NULL-terminated list of command-line arguments.
139    */
140   char **binary_argv;
141
142   /**
143    * Task to read from the helper.
144    */
145   struct GNUNET_SCHEDULER_Task *read_task;
146
147   /**
148    * Task to read from the helper.
149    */
150   struct GNUNET_SCHEDULER_Task *write_task;
151
152   /**
153    * Restart task.
154    */
155   struct GNUNET_SCHEDULER_Task *restart_task;
156
157   /**
158    * Does the helper support the use of a control pipe for signalling?
159    */
160   int with_control_pipe;
161
162   /**
163    * Count start attempts to increase linear back off
164    */
165   unsigned int retry_back_off;
166 };
167
168
169 /**
170  * Sends termination signal to the helper process.  The helper process is not
171  * reaped; call GNUNET_HELPER_wait() for reaping the dead helper process.
172  *
173  * @param h the helper handle
174  * @param soft_kill if GNUNET_YES, signals termination by closing the helper's
175  *          stdin; GNUNET_NO to signal termination by sending SIGTERM to helper
176  * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
177  */
178 int
179 GNUNET_HELPER_kill (struct GNUNET_HELPER_Handle *h, int soft_kill)
180 {
181   struct GNUNET_HELPER_SendHandle *sh;
182   int ret;
183
184   while (NULL != (sh = h->sh_head))
185   {
186     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
187     if (NULL != sh->cont)
188       sh->cont (sh->cont_cls, GNUNET_NO);
189     GNUNET_free (sh);
190   }
191   if (NULL != h->restart_task)
192   {
193     GNUNET_SCHEDULER_cancel (h->restart_task);
194     h->restart_task = NULL;
195   }
196   if (NULL != h->read_task)
197   {
198     GNUNET_SCHEDULER_cancel (h->read_task);
199     h->read_task = NULL;
200   }
201   if (NULL == h->helper_proc)
202     return GNUNET_SYSERR;
203   if (GNUNET_YES == soft_kill)
204   {
205     /* soft-kill only possible with pipes */
206     GNUNET_assert (NULL != h->helper_in);
207     ret = GNUNET_DISK_pipe_close (h->helper_in);
208     h->helper_in = NULL;
209     h->fh_to_helper = NULL;
210     return ret;
211   }
212   if (0 != GNUNET_OS_process_kill (h->helper_proc, GNUNET_TERM_SIG))
213     return GNUNET_SYSERR;
214   return GNUNET_OK;
215 }
216
217
218 /**
219  * Reap the helper process.  This call is blocking(!).  The helper process
220  * should either be sent a termination signal before or should be dead before
221  * calling this function
222  *
223  * @param h the helper handle
224  * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
225  */
226 int
227 GNUNET_HELPER_wait (struct GNUNET_HELPER_Handle *h)
228 {
229   struct GNUNET_HELPER_SendHandle *sh;
230   int ret;
231
232   ret = GNUNET_SYSERR;
233   if (NULL != h->helper_proc)
234   {
235     ret = GNUNET_OS_process_wait (h->helper_proc);
236     GNUNET_OS_process_destroy (h->helper_proc);
237     h->helper_proc = NULL;
238   }
239   if (NULL != h->read_task)
240   {
241     GNUNET_SCHEDULER_cancel (h->read_task);
242     h->read_task = NULL;
243   }
244   if (NULL != h->write_task)
245   {
246     GNUNET_SCHEDULER_cancel (h->write_task);
247     h->write_task = NULL;
248   }
249   if (NULL != h->helper_in)
250   {
251     GNUNET_DISK_pipe_close (h->helper_in);
252     h->helper_in = NULL;
253     h->fh_to_helper = NULL;
254   }
255   if (NULL != h->helper_out)
256   {
257     GNUNET_DISK_pipe_close (h->helper_out);
258     h->helper_out = NULL;
259     h->fh_from_helper = NULL;
260   }
261   while (NULL != (sh = h->sh_head))
262   {
263     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
264     if (NULL != sh->cont)
265       sh->cont (sh->cont_cls, GNUNET_NO);
266     GNUNET_free (sh);
267   }
268   /* purge MST buffer */
269   if (NULL != h->mst)
270     (void) GNUNET_MST_from_buffer (h->mst, NULL, 0, GNUNET_YES, GNUNET_NO);
271   return ret;
272 }
273
274
275 /**
276  * Stop the helper process, we're closing down or had an error.
277  *
278  * @param h handle to the helper process
279  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
280  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
281  */
282 static void
283 stop_helper (struct GNUNET_HELPER_Handle *h, int soft_kill)
284 {
285   if (NULL != h->restart_task)
286   {
287     GNUNET_SCHEDULER_cancel (h->restart_task);
288     h->restart_task = NULL;
289   }
290   else
291   {
292     GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
293     GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
294   }
295 }
296
297
298 /**
299  * Restart the helper process.
300  *
301  * @param cls handle to the helper process
302  */
303 static void
304 restart_task (void *cls);
305
306
307 /**
308  * Read from the helper-process
309  *
310  * @param cls handle to the helper process
311  */
312 static void
313 helper_read (void *cls)
314 {
315   struct GNUNET_HELPER_Handle *h = cls;
316   char buf[GNUNET_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
317   ssize_t t;
318
319   h->read_task = NULL;
320   t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof (buf));
321   if (t < 0)
322   {
323     /* On read-error, restart the helper */
324     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
325                 _ ("Error reading from `%s': %s\n"),
326                 h->binary_name,
327                 strerror (errno));
328     if (NULL != h->exp_cb)
329     {
330       h->exp_cb (h->cb_cls);
331       GNUNET_HELPER_stop (h, GNUNET_NO);
332       return;
333     }
334     stop_helper (h, GNUNET_NO);
335     /* Restart the helper */
336     h->restart_task = GNUNET_SCHEDULER_add_delayed (
337       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
338                                      h->retry_back_off),
339       &restart_task,
340       h);
341     return;
342   }
343   if (0 == t)
344   {
345     /* this happens if the helper is shut down via a
346        signal, so it is not a "hard" error */
347     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
348                 "Got 0 bytes from helper `%s' (EOF)\n",
349                 h->binary_name);
350     if (NULL != h->exp_cb)
351     {
352       h->exp_cb (h->cb_cls);
353       GNUNET_HELPER_stop (h, GNUNET_NO);
354       return;
355     }
356     stop_helper (h, GNUNET_NO);
357     /* Restart the helper */
358     h->restart_task = GNUNET_SCHEDULER_add_delayed (
359       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
360                                      h->retry_back_off),
361       &restart_task,
362       h);
363     return;
364   }
365   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
366               "Got %u bytes from helper `%s'\n",
367               (unsigned int) t,
368               h->binary_name);
369   h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
370                                                  h->fh_from_helper,
371                                                  &helper_read,
372                                                  h);
373   if (GNUNET_SYSERR ==
374       GNUNET_MST_from_buffer (h->mst, buf, t, GNUNET_NO, GNUNET_NO))
375   {
376     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
377                 _ ("Failed to parse inbound message from helper `%s'\n"),
378                 h->binary_name);
379     if (NULL != h->exp_cb)
380     {
381       h->exp_cb (h->cb_cls);
382       GNUNET_HELPER_stop (h, GNUNET_NO);
383       return;
384     }
385     stop_helper (h, GNUNET_NO);
386     /* Restart the helper */
387     h->restart_task = GNUNET_SCHEDULER_add_delayed (
388       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
389                                      h->retry_back_off),
390       &restart_task,
391       h);
392     return;
393   }
394 }
395
396
397 /**
398  * Start the helper process.
399  *
400  * @param h handle to the helper process
401  */
402 static void
403 start_helper (struct GNUNET_HELPER_Handle *h)
404 {
405   h->helper_in =
406     GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_YES, GNUNET_NO);
407   h->helper_out =
408     GNUNET_DISK_pipe (GNUNET_YES, GNUNET_YES, GNUNET_NO, GNUNET_YES);
409   if ((h->helper_in == NULL) || (h->helper_out == NULL))
410   {
411     /* out of file descriptors? try again later... */
412     stop_helper (h, GNUNET_NO);
413     h->restart_task = GNUNET_SCHEDULER_add_delayed (
414       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
415                                      h->retry_back_off),
416       &restart_task,
417       h);
418     return;
419   }
420   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
421               "Starting HELPER process `%s'\n",
422               h->binary_name);
423   h->fh_from_helper =
424     GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
425   h->fh_to_helper =
426     GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
427   h->helper_proc = GNUNET_OS_start_process_vap (h->with_control_pipe,
428                                                 GNUNET_OS_INHERIT_STD_ERR,
429                                                 h->helper_in,
430                                                 h->helper_out,
431                                                 NULL,
432                                                 h->binary_name,
433                                                 h->binary_argv);
434   if (NULL == h->helper_proc)
435   {
436     /* failed to start process? try again later... */
437     stop_helper (h, GNUNET_NO);
438     h->restart_task = GNUNET_SCHEDULER_add_delayed (
439       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
440                                      h->retry_back_off),
441       &restart_task,
442       h);
443     return;
444   }
445   GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
446   GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
447   if (NULL != h->mst)
448     h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
449                                                    h->fh_from_helper,
450                                                    &helper_read,
451                                                    h);
452 }
453
454
455 /**
456  * Restart the helper process.
457  *
458  * @param cls handle to the helper process
459  */
460 static void
461 restart_task (void *cls)
462 {
463   struct GNUNET_HELPER_Handle *h = cls;
464
465   h->restart_task = NULL;
466   h->retry_back_off++;
467   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
468               "Restarting helper with back-off %u\n",
469               h->retry_back_off);
470   start_helper (h);
471 }
472
473
474 /**
475  * Starts a helper and begins reading from it. The helper process is
476  * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
477  * or when the exp_cb callback is not NULL.
478  *
479  * @param with_control_pipe does the helper support the use of a control pipe for signalling?
480  * @param binary_name name of the binary to run
481  * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
482  *                    argument must not be modified by the client for
483  *                     the lifetime of the helper handle)
484  * @param cb function to call if we get messages from the helper
485  * @param exp_cb the exception callback to call. Set this to NULL if the helper
486  *          process has to be restarted automatically when it dies/crashes
487  * @param cb_cls closure for the above callback
488  * @return the new Handle, NULL on error
489  */
490 struct GNUNET_HELPER_Handle *
491 GNUNET_HELPER_start (int with_control_pipe,
492                      const char *binary_name,
493                      char *const binary_argv[],
494                      GNUNET_MessageTokenizerCallback cb,
495                      GNUNET_HELPER_ExceptionCallback exp_cb,
496                      void *cb_cls)
497 {
498   struct GNUNET_HELPER_Handle *h;
499   unsigned int c;
500
501   h = GNUNET_new (struct GNUNET_HELPER_Handle);
502   h->with_control_pipe = with_control_pipe;
503   /* Lookup in libexec path only if we are starting gnunet helpers */
504   if (NULL != strstr (binary_name, "gnunet"))
505     h->binary_name = GNUNET_OS_get_libexec_binary_path (binary_name);
506   else
507     h->binary_name = GNUNET_strdup (binary_name);
508   for (c = 0; NULL != binary_argv[c]; c++)
509     ;
510   h->binary_argv = GNUNET_malloc (sizeof (char *) * (c + 1));
511   for (c = 0; NULL != binary_argv[c]; c++)
512     h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
513   h->binary_argv[c] = NULL;
514   h->cb_cls = cb_cls;
515   if (NULL != cb)
516     h->mst = GNUNET_MST_create (cb, h->cb_cls);
517   h->exp_cb = exp_cb;
518   h->retry_back_off = 0;
519   start_helper (h);
520   return h;
521 }
522
523
524 /**
525  * Free's the resources occupied by the helper handle
526  *
527  * @param h the helper handle to free
528  */
529 void
530 GNUNET_HELPER_destroy (struct GNUNET_HELPER_Handle *h)
531 {
532   unsigned int c;
533   struct GNUNET_HELPER_SendHandle *sh;
534
535   if (NULL != h->write_task)
536   {
537     GNUNET_SCHEDULER_cancel (h->write_task);
538     h->write_task = NULL;
539   }
540   GNUNET_assert (NULL == h->read_task);
541   GNUNET_assert (NULL == h->restart_task);
542   while (NULL != (sh = h->sh_head))
543   {
544     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
545     if (NULL != sh->cont)
546       sh->cont (sh->cont_cls, GNUNET_SYSERR);
547     GNUNET_free (sh);
548   }
549   if (NULL != h->mst)
550     GNUNET_MST_destroy (h->mst);
551   GNUNET_free (h->binary_name);
552   for (c = 0; h->binary_argv[c] != NULL; c++)
553     GNUNET_free (h->binary_argv[c]);
554   GNUNET_free (h->binary_argv);
555   GNUNET_free (h);
556 }
557
558
559 /**
560  * Kills the helper, closes the pipe and frees the handle
561  *
562  * @param h handle to helper to stop
563  * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
564  *          stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
565  */
566 void
567 GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h, int soft_kill)
568 {
569   h->exp_cb = NULL;
570   stop_helper (h, soft_kill);
571   GNUNET_HELPER_destroy (h);
572 }
573
574
575 /**
576  * Write to the helper-process
577  *
578  * @param cls handle to the helper process
579  */
580 static void
581 helper_write (void *cls)
582 {
583   struct GNUNET_HELPER_Handle *h = cls;
584   struct GNUNET_HELPER_SendHandle *sh;
585   const char *buf;
586   ssize_t t;
587
588   h->write_task = NULL;
589   if (NULL == (sh = h->sh_head))
590   {
591     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Helper write had no work!\n");
592     return; /* how did this happen? */
593   }
594   buf = (const char *) sh->msg;
595   t = GNUNET_DISK_file_write (h->fh_to_helper,
596                               &buf[sh->wpos],
597                               ntohs (sh->msg->size) - sh->wpos);
598   if (-1 == t)
599   {
600     /* On write-error, restart the helper */
601     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
602                 _ ("Error writing to `%s': %s\n"),
603                 h->binary_name,
604                 strerror (errno));
605     if (NULL != h->exp_cb)
606     {
607       h->exp_cb (h->cb_cls);
608       GNUNET_HELPER_stop (h, GNUNET_NO);
609       return;
610     }
611     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
612                 "Stopping and restarting helper task!\n");
613     stop_helper (h, GNUNET_NO);
614     /* Restart the helper */
615     h->restart_task = GNUNET_SCHEDULER_add_delayed (
616       GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
617                                      h->retry_back_off),
618       &restart_task,
619       h);
620     return;
621   }
622   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
623               "Transmitted %u bytes to %s\n",
624               (unsigned int) t,
625               h->binary_name);
626   sh->wpos += t;
627   if (sh->wpos == ntohs (sh->msg->size))
628   {
629     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
630     if (NULL != sh->cont)
631       sh->cont (sh->cont_cls, GNUNET_YES);
632     GNUNET_free (sh);
633   }
634   if (NULL != h->sh_head)
635     h->write_task =
636       GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
637                                        h->fh_to_helper,
638                                        &helper_write,
639                                        h);
640 }
641
642
643 /**
644  * Send an message to the helper.
645  *
646  * @param h helper to send message to
647  * @param msg message to send
648  * @param can_drop can the message be dropped if there is already one in the queue?
649  * @param cont continuation to run once the message is out (#GNUNET_OK on succees, #GNUNET_NO
650  *             if the helper process died, #GNUNET_SYSERR during #GNUNET_HELPER_destroy).
651  * @param cont_cls closure for @a cont
652  * @return NULL if the message was dropped,
653  *         otherwise handle to cancel *cont* (actual transmission may
654  *         not be abortable)
655  */
656 struct GNUNET_HELPER_SendHandle *
657 GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h,
658                     const struct GNUNET_MessageHeader *msg,
659                     int can_drop,
660                     GNUNET_HELPER_Continuation cont,
661                     void *cont_cls)
662 {
663   struct GNUNET_HELPER_SendHandle *sh;
664   uint16_t mlen;
665
666   if (NULL == h->fh_to_helper)
667     return NULL;
668   if ((GNUNET_YES == can_drop) && (NULL != h->sh_head))
669     return NULL;
670   mlen = ntohs (msg->size);
671   sh = GNUNET_malloc (sizeof (struct GNUNET_HELPER_SendHandle) + mlen);
672   sh->msg = (const struct GNUNET_MessageHeader *) &sh[1];
673   GNUNET_memcpy (&sh[1], msg, mlen);
674   sh->h = h;
675   sh->cont = cont;
676   sh->cont_cls = cont_cls;
677   GNUNET_CONTAINER_DLL_insert_tail (h->sh_head, h->sh_tail, sh);
678   if (NULL == h->write_task)
679     h->write_task =
680       GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
681                                        h->fh_to_helper,
682                                        &helper_write,
683                                        h);
684
685   return sh;
686 }
687
688 /**
689  * Cancel a #GNUNET_HELPER_send operation.  If possible, transmitting the
690  * message is also aborted, but at least 'cont' won't be
691  * called.
692  *
693  * @param sh operation to cancel
694  */
695 void
696 GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
697 {
698   struct GNUNET_HELPER_Handle *h = sh->h;
699
700   sh->cont = NULL;
701   sh->cont_cls = NULL;
702   if (0 == sh->wpos)
703   {
704     GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
705     GNUNET_free (sh);
706     if (NULL == h->sh_head)
707     {
708       GNUNET_SCHEDULER_cancel (h->write_task);
709       h->write_task = NULL;
710     }
711   }
712 }
713
714
715 /* end of helper.c */