diff options
author | Werner Koch <[email protected]> | 2014-04-10 11:01:00 +0000 |
---|---|---|
committer | Werner Koch <[email protected]> | 2014-04-10 11:01:00 +0000 |
commit | 4f2d652e60700e03809307a10015ff9003ac3579 (patch) | |
tree | 9abc417b8a7ba1fca91c0986e921288480441d84 /src/engine-spawn.c | |
parent | Add gpgme_get_dirinfo. (diff) | |
download | gpgme-4f2d652e60700e03809307a10015ff9003ac3579.tar.gz gpgme-4f2d652e60700e03809307a10015ff9003ac3579.zip |
Add GPGME_PROTOCOL_SPAWN and gpgme_op_spawn.
* src/gpgme.h.in (GPGME_PROTOCOL_SPAWN): New.
(GPGME_SPAWN_DETACHED, GPGME_SPAWN_ALLOW_SET_FG): New.
* src/gpgme.c (gpgme_set_protocol): Add new protocol.
(gpgme_get_protocol_name): Ditto.
* src/spawn.c: New.
* src/libgpgme.vers, src/gpgme.def: Add new public functions.
* src/engine-spawn.c: New.
* src/Makefile.am: Add new files.
* src/engine-backend.h (struct engine_ops): Add OPSPAWN.
* src/engine.c (engine_ops): Add _gpgme_engine_ops_spawn.
(gpgme_get_engine_info): Add Spawn to the list of protocols.
(_gpgme_engine_op_spawn): New.
* src/gpgme-tool.c (gt_protocol_from_name): Add new protocol.
(gt_spawn, cmd_spawn): New.
Diffstat (limited to 'src/engine-spawn.c')
-rw-r--r-- | src/engine-spawn.c | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/src/engine-spawn.c b/src/engine-spawn.c new file mode 100644 index 00000000..1e71c1cc --- /dev/null +++ b/src/engine-spawn.c @@ -0,0 +1,467 @@ +/* engine-spawn.c - Run an arbitrary program + Copyright (C) 2014 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#if HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "gpgme.h" +#include "util.h" +#include "ops.h" +#include "wait.h" +#include "context.h" /*temp hack until we have GpmeData methods to do I/O */ +#include "priv-io.h" +#include "sema.h" +#include "debug.h" + +#include "engine-backend.h" + + +/* This type is used to build a list of data sources/sinks. */ +struct datalist_s +{ + struct datalist_s *next; + gpgme_data_t data; /* The data object. */ + int inbound; /* True if this is used for reading from the peer. */ + int dup_to; /* The fd used by the peer. */ +}; + + +struct fd_data_map_s +{ + gpgme_data_t data; + int inbound; /* True if this is used for reading from the peer. */ + int dup_to; /* Dup the fd to that one. */ + int fd; /* The fd to use. */ + int peer_fd; /* The other side of the pipe. */ + void *tag; /* Tag used by the I/O callback. */ +}; + + +struct engine_spawn +{ + struct datalist_s *arglist; + struct datalist_s **argtail; + + struct fd_data_map_s *fd_data_map; + + struct gpgme_io_cbs io_cbs; +}; +typedef struct engine_spawn *engine_spawn_t; + + +static void engspawn_io_event (void *engine, + gpgme_event_io_t type, void *type_data); +static gpgme_error_t engspawn_cancel (void *engine); + + + +static void +close_notify_handler (int fd, void *opaque) +{ + engine_spawn_t esp = opaque; + int i; + + assert (fd != -1); + + if (esp->fd_data_map) + { + for (i = 0; esp->fd_data_map[i].data; i++) + { + if (esp->fd_data_map[i].fd == fd) + { + if (esp->fd_data_map[i].tag) + (*esp->io_cbs.remove) (esp->fd_data_map[i].tag); + esp->fd_data_map[i].fd = -1; + break; + } + if (esp->fd_data_map[i].peer_fd == fd) + { + esp->fd_data_map[i].peer_fd = -1; + break; + } + } + } +} + + +static gpgme_error_t +add_data (engine_spawn_t esp, gpgme_data_t data, int dup_to, int inbound) +{ + struct datalist_s *a; + + assert (esp); + assert (data); + + a = malloc (sizeof *a - 1); + if (!a) + return gpg_error_from_syserror (); + a->next = NULL; + a->data = data; + a->inbound = inbound; + a->dup_to = dup_to; + *esp->argtail = a; + esp->argtail = &a->next; + return 0; +} + + +static void +free_fd_data_map (struct fd_data_map_s *fd_data_map) +{ + int i; + + if (!fd_data_map) + return; + + for (i = 0; fd_data_map[i].data; i++) + { + if (fd_data_map[i].fd != -1) + _gpgme_io_close (fd_data_map[i].fd); + if (fd_data_map[i].peer_fd != -1) + _gpgme_io_close (fd_data_map[i].peer_fd); + /* Don't release data because this is only a reference. */ + } + free (fd_data_map); +} + + +static gpgme_error_t +build_fd_data_map (engine_spawn_t esp) +{ + struct datalist_s *a; + size_t datac; + int fds[2]; + + for (datac = 0, a = esp->arglist; a; a = a->next) + if (a->data) + datac++; + + free_fd_data_map (esp->fd_data_map); + esp->fd_data_map = calloc (datac + 1, sizeof *esp->fd_data_map); + if (!esp->fd_data_map) + return gpg_error_from_syserror (); + + for (datac = 0, a = esp->arglist; a; a = a->next) + { + assert (a->data); + + if (_gpgme_io_pipe (fds, a->inbound ? 1 : 0) == -1) + { + free (esp->fd_data_map); + esp->fd_data_map = NULL; + return gpg_error_from_syserror (); + } + if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, esp) + || _gpgme_io_set_close_notify (fds[1], close_notify_handler, esp)) + { + /* FIXME: Need error cleanup. */ + return gpg_error (GPG_ERR_GENERAL); + } + + esp->fd_data_map[datac].inbound = a->inbound; + if (a->inbound) + { + esp->fd_data_map[datac].fd = fds[0]; + esp->fd_data_map[datac].peer_fd = fds[1]; + } + else + { + esp->fd_data_map[datac].fd = fds[1]; + esp->fd_data_map[datac].peer_fd = fds[0]; + } + esp->fd_data_map[datac].data = a->data; + esp->fd_data_map[datac].dup_to = a->dup_to; + datac++; + } + + return 0; +} + + +static gpgme_error_t +add_io_cb (engine_spawn_t esp, int fd, int dir, gpgme_io_cb_t handler, + void *data, void **tag) +{ + gpgme_error_t err; + + err = (*esp->io_cbs.add) (esp->io_cbs.add_priv, fd, dir, handler, data, tag); + if (err) + return err; + if (!dir) /* Fixme: Kludge around poll() problem. */ + err = _gpgme_io_set_nonblocking (fd); + return err; +} + + +static gpgme_error_t +engspawn_start (engine_spawn_t esp, const char *file, const char *argv[], + unsigned int flags) +{ + gpgme_error_t err; + int i, n; + int status; + struct spawn_fd_item_s *fd_list; + pid_t pid; + unsigned int spflags; + + if (!esp || !file || !argv || !argv[0]) + return gpg_error (GPG_ERR_INV_VALUE); + + spflags = 0; + if ((flags & GPGME_SPAWN_DETACHED)) + spflags |= IOSPAWN_FLAG_DETACHED; + if ((flags & GPGME_SPAWN_ALLOW_SET_FG)) + spflags |= IOSPAWN_FLAG_ALLOW_SET_FG; + + + err = build_fd_data_map (esp); + if (err) + return err; + + n = 0; + for (i = 0; esp->fd_data_map[i].data; i++) + n++; + fd_list = calloc (n, sizeof *fd_list); + if (!fd_list) + return gpg_error_from_syserror (); + + /* Build the fd list for the child. */ + n = 0; + for (i = 0; esp->fd_data_map[i].data; i++) + { + fd_list[n].fd = esp->fd_data_map[i].peer_fd; + fd_list[n].dup_to = esp->fd_data_map[i].dup_to; + n++; + } + fd_list[n].fd = -1; + fd_list[n].dup_to = -1; + + status = _gpgme_io_spawn (file, (char * const *)argv, spflags, + fd_list, NULL, NULL, &pid); + free (fd_list); + if (status == -1) + return gpg_error_from_syserror (); + + for (i = 0; esp->fd_data_map[i].data; i++) + { + err = add_io_cb (esp, esp->fd_data_map[i].fd, + esp->fd_data_map[i].inbound, + esp->fd_data_map[i].inbound + ? _gpgme_data_inbound_handler + : _gpgme_data_outbound_handler, + esp->fd_data_map[i].data, &esp->fd_data_map[i].tag); + if (err) + return err; /* FIXME: kill the child */ + } + + engspawn_io_event (esp, GPGME_EVENT_START, NULL); + + return 0; +} + + + +/* + Public functions + */ + +static const char * +engspawn_get_file_name (void) +{ + return "/nonexistent"; +} + + +static char * +engspawn_get_version (const char *file_name) +{ + (void)file_name; + return strdup ("1.0"); +} + + +static const char * +engspawn_get_req_version (void) +{ + return "1.0"; +} + + +static gpgme_error_t +engspawn_new (void **engine, const char *file_name, const char *home_dir) +{ + engine_spawn_t esp; + + (void)file_name; + (void)home_dir; + + esp = calloc (1, sizeof *esp); + if (!esp) + return gpg_error_from_syserror (); + + esp->argtail = &esp->arglist; + *engine = esp; + return 0; +} + + +static void +engspawn_release (void *engine) +{ + engine_spawn_t esp = engine; + + if (!esp) + return; + + engspawn_cancel (engine); + + while (esp->arglist) + { + struct datalist_s *next = esp->arglist->next; + + if (esp->arglist) + free (esp->arglist); + esp->arglist = next; + } + + free (esp); +} + + +static void +engspawn_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ + engine_spawn_t esp = engine; + + esp->io_cbs = *io_cbs; +} + + +static void +engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data) +{ + engine_spawn_t esp = engine; + + TRACE3 (DEBUG_ENGINE, "gpgme:engspawn_io_event", esp, + "event %p, type %d, type_data %p", + esp->io_cbs.event, type, type_data); + if (esp->io_cbs.event) + (*esp->io_cbs.event) (esp->io_cbs.event_priv, type, type_data); +} + + +static gpgme_error_t +engspawn_cancel (void *engine) +{ + engine_spawn_t esp = engine; + + if (!esp) + return gpg_error (GPG_ERR_INV_VALUE); + + if (esp->fd_data_map) + { + free_fd_data_map (esp->fd_data_map); + esp->fd_data_map = NULL; + } + + return 0; +} + + +static gpgme_error_t +engspawn_op_spawn (void *engine, + const char *file, const char *argv[], + gpgme_data_t datain, + gpgme_data_t dataout, gpgme_data_t dataerr, + unsigned int flags) +{ + engine_spawn_t esp = engine; + gpgme_error_t err = 0; + + if (datain) + err = add_data (esp, datain, 0, 0); + if (!err && dataout) + err = add_data (esp, dataout, 1, 1); + if (!err && dataerr) + err = add_data (esp, dataerr, 2, 1); + + if (!err) + err = engspawn_start (esp, file, argv, flags); + + return err; +} + + + +struct engine_ops _gpgme_engine_ops_spawn = + { + /* Static functions. */ + engspawn_get_file_name, + NULL, /* get_home_dir */ + engspawn_get_version, + engspawn_get_req_version, + engspawn_new, + + /* Member functions. */ + engspawn_release, + NULL, /* reset */ + NULL, /* set_status_handler */ + NULL, /* set_command_handler */ + NULL, /* set_colon_line_handler */ + NULL, /* set_locale */ + NULL, /* set_protocol */ + NULL, /* decrypt */ + NULL, /* decrypt_verify */ + NULL, /* delete */ + NULL, /* edit */ + NULL, /* encrypt */ + NULL, /* encrypt_sign */ + NULL, /* export */ + NULL, /* export_ext */ + NULL, /* genkey */ + NULL, /* import */ + NULL, /* keylist */ + NULL, /* keylist_ext */ + NULL, /* sign */ + NULL, /* trustlist */ + NULL, /* verify */ + NULL, /* getauditlog */ + NULL, /* opassuan_transact */ + NULL, /* conf_load */ + NULL, /* conf_save */ + engspawn_set_io_cbs, + engspawn_io_event, /* io_event */ + engspawn_cancel, /* cancel */ + NULL, /* cancel_op */ + NULL, /* passwd */ + NULL, /* set_pinentry_mode */ + engspawn_op_spawn /* opspawn */ + }; |