diff --git a/NEWS b/NEWS
index f625ab95..acba24df 100644
--- a/NEWS
+++ b/NEWS
@@ -9,9 +9,17 @@ Noteworthy changes in version 1.5.0 (unreleased)
whatever gpgconf tells as name for the OpenPGP engine. If gpgconf
is not found, GPGME looks for an engine named "gpg".
+ * Add feature to use the gpgme I/O subsystem to run arbitrary
+ commands.
+
* Interface changes relative to the 1.4.3 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gpgme_get_dirinfo NEW.
+ gpgme_op_spawn_start NEW.
+ gpgme_op_spawn NEW.
+ GPGME_PROTOCOL_SPAWN NEW.
+ GPGME_SPAWN_DETACHED NEW.
+ GPGME_SPAWN_ALLOW_SET_FG NEW.
Noteworthy changes in version 1.4.3 (2013-08-12)
diff --git a/doc/gpgme.texi b/doc/gpgme.texi
index e12fd738..027e1eff 100644
--- a/doc/gpgme.texi
+++ b/doc/gpgme.texi
@@ -113,7 +113,6 @@ Indices
* Concept Index:: Index of concepts and programs.
* Function and Data Index:: Index of functions, variables and data types.
-
@detailmenu
--- The Detailed Node Listing ---
@@ -170,6 +169,7 @@ Manipulating Data Buffers
* Data Buffer I/O Operations:: I/O operations on data buffers.
* Data Buffer Meta-Data:: Meta-data manipulation of data buffers.
+* Data Buffer Convenience:: Convenience function for data buffers.
Contexts
@@ -180,6 +180,7 @@ Contexts
* Key Management:: Managing keys with @acronym{GPGME}.
* Trust Item Management:: Managing trust items with @acronym{GPGME}.
* Crypto Operations:: Using a context for cryptography.
+* Miscellaneous:: Miscellaneous operations.
* Run Control:: Controlling how operations are run.
Context Attributes
@@ -204,6 +205,7 @@ Key Management
* Exporting Keys:: Retrieving key data from the key ring.
* Importing Keys:: Adding keys to the key ring.
* Deleting Keys:: Removing keys from the key ring.
+* Changing Passphrases:: Change the passphrase of a key.
* Advanced Key Editing:: Advanced key edit operation.
Trust Item Management
@@ -230,6 +232,10 @@ Encrypt
* Encrypting a Plaintext:: How to encrypt a plaintext.
+Miscellaneous
+
+* Running other Programs:: Running other Programs
+
Run Control
* Waiting For Completion:: Waiting until an operation is completed.
@@ -850,6 +856,9 @@ Under development. Please ask on @email{gnupg-devel@@gnupg.org} for help.
@item GPGME_PROTOCOL_UISERVER
Under development. Please ask on @email{gnupg-devel@@gnupg.org} for help.
+@item GPGME_PROTOCOL_SPAWN
+Special protocol for use with @code{gpgme_op_spawn}.
+
@item GPGME_PROTOCOL_UNKNOWN
Reserved for future extension. You may use this to indicate that the
used protocol is not known to the application. Currently,
@@ -1946,7 +1955,7 @@ be used to manipulate both.
@menu
* Data Buffer I/O Operations:: I/O operations on data buffers.
* Data Buffer Meta-Data:: Meta-data manipulation of data buffers.
-* Data Buffer Convenience:: Convenience fucntion for data buffers.
+* Data Buffer Convenience:: Convenience function for data buffers.
@end menu
@@ -2187,6 +2196,7 @@ cryptographic operations.
* Key Management:: Managing keys with @acronym{GPGME}.
* Trust Item Management:: Managing trust items with @acronym{GPGME}.
* Crypto Operations:: Using a context for cryptography.
+* Miscellaneous:: Miscellaneous operations
* Run Control:: Controlling how operations are run.
@end menu
@@ -2261,7 +2271,7 @@ started. In fact, these references are accessed through the
* Crypto Engine:: Configuring the crypto engine.
* ASCII Armor:: Requesting @acronym{ASCII} armored output.
* Text Mode:: Choosing canonical text mode.
-* Included Certificates:: Including a number of certificates.
+* Included Certificates:: Including a number of certificates.
* Key Listing Mode:: Selecting key listing mode.
* Passphrase Callback:: Getting the passphrase from the user.
* Progress Meter Callback:: Being informed about the progress.
@@ -5222,6 +5232,66 @@ pointer.
@end deftypefun
+@node Miscellaneous
+@section Miscellaneous operations
+
+Here are some support functions which are sometimes useful.
+
+@menu
+* Running other Programs:: Running other Programs
+@end menu
+
+
+@node Running other Programs
+@subsection Running other Programs
+
+GPGME features an internal subsystem to run the actual backend
+engines. Along with data abstraction object this subsystem can be
+used to run arbitrary simple programs which even need not be related
+to cryptographic features. It may for example be used to run tools
+which are part of the GnuPG system but are not directly accessible
+with the GPGME API.
+
+
+@deftypefun gpgme_error_t gpgme_op_spawn
+ (@w{gpgme_ctx_t @var{ctx}}, @w{const char *@var{file}}, @
+ @w{const char *@var{argv}[]}, @w{gpgme_data_t @var{datain}}, @
+ @w{gpgme_data_t @var{dataout}}, @w{gpgme_data_t @var{dataerr}}, @
+ @w{unsigned int @var{flags}})
+
+The function @code{gpgme_op_spawn} runs the program @var{file} with
+the arguments taken from the NULL terminated array @var{argv}. If no
+arguments are required @var{argv} may be given as @code{NULL} (in that
+case GPGME uses the basename of @var{file} for @code{argv[0]}). The
+file descriptors @code{stdin}, @code{stdout}, and @code{stderr} are
+connected to the data objects @var{datain}, @var{dataout}, and
+@var{dataerr}. If NULL is passed for one of these data objects the
+corresponding file descriptor is connected to @file{/dev/null}.
+
+The value in @var{flags} is a bitwise-or combination of one or
+multiple of the following bit values:
+
+@table @code
+@item GPGME_SPAWN_DETACHED
+Under Windows this flag inhibits the allocation of a new console for
+the program. This is useful for a GUI application which needs to call
+a command line helper tool.
+@item GPGME_SPAWN_ALLOW_SET_FG
+Under Windows this flag allows the called program to put itself into
+the foreground.
+@end table
+@end deftypefun
+
+@deftypefun gpgme_error_t gpgme_op_spawn_start
+ (@w{gpgme_ctx_t @var{ctx}}, @w{const char *@var{file}}, @
+ @w{const char *@var{argv}[]}, @w{gpgme_data_t @var{datain}}, @
+ @w{gpgme_data_t @var{dataout}}, @w{gpgme_data_t @var{dataerr}}, @
+ @w{unsigned int @var{flags}})
+
+This is the asynchronous variant of @code{gpgme_op_spawn}.
+@end deftypefun
+
+
@node Run Control
@section Run Control
@cindex run control
diff --git a/src/Makefile.am b/src/Makefile.am
index abc014c7..82f5327a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -89,11 +89,12 @@ main_sources = \
sign.c passphrase.c progress.c \
key.c keylist.c trust-item.c trustlist.c \
import.c export.c genkey.c delete.c edit.c getauditlog.c \
- opassuan.c passwd.c assuan-support.c \
+ opassuan.c passwd.c spawn.c assuan-support.c \
engine.h engine-backend.h engine.c engine-gpg.c status-table.c \
engine-gpgsm.c engine-assuan.c engine-gpgconf.c \
$(uiserver_components) \
engine-g13.c vfs-mount.c vfs-create.c \
+ engine-spawn.c \
gpgconf.c \
sema.h priv-io.h $(system_components) sys-util.h dirinfo.c \
debug.c debug.h gpgme.c version.c error.c
diff --git a/src/engine-assuan.c b/src/engine-assuan.c
index 5ef30479..663b2eab 100644
--- a/src/engine-assuan.c
+++ b/src/engine-assuan.c
@@ -783,5 +783,6 @@ struct engine_ops _gpgme_engine_ops_assuan =
llass_cancel,
llass_cancel_op,
NULL, /* passwd */
- NULL /* set_pinentry_mode */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};
diff --git a/src/engine-backend.h b/src/engine-backend.h
index 7e6c0c12..dbb9e932 100644
--- a/src/engine-backend.h
+++ b/src/engine-backend.h
@@ -124,6 +124,14 @@ struct engine_ops
/* Set the pinentry mode. */
gpgme_error_t (*set_pinentry_mode) (void *engine, gpgme_pinentry_mode_t mode);
+
+ /* The spawn command. */
+ gpgme_error_t (*opspawn) (void * engine,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout,
+ gpgme_data_t dataerr);
+
};
@@ -135,6 +143,7 @@ extern struct engine_ops _gpgme_engine_ops_g13; /* Crypto VFS. */
#ifdef ENABLE_UISERVER
extern struct engine_ops _gpgme_engine_ops_uiserver;
#endif
+extern struct engine_ops _gpgme_engine_ops_spawn; /* Spawn engine. */
/* Prototypes for extra functions in engine-gpgconf.c */
diff --git a/src/engine-g13.c b/src/engine-g13.c
index 75154ca0..a9717eec 100644
--- a/src/engine-g13.c
+++ b/src/engine-g13.c
@@ -799,5 +799,6 @@ struct engine_ops _gpgme_engine_ops_g13 =
g13_cancel,
g13_cancel_op,
NULL, /* passwd */
- NULL /* set_pinentry_mode */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};
diff --git a/src/engine-gpg.c b/src/engine-gpg.c
index 9037dd7c..a8eab3a4 100644
--- a/src/engine-gpg.c
+++ b/src/engine-gpg.c
@@ -2445,5 +2445,6 @@ struct engine_ops _gpgme_engine_ops_gpg =
gpg_cancel,
NULL, /* cancel_op */
gpg_passwd,
- gpg_set_pinentry_mode
+ gpg_set_pinentry_mode,
+ NULL /* opspawn */
};
diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c
index 811ad9d7..a2407ac7 100644
--- a/src/engine-gpgconf.c
+++ b/src/engine-gpgconf.c
@@ -963,5 +963,6 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
NULL, /* cancel */
NULL, /* cancel_op */
NULL, /* passwd */
- NULL /* set_pinentry_mode */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};
diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c
index 6bcc0952..710bf14a 100644
--- a/src/engine-gpgsm.c
+++ b/src/engine-gpgsm.c
@@ -1988,5 +1988,6 @@ struct engine_ops _gpgme_engine_ops_gpgsm =
gpgsm_cancel,
NULL, /* cancel_op */
gpgsm_passwd,
- NULL /* set_pinentry_mode */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};
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 .
+*/
+
+#if HAVE_CONFIG_H
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#ifdef HAVE_UNISTD_H
+# include
+#endif
+#ifdef HAVE_LOCALE_H
+#include
+#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 */
+ };
diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c
index bd140f90..2738c366 100644
--- a/src/engine-uiserver.c
+++ b/src/engine-uiserver.c
@@ -1340,5 +1340,6 @@ struct engine_ops _gpgme_engine_ops_uiserver =
uiserver_cancel,
NULL, /* cancel_op */
NULL, /* passwd */
- NULL /* set_pinentry_mode */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};
diff --git a/src/engine.c b/src/engine.c
index 4f2000c2..f5034306 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -51,10 +51,11 @@ static struct engine_ops *engine_ops[] =
&_gpgme_engine_ops_assuan, /* Low-Level Assuan. */
&_gpgme_engine_ops_g13, /* Crypto VFS. */
#ifdef ENABLE_UISERVER
- &_gpgme_engine_ops_uiserver /* UI-Server. */
+ &_gpgme_engine_ops_uiserver, /* UI-Server. */
#else
- NULL
+ NULL,
#endif
+ &_gpgme_engine_ops_spawn
};
@@ -193,7 +194,8 @@ gpgme_get_engine_info (gpgme_engine_info_t *info)
GPGME_PROTOCOL_GPGCONF,
GPGME_PROTOCOL_ASSUAN,
GPGME_PROTOCOL_G13,
- GPGME_PROTOCOL_UISERVER };
+ GPGME_PROTOCOL_UISERVER,
+ GPGME_PROTOCOL_SPAWN };
unsigned int proto;
err = 0;
@@ -936,3 +938,20 @@ _gpgme_engine_set_pinentry_mode (engine_t engine, gpgme_pinentry_mode_t mode)
return (*engine->ops->set_pinentry_mode) (engine->engine, mode);
}
+
+
+gpgme_error_t
+_gpgme_engine_op_spawn (engine_t engine,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr)
+{
+ if (!engine)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ if (!engine->ops->opspawn)
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ return (*engine->ops->opspawn) (engine->engine, file, argv,
+ datain, dataout, dataerr);
+}
diff --git a/src/engine.h b/src/engine.h
index a0287add..ade7de15 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -163,5 +163,11 @@ gpgme_error_t _gpgme_engine_op_passwd (engine_t engine, gpgme_key_t key,
gpgme_error_t _gpgme_engine_set_pinentry_mode (engine_t engine,
gpgme_pinentry_mode_t mode);
+gpgme_error_t _gpgme_engine_op_spawn (engine_t engine,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout,
+ gpgme_data_t dataerr);
+
#endif /* ENGINE_H */
diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c
index 2bf7654a..a980347c 100644
--- a/src/gpgme-tool.c
+++ b/src/gpgme-tool.c
@@ -1742,6 +1742,8 @@ gt_protocol_from_name (const char *name)
return GPGME_PROTOCOL_G13;
if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_UISERVER)))
return GPGME_PROTOCOL_UISERVER;
+ if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_SPAWN)))
+ return GPGME_PROTOCOL_SPAWN;
if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_DEFAULT)))
return GPGME_PROTOCOL_DEFAULT;
return GPGME_PROTOCOL_UNKNOWN;
@@ -2106,6 +2108,18 @@ gt_identify (gpgme_tool_t gt, gpgme_data_t data)
}
+gpg_error_t
+gt_spawn (gpgme_tool_t gt, const char *pgm,
+ gpgme_data_t inp, gpgme_data_t outp)
+{
+ gpg_error_t err;
+
+ err = gpgme_op_spawn (gt->ctx, pgm, NULL, inp, outp, outp, 0);
+
+ return err;
+}
+
+
#define GT_RESULT_ENCRYPT 0x1
#define GT_RESULT_DECRYPT 0x2
#define GT_RESULT_SIGN 0x4
@@ -3487,6 +3501,55 @@ cmd_identify (assuan_context_t ctx, char *line)
}
+static const char hlp_spawn[] =
+ "SPAWN PGM [args]\n"
+ "\n"
+ "Run program PGM with stdin connected to the INPUT source;\n"
+ "stdout and stderr to the OUTPUT source.";
+static gpg_error_t
+cmd_spawn (assuan_context_t ctx, char *line)
+{
+ struct server *server = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ assuan_fd_t inp_fd;
+ char *inp_fn;
+ assuan_fd_t out_fd;
+ char *out_fn;
+ gpgme_data_t inp_data = NULL;
+ gpgme_data_t out_data = NULL;
+
+ inp_fd = server->input_fd;
+ inp_fn = server->input_filename;
+ out_fd = server->output_fd;
+ out_fn = server->output_filename;
+ if (inp_fd != ASSUAN_INVALID_FD || inp_fn)
+ {
+ err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+ &server->input_stream);
+ if (err)
+ return err;
+ }
+ if (out_fd != ASSUAN_INVALID_FD || out_fn)
+ {
+ err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+ &server->output_stream);
+ if (err)
+ {
+ gpgme_data_release (inp_data);
+ return err;
+ }
+ }
+
+ err = gt_spawn (server->gt, line, inp_data, out_data);
+
+ gpgme_data_release (inp_data);
+ gpgme_data_release (out_data);
+
+ server_reset_fds (server);
+
+ return err;
+}
+
/* Tell the assuan library about our commands. */
static gpg_error_t
@@ -3547,6 +3610,7 @@ register_commands (assuan_context_t ctx)
{ "HASH_ALGO_NAME", cmd_hash_algo_name },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "IDENTIFY", cmd_identify, hlp_identify },
+ { "SPAWN", cmd_spawn, hlp_spawn },
{ NULL }
};
int idx;
diff --git a/src/gpgme.c b/src/gpgme.c
index 438fef21..24b04fc8 100644
--- a/src/gpgme.c
+++ b/src/gpgme.c
@@ -1,6 +1,7 @@
/* gpgme.c - GnuPG Made Easy.
Copyright (C) 2000 Werner Koch (dd9jn)
- Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012 g10 Code GmbH
+ Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012,
+ 2014 g10 Code GmbH
This file is part of GPGME.
@@ -15,9 +16,8 @@
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, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- 02111-1307, USA. */
+ License along with this program; if not, see .
+ */
#if HAVE_CONFIG_H
#include
@@ -321,7 +321,8 @@ gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
&& protocol != GPGME_PROTOCOL_GPGCONF
&& protocol != GPGME_PROTOCOL_ASSUAN
&& protocol != GPGME_PROTOCOL_G13
- && protocol != GPGME_PROTOCOL_UISERVER)
+ && protocol != GPGME_PROTOCOL_UISERVER
+ && protocol != GPGME_PROTOCOL_SPAWN)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
if (!ctx)
@@ -405,6 +406,9 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol)
case GPGME_PROTOCOL_UISERVER:
return "UIServer";
+ case GPGME_PROTOCOL_SPAWN:
+ return "Spawn";
+
case GPGME_PROTOCOL_DEFAULT:
return "default";
diff --git a/src/gpgme.def b/src/gpgme.def
index ee0c42e2..dc189484 100644
--- a/src/gpgme.def
+++ b/src/gpgme.def
@@ -215,5 +215,7 @@ EXPORTS
gpgme_get_dirinfo @162
+ gpgme_op_spawn_start @163
+ gpgme_op_spawn @164
; END
diff --git a/src/gpgme.h.in b/src/gpgme.h.in
index 050051a4..655602df 100644
--- a/src/gpgme.h.in
+++ b/src/gpgme.h.in
@@ -1,7 +1,7 @@
/* gpgme.h - Public interface to GnuPG Made Easy. -*- c -*-
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009
- 2010, 2011, 2012, 2013 g10 Code GmbH
+ 2010, 2011, 2012, 2013, 2014 g10 Code GmbH
This file is part of GPGME.
@@ -354,6 +354,7 @@ typedef enum
GPGME_PROTOCOL_ASSUAN = 3, /* Low-level access to an Assuan server. */
GPGME_PROTOCOL_G13 = 4,
GPGME_PROTOCOL_UISERVER= 5,
+ GPGME_PROTOCOL_SPAWN = 6, /* Direct access to any program. */
GPGME_PROTOCOL_DEFAULT = 254,
GPGME_PROTOCOL_UNKNOWN = 255
}
@@ -1694,6 +1695,26 @@ gpgme_error_t gpgme_op_card_edit (gpgme_ctx_t ctx, gpgme_key_t key,
gpgme_edit_cb_t fnc, void *fnc_value,
gpgme_data_t out);
+
+/* Flags for the spawn operations. */
+#define GPGME_SPAWN_DETACHED 1
+#define GPGME_SPAWN_ALLOW_SET_FG 2
+
+
+/* Run the command FILE with the arguments in ARGV. Connect stdin to
+ DATAIN, stdout to DATAOUT, and STDERR to DATAERR. If one the data
+ streams is NULL, connect to /dev/null instead. */
+gpgme_error_t gpgme_op_spawn_start (gpgme_ctx_t ctx,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr,
+ unsigned int flags);
+gpgme_error_t gpgme_op_spawn (gpgme_ctx_t ctx,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr,
+ unsigned int flags);
+
/* Key management functions. */
struct _gpgme_op_keylist_result
diff --git a/src/libgpgme.vers b/src/libgpgme.vers
index 4db1d408..39663c1c 100644
--- a/src/libgpgme.vers
+++ b/src/libgpgme.vers
@@ -89,6 +89,9 @@ GPGME_1.1 {
gpgme_get_pinentry_mode;
gpgme_get_dirinfo;
+
+ gpgme_op_spawn_start;
+ gpgme_op_spawn;
};
diff --git a/src/spawn.c b/src/spawn.c
new file mode 100644
index 00000000..e3454f3b
--- /dev/null
+++ b/src/spawn.c
@@ -0,0 +1,105 @@
+/* spawn.c - Run an arbitrary command with callbacks.
+ Copyright (C) 2014 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA. */
+
+#if HAVE_CONFIG_H
+#include
+#endif
+#include
+
+#include "gpgme.h"
+#include "debug.h"
+#include "context.h"
+#include "util.h"
+#include "ops.h"
+
+
+static gpgme_error_t
+spawn_start (gpgme_ctx_t ctx, int synchronous,
+ const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr)
+{
+ gpgme_error_t err;
+ const char *tmp_argv[2];
+
+ if (ctx->protocol != GPGME_PROTOCOL_SPAWN)
+ return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+
+ err = _gpgme_op_reset (ctx, synchronous);
+ if (err)
+ return err;
+
+ if (!argv)
+ {
+ tmp_argv[0] = _gpgme_get_basename (file);
+ tmp_argv[1] = NULL;
+ argv = tmp_argv;
+ }
+
+ return _gpgme_engine_op_spawn (ctx->engine, file, argv,
+ datain, dataout, dataerr);
+}
+
+
+/* Run the command FILE with the arguments in ARGV. Connect stdin to
+ DATAIN, stdout to DATAOUT, and STDERR to DATAERR. If one the data
+ streams is NULL, connect to /dev/null instead. */
+gpgme_error_t
+gpgme_op_spawn_start (gpgme_ctx_t ctx, const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr,
+ unsigned int flags)
+{
+ gpgme_error_t err;
+
+ TRACE_BEG2 (DEBUG_CTX, "gpgme_op_spawn_start", ctx, "file=(%s) flaggs=%x",
+ file, flags);
+
+ if (!ctx)
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+ err = spawn_start (ctx, 0, file, argv, datain, dataout, dataerr);
+ return err;
+}
+
+
+/* Run the command FILE with the arguments in ARGV. Connect stdin to
+ DATAIN, stdout to DATAOUT, and STDERR to DATAERR. If one the data
+ streams is NULL, connect to /dev/null instead. Synchronous
+ variant. */
+gpgme_error_t
+gpgme_op_spawn (gpgme_ctx_t ctx, const char *file, const char *argv[],
+ gpgme_data_t datain,
+ gpgme_data_t dataout, gpgme_data_t dataerr,
+ unsigned int flags)
+{
+ gpgme_error_t err;
+
+ TRACE_BEG2 (DEBUG_CTX, "gpgme_op_spawn", ctx, "file=(%s) flags=%x",
+ file, flags);
+ if (!ctx)
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+ err = spawn_start (ctx, 1, file, argv, datain, dataout, dataerr);
+
+ if (!err)
+ err = _gpgme_wait_one (ctx);
+ return TRACE_ERR (err);
+}