123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716 |
- /*
- This file is part of GNUnet.
- Copyright (C) 2011, 2012 Christian Grothoff
- GNUnet is free software: you can redistribute it and/or modify it
- under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
- GNUnet is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- SPDX-License-Identifier: AGPL3.0-or-later
- */
- /**
- * @file util/helper.c
- * @brief API for dealing with (SUID) helper processes that communicate via
- * GNUNET_MessageHeaders on stdin/stdout
- * @author Philipp Toelke
- * @author Christian Grothoff
- */
- #include "platform.h"
- #include "gnunet_util_lib.h"
- #include "gnunet_mst_lib.h"
- /**
- * Entry in the queue of messages we need to transmit to the helper.
- */
- struct GNUNET_HELPER_SendHandle
- {
- /**
- * This is an entry in a DLL.
- */
- struct GNUNET_HELPER_SendHandle *next;
- /**
- * This is an entry in a DLL.
- */
- struct GNUNET_HELPER_SendHandle *prev;
- /**
- * Message to transmit (allocated at the end of this struct)
- */
- const struct GNUNET_MessageHeader *msg;
- /**
- * The handle to a helper process.
- */
- struct GNUNET_HELPER_Handle *h;
- /**
- * Function to call upon completion.
- */
- GNUNET_HELPER_Continuation cont;
- /**
- * Closure to 'cont'.
- */
- void *cont_cls;
- /**
- * Current write position.
- */
- unsigned int wpos;
- };
- /**
- * The handle to a helper process.
- */
- struct GNUNET_HELPER_Handle
- {
- /**
- * PipeHandle to receive data from the helper
- */
- struct GNUNET_DISK_PipeHandle *helper_in;
- /**
- * PipeHandle to send data to the helper
- */
- struct GNUNET_DISK_PipeHandle *helper_out;
- /**
- * FileHandle to receive data from the helper
- */
- const struct GNUNET_DISK_FileHandle *fh_from_helper;
- /**
- * FileHandle to send data to the helper
- */
- const struct GNUNET_DISK_FileHandle *fh_to_helper;
- /**
- * The process id of the helper
- */
- struct GNUNET_OS_Process *helper_proc;
- /**
- * The Message-Tokenizer that tokenizes the messages coming from the helper
- */
- struct GNUNET_MessageStreamTokenizer *mst;
- /**
- * The exception callback
- */
- GNUNET_HELPER_ExceptionCallback exp_cb;
- /**
- * The closure for callbacks
- */
- void *cb_cls;
- /**
- * First message queued for transmission to helper.
- */
- struct GNUNET_HELPER_SendHandle *sh_head;
- /**
- * Last message queued for transmission to helper.
- */
- struct GNUNET_HELPER_SendHandle *sh_tail;
- /**
- * Binary to run.
- */
- char *binary_name;
- /**
- * NULL-terminated list of command-line arguments.
- */
- char **binary_argv;
- /**
- * Task to read from the helper.
- */
- struct GNUNET_SCHEDULER_Task *read_task;
- /**
- * Task to read from the helper.
- */
- struct GNUNET_SCHEDULER_Task *write_task;
- /**
- * Restart task.
- */
- struct GNUNET_SCHEDULER_Task *restart_task;
- /**
- * Does the helper support the use of a control pipe for signalling?
- */
- int with_control_pipe;
- /**
- * Count start attempts to increase linear back off
- */
- unsigned int retry_back_off;
- };
- /**
- * Sends termination signal to the helper process. The helper process is not
- * reaped; call GNUNET_HELPER_wait() for reaping the dead helper process.
- *
- * @param h the helper handle
- * @param soft_kill if GNUNET_YES, signals termination by closing the helper's
- * stdin; GNUNET_NO to signal termination by sending SIGTERM to helper
- * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
- */
- int
- GNUNET_HELPER_kill (struct GNUNET_HELPER_Handle *h, int soft_kill)
- {
- struct GNUNET_HELPER_SendHandle *sh;
- int ret;
- while (NULL != (sh = h->sh_head))
- {
- GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
- if (NULL != sh->cont)
- sh->cont (sh->cont_cls, GNUNET_NO);
- GNUNET_free (sh);
- }
- if (NULL != h->restart_task)
- {
- GNUNET_SCHEDULER_cancel (h->restart_task);
- h->restart_task = NULL;
- }
- if (NULL != h->read_task)
- {
- GNUNET_SCHEDULER_cancel (h->read_task);
- h->read_task = NULL;
- }
- if (NULL == h->helper_proc)
- return GNUNET_SYSERR;
- if (GNUNET_YES == soft_kill)
- {
- /* soft-kill only possible with pipes */
- GNUNET_assert (NULL != h->helper_in);
- ret = GNUNET_DISK_pipe_close (h->helper_in);
- h->helper_in = NULL;
- h->fh_to_helper = NULL;
- return ret;
- }
- if (0 != GNUNET_OS_process_kill (h->helper_proc, GNUNET_TERM_SIG))
- return GNUNET_SYSERR;
- return GNUNET_OK;
- }
- /**
- * Reap the helper process. This call is blocking(!). The helper process
- * should either be sent a termination signal before or should be dead before
- * calling this function
- *
- * @param h the helper handle
- * @return #GNUNET_OK on success; #GNUNET_SYSERR on error
- */
- int
- GNUNET_HELPER_wait (struct GNUNET_HELPER_Handle *h)
- {
- struct GNUNET_HELPER_SendHandle *sh;
- int ret;
- ret = GNUNET_SYSERR;
- if (NULL != h->helper_proc)
- {
- ret = GNUNET_OS_process_wait (h->helper_proc);
- GNUNET_OS_process_destroy (h->helper_proc);
- h->helper_proc = NULL;
- }
- if (NULL != h->read_task)
- {
- GNUNET_SCHEDULER_cancel (h->read_task);
- h->read_task = NULL;
- }
- if (NULL != h->write_task)
- {
- GNUNET_SCHEDULER_cancel (h->write_task);
- h->write_task = NULL;
- }
- if (NULL != h->helper_in)
- {
- GNUNET_DISK_pipe_close (h->helper_in);
- h->helper_in = NULL;
- h->fh_to_helper = NULL;
- }
- if (NULL != h->helper_out)
- {
- GNUNET_DISK_pipe_close (h->helper_out);
- h->helper_out = NULL;
- h->fh_from_helper = NULL;
- }
- while (NULL != (sh = h->sh_head))
- {
- GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
- if (NULL != sh->cont)
- sh->cont (sh->cont_cls, GNUNET_NO);
- GNUNET_free (sh);
- }
- /* purge MST buffer */
- if (NULL != h->mst)
- (void) GNUNET_MST_from_buffer (h->mst, NULL, 0, GNUNET_YES, GNUNET_NO);
- return ret;
- }
- /**
- * Stop the helper process, we're closing down or had an error.
- *
- * @param h handle to the helper process
- * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
- * stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
- */
- static void
- stop_helper (struct GNUNET_HELPER_Handle *h, int soft_kill)
- {
- if (NULL != h->restart_task)
- {
- GNUNET_SCHEDULER_cancel (h->restart_task);
- h->restart_task = NULL;
- }
- else
- {
- GNUNET_break (GNUNET_OK == GNUNET_HELPER_kill (h, soft_kill));
- GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (h));
- }
- }
- /**
- * Restart the helper process.
- *
- * @param cls handle to the helper process
- */
- static void
- restart_task (void *cls);
- /**
- * Read from the helper-process
- *
- * @param cls handle to the helper process
- */
- static void
- helper_read (void *cls)
- {
- struct GNUNET_HELPER_Handle *h = cls;
- char buf[GNUNET_MAX_MESSAGE_SIZE] GNUNET_ALIGN;
- ssize_t t;
- h->read_task = NULL;
- t = GNUNET_DISK_file_read (h->fh_from_helper, &buf, sizeof(buf));
- if (t < 0)
- {
- /* On read-error, restart the helper */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- _ ("Error reading from `%s': %s\n"),
- h->binary_name,
- strerror (errno));
- if (NULL != h->exp_cb)
- {
- h->exp_cb (h->cb_cls);
- GNUNET_HELPER_stop (h, GNUNET_NO);
- return;
- }
- stop_helper (h, GNUNET_NO);
- /* Restart the helper */
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- if (0 == t)
- {
- /* this happens if the helper is shut down via a
- signal, so it is not a "hard" error */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got 0 bytes from helper `%s' (EOF)\n",
- h->binary_name);
- if (NULL != h->exp_cb)
- {
- h->exp_cb (h->cb_cls);
- GNUNET_HELPER_stop (h, GNUNET_NO);
- return;
- }
- stop_helper (h, GNUNET_NO);
- /* Restart the helper */
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got %u bytes from helper `%s'\n",
- (unsigned int) t,
- h->binary_name);
- h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
- h->fh_from_helper,
- &helper_read,
- h);
- if (GNUNET_SYSERR ==
- GNUNET_MST_from_buffer (h->mst, buf, t, GNUNET_NO, GNUNET_NO))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- _ ("Failed to parse inbound message from helper `%s'\n"),
- h->binary_name);
- if (NULL != h->exp_cb)
- {
- h->exp_cb (h->cb_cls);
- GNUNET_HELPER_stop (h, GNUNET_NO);
- return;
- }
- stop_helper (h, GNUNET_NO);
- /* Restart the helper */
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- }
- /**
- * Start the helper process.
- *
- * @param h handle to the helper process
- */
- static void
- start_helper (struct GNUNET_HELPER_Handle *h)
- {
- h->helper_in =
- GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
- h->helper_out =
- GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
- if ((h->helper_in == NULL) || (h->helper_out == NULL))
- {
- /* out of file descriptors? try again later... */
- stop_helper (h, GNUNET_NO);
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting HELPER process `%s'\n",
- h->binary_name);
- h->fh_from_helper =
- GNUNET_DISK_pipe_handle (h->helper_out, GNUNET_DISK_PIPE_END_READ);
- h->fh_to_helper =
- GNUNET_DISK_pipe_handle (h->helper_in, GNUNET_DISK_PIPE_END_WRITE);
- h->helper_proc = GNUNET_OS_start_process_vap (h->with_control_pipe
- ? GNUNET_OS_INHERIT_STD_ERR
- | GNUNET_OS_USE_PIPE_CONTROL
- : GNUNET_OS_INHERIT_STD_ERR,
- h->helper_in,
- h->helper_out,
- NULL,
- h->binary_name,
- h->binary_argv);
- if (NULL == h->helper_proc)
- {
- /* failed to start process? try again later... */
- stop_helper (h, GNUNET_NO);
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- GNUNET_DISK_pipe_close_end (h->helper_out, GNUNET_DISK_PIPE_END_WRITE);
- GNUNET_DISK_pipe_close_end (h->helper_in, GNUNET_DISK_PIPE_END_READ);
- if (NULL != h->mst)
- h->read_task = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
- h->fh_from_helper,
- &helper_read,
- h);
- }
- /**
- * Restart the helper process.
- *
- * @param cls handle to the helper process
- */
- static void
- restart_task (void *cls)
- {
- struct GNUNET_HELPER_Handle *h = cls;
- h->restart_task = NULL;
- h->retry_back_off++;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Restarting helper with back-off %u\n",
- h->retry_back_off);
- start_helper (h);
- }
- /**
- * Starts a helper and begins reading from it. The helper process is
- * restarted when it dies except when it is stopped using GNUNET_HELPER_stop()
- * or when the exp_cb callback is not NULL.
- *
- * @param with_control_pipe does the helper support the use of a control pipe for signalling?
- * @param binary_name name of the binary to run
- * @param binary_argv NULL-terminated list of arguments to give when starting the binary (this
- * argument must not be modified by the client for
- * the lifetime of the helper handle)
- * @param cb function to call if we get messages from the helper
- * @param exp_cb the exception callback to call. Set this to NULL if the helper
- * process has to be restarted automatically when it dies/crashes
- * @param cb_cls closure for the above callback
- * @return the new Handle, NULL on error
- */
- struct GNUNET_HELPER_Handle *
- GNUNET_HELPER_start (int with_control_pipe,
- const char *binary_name,
- char *const binary_argv[],
- GNUNET_MessageTokenizerCallback cb,
- GNUNET_HELPER_ExceptionCallback exp_cb,
- void *cb_cls)
- {
- struct GNUNET_HELPER_Handle *h;
- unsigned int c;
- h = GNUNET_new (struct GNUNET_HELPER_Handle);
- h->with_control_pipe = with_control_pipe;
- /* Lookup in libexec path only if we are starting gnunet helpers */
- if (NULL != strstr (binary_name, "gnunet"))
- h->binary_name = GNUNET_OS_get_libexec_binary_path (binary_name);
- else
- h->binary_name = GNUNET_strdup (binary_name);
- for (c = 0; NULL != binary_argv[c]; c++)
- ;
- h->binary_argv = GNUNET_malloc (sizeof(char *) * (c + 1));
- for (c = 0; NULL != binary_argv[c]; c++)
- h->binary_argv[c] = GNUNET_strdup (binary_argv[c]);
- h->binary_argv[c] = NULL;
- h->cb_cls = cb_cls;
- if (NULL != cb)
- h->mst = GNUNET_MST_create (cb, h->cb_cls);
- h->exp_cb = exp_cb;
- h->retry_back_off = 0;
- start_helper (h);
- return h;
- }
- /**
- * Free's the resources occupied by the helper handle
- *
- * @param h the helper handle to free
- */
- void
- GNUNET_HELPER_destroy (struct GNUNET_HELPER_Handle *h)
- {
- unsigned int c;
- struct GNUNET_HELPER_SendHandle *sh;
- if (NULL != h->write_task)
- {
- GNUNET_SCHEDULER_cancel (h->write_task);
- h->write_task = NULL;
- }
- GNUNET_assert (NULL == h->read_task);
- GNUNET_assert (NULL == h->restart_task);
- while (NULL != (sh = h->sh_head))
- {
- GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
- if (NULL != sh->cont)
- sh->cont (sh->cont_cls, GNUNET_SYSERR);
- GNUNET_free (sh);
- }
- if (NULL != h->mst)
- GNUNET_MST_destroy (h->mst);
- GNUNET_free (h->binary_name);
- for (c = 0; h->binary_argv[c] != NULL; c++)
- GNUNET_free (h->binary_argv[c]);
- GNUNET_free (h->binary_argv);
- GNUNET_free (h);
- }
- /**
- * Kills the helper, closes the pipe and frees the handle
- *
- * @param h handle to helper to stop
- * @param soft_kill if #GNUNET_YES, signals termination by closing the helper's
- * stdin; #GNUNET_NO to signal termination by sending SIGTERM to helper
- */
- void
- GNUNET_HELPER_stop (struct GNUNET_HELPER_Handle *h, int soft_kill)
- {
- h->exp_cb = NULL;
- stop_helper (h, soft_kill);
- GNUNET_HELPER_destroy (h);
- }
- /**
- * Write to the helper-process
- *
- * @param cls handle to the helper process
- */
- static void
- helper_write (void *cls)
- {
- struct GNUNET_HELPER_Handle *h = cls;
- struct GNUNET_HELPER_SendHandle *sh;
- const char *buf;
- ssize_t t;
- h->write_task = NULL;
- if (NULL == (sh = h->sh_head))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Helper write had no work!\n");
- return; /* how did this happen? */
- }
- buf = (const char *) sh->msg;
- t = GNUNET_DISK_file_write (h->fh_to_helper,
- &buf[sh->wpos],
- ntohs (sh->msg->size) - sh->wpos);
- if (-1 == t)
- {
- /* On write-error, restart the helper */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- _ ("Error writing to `%s': %s\n"),
- h->binary_name,
- strerror (errno));
- if (NULL != h->exp_cb)
- {
- h->exp_cb (h->cb_cls);
- GNUNET_HELPER_stop (h, GNUNET_NO);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Stopping and restarting helper task!\n");
- stop_helper (h, GNUNET_NO);
- /* Restart the helper */
- h->restart_task = GNUNET_SCHEDULER_add_delayed (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- h->retry_back_off),
- &restart_task,
- h);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Transmitted %u bytes to %s\n",
- (unsigned int) t,
- h->binary_name);
- sh->wpos += t;
- if (sh->wpos == ntohs (sh->msg->size))
- {
- GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
- if (NULL != sh->cont)
- sh->cont (sh->cont_cls, GNUNET_YES);
- GNUNET_free (sh);
- }
- if (NULL != h->sh_head)
- h->write_task =
- GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
- h->fh_to_helper,
- &helper_write,
- h);
- }
- /**
- * Send an message to the helper.
- *
- * @param h helper to send message to
- * @param msg message to send
- * @param can_drop can the message be dropped if there is already one in the queue?
- * @param cont continuation to run once the message is out (#GNUNET_OK on success, #GNUNET_NO
- * if the helper process died, #GNUNET_SYSERR during #GNUNET_HELPER_destroy).
- * @param cont_cls closure for @a cont
- * @return NULL if the message was dropped,
- * otherwise handle to cancel *cont* (actual transmission may
- * not be abortable)
- */
- struct GNUNET_HELPER_SendHandle *
- GNUNET_HELPER_send (struct GNUNET_HELPER_Handle *h,
- const struct GNUNET_MessageHeader *msg,
- int can_drop,
- GNUNET_HELPER_Continuation cont,
- void *cont_cls)
- {
- struct GNUNET_HELPER_SendHandle *sh;
- uint16_t mlen;
- if (NULL == h->fh_to_helper)
- return NULL;
- if ((GNUNET_YES == can_drop) && (NULL != h->sh_head))
- return NULL;
- mlen = ntohs (msg->size);
- sh = GNUNET_malloc (sizeof(struct GNUNET_HELPER_SendHandle) + mlen);
- sh->msg = (const struct GNUNET_MessageHeader *) &sh[1];
- GNUNET_memcpy (&sh[1], msg, mlen);
- sh->h = h;
- sh->cont = cont;
- sh->cont_cls = cont_cls;
- GNUNET_CONTAINER_DLL_insert_tail (h->sh_head, h->sh_tail, sh);
- if (NULL == h->write_task)
- h->write_task =
- GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
- h->fh_to_helper,
- &helper_write,
- h);
- return sh;
- }
- /**
- * Cancel a #GNUNET_HELPER_send operation. If possible, transmitting the
- * message is also aborted, but at least 'cont' won't be
- * called.
- *
- * @param sh operation to cancel
- */
- void
- GNUNET_HELPER_send_cancel (struct GNUNET_HELPER_SendHandle *sh)
- {
- struct GNUNET_HELPER_Handle *h = sh->h;
- sh->cont = NULL;
- sh->cont_cls = NULL;
- if (0 == sh->wpos)
- {
- GNUNET_CONTAINER_DLL_remove (h->sh_head, h->sh_tail, sh);
- GNUNET_free (sh);
- if (NULL == h->sh_head)
- {
- GNUNET_SCHEDULER_cancel (h->write_task);
- h->write_task = NULL;
- }
- }
- }
- /* end of helper.c */
|