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