[gnome-keyring: 1/3] gcr: Refactor gnupg process execution into its own class.



commit 9a9fa2d72cc7ee1f661b101c522ae23c12a15305
Author: Stef Walter <stefw collabora co uk>
Date:   Thu May 12 16:13:33 2011 +0200

    gcr: Refactor gnupg process execution into its own class.
    
     * Support getting user attribute data from keyring.
     * Support getting status messages from gpg.
     * Better cancellation api.
     * Control over LOCALE
    
    https://bugzilla.gnome.org/show_bug.cgi?id=650336

 .gitignore                                         |    1 +
 docs/reference/gcr/gcr-sections.txt                |   12 +-
 gcr/Makefile.am                                    |    1 +
 gcr/gcr-gnupg-collection.c                         |  156 ++---
 gcr/gcr-gnupg-collection.h                         |   10 +-
 gcr/gcr-gnupg-process.c                            |  866 ++++++++++++++++++++
 gcr/gcr-gnupg-process.h                            |   88 ++
 gcr/gcr-marshal.list                               |    2 +
 gcr/tests/Makefile.am                              |    3 +-
 gcr/tests/files/gnupg-mock/mock-arguments-environ  |   22 +
 gcr/tests/files/gnupg-mock/mock-fail-exit          |    6 +
 gcr/tests/files/gnupg-mock/mock-fail-signal        |    8 +
 gcr/tests/files/gnupg-mock/mock-simple-error       |    9 +
 gcr/tests/files/gnupg-mock/mock-simple-output      |    8 +
 .../files/gnupg-mock/mock-status-and-attribute     |   34 +
 gcr/tests/files/gnupg-mock/mock-status-and-output  |   24 +
 gcr/tests/files/gnupg-mock/mock-with-homedir       |   22 +
 gcr/tests/test-gnupg-collection.c                  |    1 -
 gcr/tests/test-gnupg-process.c                     |  446 ++++++++++
 19 files changed, 1609 insertions(+), 110 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 124bc13..fac973e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,6 +125,7 @@ run-tests
 /gcr/tests/test-fingerprint
 /gcr/tests/test-gnupg-collection
 /gcr/tests/test-gnupg-key
+/gcr/tests/test-gnupg-process
 /gcr/tests/test-parser
 /gcr/tests/test-pkcs11-certificate
 /gcr/tests/test-record
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 9028b4b..e8df765 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -475,6 +475,16 @@ GcrRecordPubColumns
 GcrRecordUidColumns
 GcrRecordSecColumns
 GcrRecord
+GCR_GNUPG_PROCESS
+GCR_GNUPG_PROCESS_CLASS
+GCR_GNUPG_PROCESS_GET_CLASS
+GCR_IS_GNUPG_PROCESS
+GCR_IS_GNUPG_PROCESS_CLASS
+GCR_TYPE_GNUPG_PROCESS
+GcrGnupgProcess
+GcrGnupgProcessClass
+GcrGnupgProcessPrivate
+GcrGnupgProcessFlags
 GcrGnupgCollection
 GcrGnupgCollectionClass
 GcrGnupgCollectionPrivate
@@ -482,4 +492,4 @@ GcrGnupgKey
 GcrGnupgKeyClass
 GcrGnupgKeyPrivate
 GcrLineCallback
-</SECTION>
\ No newline at end of file
+</SECTION>
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index 921f9ae..5a5270c 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -90,6 +90,7 @@ libgcr_ GCR_MAJOR@_la_SOURCES = \
 	gcr-gnupg-collection.c gcr-gnupg-collection.h \
 	gcr-gnupg-key.c gcr-gnupg-key.h \
 	gcr-fingerprint.c gcr-fingerprint.h \
+	gcr-gnupg-process.c gcr-gnupg-process.h \
 	gcr-icons.c gcr-icons.h \
 	gcr-import-dialog.c gcr-import-dialog.h \
 	gcr-importer.c gcr-importer.h  \
diff --git a/gcr/gcr-gnupg-collection.c b/gcr/gcr-gnupg-collection.c
index 4e3da44..44abe93 100644
--- a/gcr/gcr-gnupg-collection.c
+++ b/gcr/gcr-gnupg-collection.c
@@ -29,11 +29,10 @@
 #include "gcr-debug.h"
 #include "gcr-gnupg-collection.h"
 #include "gcr-gnupg-key.h"
+#include "gcr-gnupg-process.h"
 #include "gcr-internal.h"
 #include "gcr-util.h"
 
-#include "egg/egg-spawn.h"
-
 #include <sys/wait.h>
 #include <string.h>
 
@@ -215,10 +214,15 @@ typedef struct {
 	GcrGnupgCollection *collection;       /* reffed pointer back to collection */
 	GcrLoadingPhase loading_phase;        /* Whether loading public or private */
 	GPtrArray *records;                   /* GcrRecord* not yet made into a key */
-	guint spawn_sig;
+	GcrGnupgProcess *process;             /* The gnupg process itself */
+	GCancellable *cancel;                 /* Cancellation for process */
 	GString *out_data;                    /* Pending output not yet parsed into colons */
-	GString *err_data;                    /* Pending errors not yet printed */
 	GHashTable *difference;               /* Hashset gchar *keyid -> gchar *keyid */
+
+	guint output_sig;
+	guint error_sig;
+	guint status_sig;
+	guint attribute_sig;
 } GcrGnupgCollectionLoad;
 
 /* Forward declarations */
@@ -231,13 +235,22 @@ _gcr_gnupg_collection_load_free (gpointer data)
 	g_assert (load);
 
 	g_ptr_array_unref (load->records);
-	g_string_free (load->err_data, TRUE);
 	g_string_free (load->out_data, TRUE);
 	g_hash_table_destroy (load->difference);
 	g_object_unref (load->collection);
 
-	if (load->spawn_sig)
-		g_source_remove (load->spawn_sig);
+	if (load->process)
+		g_object_unref (load->process);
+	if (load->output_sig)
+		g_source_remove (load->output_sig);
+	if (load->error_sig)
+		g_source_remove (load->error_sig);
+	if (load->status_sig)
+		g_source_remove (load->status_sig);
+	if (load->attribute_sig)
+		g_source_remove (load->attribute_sig);
+	if (load->cancel)
+		g_object_unref (load->cancel);
 	g_slice_free (GcrGnupgCollectionLoad, load);
 }
 
@@ -363,67 +376,41 @@ on_line_parse_output (const gchar *line, gpointer user_data)
 }
 
 
-static gboolean
-on_spawn_standard_output (int fd, gpointer user_data)
+static void
+on_gnupg_process_output_data (GcrGnupgProcess *process, GByteArray *buffer,
+                              gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
 	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
-	gchar buffer[1024];
-	gssize ret;
-
-	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
-	if (ret < 0) {
-		g_warning ("couldn't read output data from prompt process");
-		return FALSE;
-	}
 
-	g_string_append_len (load->out_data, buffer, ret);
+	g_string_append_len (load->out_data, (gchar*)buffer->data, buffer->len);
 	_gcr_util_parse_lines (load->out_data, FALSE, on_line_parse_output, load);
-
-	return (ret > 0);
 }
 
 static void
-on_line_print_error (const gchar *line, gpointer unused)
+on_gnupg_process_error_line (GcrGnupgProcess *process, const gchar *line,
+                             gpointer user_data)
 {
 	g_printerr ("%s\n", line);
 }
 
-static gboolean
-on_spawn_standard_error (int fd, gpointer user_data)
-{
-	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
-	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
-	gchar buffer[1024];
-	gssize ret;
-
-	ret = egg_spawn_read_output (fd, buffer, sizeof (buffer));
-	if (ret < 0) {
-		g_warning ("couldn't read error data from prompt process");
-		return FALSE;
-	}
-
-	g_string_append_len (load->err_data, buffer, ret);
-	_gcr_util_parse_lines (load->err_data, FALSE, on_line_print_error, NULL);
-
-	return ret > 0;
-}
-
 static void
-on_spawn_completed (gpointer user_data)
+on_gnupg_process_completed (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
 	GcrGnupgCollectionLoad *load = g_simple_async_result_get_op_res_gpointer (res);
 	GHashTableIter iter;
+	GError *error = NULL;
 	GObject *object;
 	gpointer keyid;
 
-	/* Should be the last call we receive */
-	g_assert (load->spawn_sig != 0);
-	load->spawn_sig = 0;
-
-	/* Print out any remaining errors */
-	_gcr_util_parse_lines (load->err_data, TRUE, on_line_print_error, NULL);
+	if (!_gcr_gnupg_process_run_finish (GCR_GNUPG_PROCESS (source), result, &error)) {
+		g_simple_async_result_set_from_error (res, error);
+		g_simple_async_result_complete (res);
+		g_object_unref (res);
+		g_clear_error (&error);
+		return;
+	}
 
 	/* Process any remaining output */
 	_gcr_util_parse_lines (load->out_data, TRUE, on_line_parse_output, load);
@@ -438,6 +425,7 @@ on_spawn_completed (gpointer user_data)
 		_gcr_debug ("public load phase completed");
 		load->loading_phase = GCR_LOADING_PHASE_SECRET;
 		spawn_gnupg_list_process (load, res);
+		g_object_unref (res);
 		return;
 	case GCR_LOADING_PHASE_SECRET:
 		_gcr_debug ("secret load phase completed");
@@ -461,50 +449,24 @@ on_spawn_completed (gpointer user_data)
 	}
 
 	g_simple_async_result_complete (res);
+	g_object_unref (res);
 }
 
 static void
-on_child_exited (GPid pid, gint status, gpointer unused)
-{
-	gint code;
-
-	_gcr_debug ("process exited: %d", (gint)pid);
-	g_spawn_close_pid (pid);
-
-	if (WIFEXITED (status)) {
-		code = WEXITSTATUS (status);
-		if (code != 0) {
-			g_message ("gnupg process exited with failure code: %d", code);
-		} else if (WIFSIGNALED (status)) {
-			code = WTERMSIG (status);
-			g_message ("gnupg process was killed with signal: %d", code);
-		}
-	}
-}
-
-static EggSpawnCallbacks spawn_callbacks = {
-	NULL,
-	on_spawn_standard_output,
-	on_spawn_standard_error,
-	on_spawn_completed,
-	g_object_unref,
-	NULL
-};
-
-static void
 spawn_gnupg_list_process (GcrGnupgCollectionLoad *load, GSimpleAsyncResult *res)
 {
-	GError *error = NULL;
+	GcrGnupgProcessFlags flags = 0;
 	GPtrArray *argv;
-	GPid pid;
 
 	argv = g_ptr_array_new ();
-	g_ptr_array_add (argv, (gpointer)GPG_EXECUTABLE);
 
 	switch (load->loading_phase) {
 	case GCR_LOADING_PHASE_PUBLIC:
 		_gcr_debug ("starting public load phase");
 		g_ptr_array_add (argv, (gpointer)"--list-keys");
+		/* Load photos in public phase */
+		flags = GCR_GNUPG_PROCESS_WITH_ATTRIBUTES |
+		        GCR_GNUPG_PROCESS_WITH_STATUS;
 		break;
 	case GCR_LOADING_PHASE_SECRET:
 		_gcr_debug ("starting secret load phase");
@@ -516,36 +478,18 @@ spawn_gnupg_list_process (GcrGnupgCollectionLoad *load, GSimpleAsyncResult *res)
 
 	g_ptr_array_add (argv, (gpointer)"--fixed-list-mode");
 	g_ptr_array_add (argv, (gpointer)"--with-colons");
-	if (load->collection->pv->directory) {
-		g_ptr_array_add (argv, (gpointer)"--homedir");
-		g_ptr_array_add (argv, (gpointer)load->collection->pv->directory);
-	}
 	g_ptr_array_add (argv, NULL);
 
-	g_assert (!load->spawn_sig);
-
 	if (_gcr_debugging) {
 		gchar *command = g_strjoinv (" ", (gchar**)argv->pdata);
 		_gcr_debug ("spawning gnupg process: %s", command);
 		g_free (command);
 	}
 
-	load->spawn_sig = egg_spawn_async_with_callbacks (load->collection->pv->directory,
-	                                                  (gchar**)argv->pdata, NULL,
-	                                                  G_SPAWN_DO_NOT_REAP_CHILD,
-	                                                  &pid, &spawn_callbacks,
-	                                                  g_object_ref (res),
-	                                                  NULL, &error);
-
-	if (error) {
-		_gcr_debug ("spawning process failed: %s", error->message);
-		g_simple_async_result_set_from_error (res, error);
-		g_simple_async_result_complete_in_idle (res);
-		g_clear_error (&error);
-	} else {
-		_gcr_debug ("process spawned: %d", (gint)pid);
-		g_child_watch_add (pid, on_child_exited, NULL);
-	}
+	/* res is unreffed in on_gnupg_process_completed */
+	_gcr_gnupg_process_run_async (load->process, (const gchar**)argv->pdata, NULL, flags,
+	                              load->cancel, on_gnupg_process_completed,
+	                              g_object_ref (res));
 
 	g_ptr_array_unref (argv);
 }
@@ -578,9 +522,17 @@ _gcr_gnupg_collection_load_async (GcrGnupgCollection *self, GCancellable *cancel
 
 	load = g_slice_new0 (GcrGnupgCollectionLoad);
 	load->records = g_ptr_array_new_with_free_func (_gcr_record_free);
-	load->err_data = g_string_sized_new (128);
 	load->out_data = g_string_sized_new (1024);
 	load->collection = g_object_ref (self);
+	load->cancel = cancellable ? g_object_ref (cancellable) : cancellable;
+
+	load->process = _gcr_gnupg_process_new (self->pv->directory, NULL);
+	load->output_sig = g_signal_connect (load->process, "output-data", G_CALLBACK (on_gnupg_process_output_data), res);
+	load->error_sig = g_signal_connect (load->process, "error-line", G_CALLBACK (on_gnupg_process_error_line), res);
+#if 0
+	load->status_sig = g_signal_connect (load->process, "status-message", G_CALLBACK (on_gnupg_process_status_message), res);
+	load->attribute_sig = g_signal_connect (load->process, "attribute-data", G_CALLBACK (on_gnupg_process_attribute_data), res);
+#endif
 
 	/*
 	 * Track all the keys we currently have, at end remove those that
diff --git a/gcr/gcr-gnupg-collection.h b/gcr/gcr-gnupg-collection.h
index b2a5d25..1a457e7 100644
--- a/gcr/gcr-gnupg-collection.h
+++ b/gcr/gcr-gnupg-collection.h
@@ -32,11 +32,11 @@
 G_BEGIN_DECLS
 
 #define GCR_TYPE_GNUPG_COLLECTION               (_gcr_gnupg_collection_get_type ())
-#define GCR_GNUPG_COLLECTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollection))
-#define GCR_GNUPG_COLLECTION_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
-#define GCR_IS_GNUPG_COLLECTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_COLLECTION))
-#define GCR_IS_GNUPG_COLLECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_COLLECTION))
-#define GCR_GNUPG_COLLECTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_COLLECTION, GcrGnupgCollectionClass))
+#define GCR_GNUPG_COLLECTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_GNUPG_COLLECTION, GcrGnupgCollection))
+#define GCR_GNUPG_COLLECTION_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_GNUPG_COLLECTION, GcrGnupgCollectionClass))
+#define GCR_IS_GNUPG_COLLECTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_GNUPG_COLLECTION))
+#define GCR_IS_GNUPG_COLLECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_GNUPG_COLLECTION))
+#define GCR_GNUPG_COLLECTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_GNUPG_COLLECTION, GcrGnupgCollectionClass))
 
 typedef struct _GcrGnupgCollection GcrGnupgCollection;
 typedef struct _GcrGnupgCollectionClass GcrGnupgCollectionClass;
diff --git a/gcr/gcr-gnupg-process.c b/gcr/gcr-gnupg-process.c
new file mode 100644
index 0000000..e1c7a00
--- /dev/null
+++ b/gcr/gcr-gnupg-process.c
@@ -0,0 +1,866 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program 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.
+ *
+ * This program 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.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#define DEBUG_FLAG GCR_DEBUG_GNUPG
+#include "gcr-debug.h"
+#include "gcr-gnupg-process.h"
+#include "gcr-marshal.h"
+#include "gcr-util.h"
+
+#include "egg/egg-spawn.h"
+
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+enum {
+	PROP_0,
+	PROP_DIRECTORY,
+	PROP_EXECUTABLE
+};
+
+enum {
+	FD_INPUT,
+	FD_OUTPUT,
+	FD_ERROR,
+	FD_STATUS,
+	FD_ATTRIBUTE,
+	NUM_FDS
+};
+
+enum {
+	OUTPUT_DATA,
+	ERROR_LINE,
+	STATUS_RECORD,
+	ATTRIBUTE_DATA,
+	NUM_SIGNALS
+};
+
+static gint signals[NUM_SIGNALS] = { 0, };
+
+typedef struct _GnupgSource {
+	GSource source;
+	GPollFD polls[NUM_FDS];         /* The various fd's we're listening to */
+
+	GcrGnupgProcess *process;       /* Pointer back to the process object */
+
+	GString *error_buf;
+	GString *status_buf;
+
+	GPid child_pid;
+	guint child_sig;
+
+	guint cancel_sig;
+} GnupgSource;
+
+struct _GcrGnupgProcessPrivate {
+	gchar *directory;
+	gchar *executable;
+
+	gboolean running;
+	gboolean complete;
+	GError *error;
+
+	guint source_sig;
+
+	GAsyncReadyCallback async_callback;
+	gpointer user_data;
+};
+
+/* Forward declarations */
+static void _gcr_gnupg_process_init_async (GAsyncResultIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GcrGnupgProcess, _gcr_gnupg_process, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, _gcr_gnupg_process_init_async));
+
+static void
+_gcr_gnupg_process_init (GcrGnupgProcess *self)
+{
+	self->pv = G_TYPE_INSTANCE_GET_PRIVATE (self, GCR_TYPE_GNUPG_PROCESS,
+	                                        GcrGnupgProcessPrivate);
+}
+
+static void
+_gcr_gnupg_process_constructed (GObject *obj)
+{
+	GcrGnupgProcess *self = GCR_GNUPG_PROCESS (obj);
+
+	if (G_OBJECT_CLASS (_gcr_gnupg_process_parent_class)->constructed)
+		G_OBJECT_CLASS (_gcr_gnupg_process_parent_class)->constructed (obj);
+
+	if (!self->pv->executable)
+		self->pv->executable = g_strdup (GPG_EXECUTABLE);
+}
+
+static void
+_gcr_gnupg_process_get_property (GObject *obj, guint prop_id, GValue *value,
+                                 GParamSpec *pspec)
+{
+	GcrGnupgProcess *self = GCR_GNUPG_PROCESS (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_value_set_string (value, self->pv->directory);
+		break;
+	case PROP_EXECUTABLE:
+		g_value_set_string (value, self->pv->executable);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_process_set_property (GObject *obj, guint prop_id, const GValue *value,
+                                 GParamSpec *pspec)
+{
+	GcrGnupgProcess *self = GCR_GNUPG_PROCESS (obj);
+
+	switch (prop_id) {
+	case PROP_DIRECTORY:
+		g_return_if_fail (!self->pv->directory);
+		self->pv->directory = g_value_dup_string (value);
+		break;
+	case PROP_EXECUTABLE:
+		g_return_if_fail (!self->pv->executable);
+		self->pv->executable = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+_gcr_gnupg_process_finalize (GObject *obj)
+{
+	GcrGnupgProcess *self = GCR_GNUPG_PROCESS (obj);
+
+	g_assert (!self->pv->running);
+	g_free (self->pv->directory);
+	g_free (self->pv->executable);
+	g_clear_error (&self->pv->error);
+
+	G_OBJECT_CLASS (_gcr_gnupg_process_parent_class)->finalize (obj);
+}
+
+static void
+_gcr_gnupg_process_class_init (GcrGnupgProcessClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	gobject_class->constructed = _gcr_gnupg_process_constructed;
+	gobject_class->get_property = _gcr_gnupg_process_get_property;
+	gobject_class->set_property = _gcr_gnupg_process_set_property;
+	gobject_class->finalize = _gcr_gnupg_process_finalize;
+
+	g_object_class_install_property (gobject_class, PROP_DIRECTORY,
+	           g_param_spec_string ("directory", "Directory", "Gnupg Directory",
+	                                NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_object_class_install_property (gobject_class, PROP_EXECUTABLE,
+	           g_param_spec_string ("executable", "Executable", "Gnupg Executable",
+	                                GPG_EXECUTABLE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	signals[OUTPUT_DATA] = g_signal_new ("output-data", GCR_TYPE_GNUPG_PROCESS,
+	           G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, output_data),
+	           NULL, NULL, _gcr_marshal_VOID__BOXED,
+	           G_TYPE_NONE, 1, G_TYPE_BYTE_ARRAY);
+
+	signals[ERROR_LINE] = g_signal_new ("error-line", GCR_TYPE_GNUPG_PROCESS,
+	           G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, error_line),
+	           NULL, NULL, _gcr_marshal_VOID__STRING,
+	           G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[STATUS_RECORD] = g_signal_new ("status-record", GCR_TYPE_GNUPG_PROCESS,
+	           G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, status_record),
+	           NULL, NULL, _gcr_marshal_VOID__BOXED,
+	           G_TYPE_NONE, 1, GCR_TYPE_RECORD);
+
+	signals[ATTRIBUTE_DATA] = g_signal_new ("attribute-data", GCR_TYPE_GNUPG_PROCESS,
+	           G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrGnupgProcessClass, attribute_data),
+	           NULL, NULL, _gcr_marshal_VOID__BOXED,
+	           G_TYPE_NONE, 1, G_TYPE_BYTE_ARRAY);
+
+	g_type_class_add_private (gobject_class, sizeof (GcrGnupgProcessPrivate));
+}
+
+static gpointer
+_gcr_gnupg_process_get_user_data (GAsyncResult *result)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_PROCESS (result), NULL);
+	return GCR_GNUPG_PROCESS (result)->pv->user_data;
+}
+
+static GObject*
+_gcr_gnupg_process_get_source_object (GAsyncResult *result)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_PROCESS (result), NULL);
+	return G_OBJECT (result);
+}
+
+static void
+_gcr_gnupg_process_init_async (GAsyncResultIface *iface)
+{
+	iface->get_source_object = _gcr_gnupg_process_get_source_object;
+	iface->get_user_data = _gcr_gnupg_process_get_user_data;
+}
+
+/**
+ * _gcr_gnupg_process_new:
+ * @directory: (allow-none): The gnupg home directory
+ * @executable: (allow-none): The gpg executable
+ *
+ * Create a new GcrGnupgProcess.
+ *
+ * The gnupg home directory is where the keyring files live. If directory is
+ * %NULL then the default gnupg home directory is used.
+ *
+ * The executable will default to the compiled in path if a %NULL executable
+ * argument is used.
+ *
+ * Returns: (transfer full) A newly allocated process.
+ */
+GcrGnupgProcess*
+_gcr_gnupg_process_new (const gchar *directory, const gchar *executable)
+{
+	return g_object_new (GCR_TYPE_GNUPG_PROCESS,
+	                     "directory", directory,
+	                     "executable", executable,
+	                     NULL);
+}
+
+static void
+run_async_ready_callback (GcrGnupgProcess *self)
+{
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+
+	_gcr_debug ("running async callback");
+
+	/* Remove these before completing */
+	callback = self->pv->async_callback;
+	user_data = self->pv->user_data;
+	self->pv->async_callback = NULL;
+	self->pv->user_data = NULL;
+
+	if (callback != NULL)
+		(callback) (G_OBJECT (self), G_ASYNC_RESULT (self), user_data);
+}
+
+static gboolean
+on_run_async_ready_callback_later (gpointer user_data)
+{
+	run_async_ready_callback (GCR_GNUPG_PROCESS (user_data));
+	return FALSE; /* Don't run this callback again */
+}
+
+static void
+run_async_ready_callback_later (GcrGnupgProcess *self)
+{
+	_gcr_debug ("running async callback later");
+	g_idle_add_full (G_PRIORITY_DEFAULT, on_run_async_ready_callback_later,
+	                 g_object_ref (self), g_object_unref);
+}
+
+static void
+complete_run_process (GcrGnupgProcess *self)
+{
+	g_return_if_fail (self->pv->running);
+	g_return_if_fail (!self->pv->complete);
+
+	self->pv->running = FALSE;
+	self->pv->complete = TRUE;
+
+	if (self->pv->source_sig) {
+		g_source_remove (self->pv->source_sig);
+		self->pv->source_sig = 0;
+	}
+
+	if (self->pv->error == NULL) {
+		_gcr_debug ("completed process");
+	} else {
+		_gcr_debug ("completed process with error: %s",
+		            self->pv->error->message);
+	}
+}
+
+static gboolean
+complete_if_source_is_done (GnupgSource *gnupg_source)
+{
+	gint i;
+
+	for (i = 0; i < NUM_FDS; ++i) {
+		if (gnupg_source->polls[i].fd >= 0)
+			return FALSE;
+	}
+
+	if (gnupg_source->child_pid)
+		return FALSE;
+
+	_gcr_debug ("all fds closed and process exited, completing");
+
+	complete_run_process (gnupg_source->process);
+	run_async_ready_callback (gnupg_source->process);
+
+	/* All done, the source can go away now */
+	g_source_unref ((GSource*)gnupg_source);
+	return TRUE;
+}
+
+static void
+close_fd (int *fd)
+{
+	g_assert (fd);
+	if (*fd >= 0) {
+		_gcr_debug ("closing fd: %d", *fd);
+		close (*fd);
+	}
+	*fd = -1;
+}
+
+static void
+close_poll (GSource *source, GPollFD *poll)
+{
+	g_source_remove_poll (source, poll);
+	close_fd (&poll->fd);
+	poll->revents = 0;
+}
+
+static gboolean
+unused_callback (gpointer data)
+{
+	/* Never called */
+	g_assert_not_reached ();
+	return FALSE;
+}
+
+static gboolean
+on_gnupg_source_prepare (GSource *source, gint *timeout_)
+{
+	GnupgSource *gnupg_source = (GnupgSource*)source;
+	gint i;
+
+	for (i = 0; i < NUM_FDS; ++i) {
+		if (gnupg_source->polls[i].fd >= 0)
+			return FALSE;
+	}
+
+	/* If none of the FDs are valid, then process immediately */
+	return TRUE;
+}
+
+static gboolean
+on_gnupg_source_check (GSource *source)
+{
+	GnupgSource *gnupg_source = (GnupgSource*)source;
+	gint i;
+
+	for (i = 0; i < NUM_FDS; ++i) {
+		if (gnupg_source->polls[i].fd >= 0 && gnupg_source->polls[i].revents != 0)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+on_gnupg_source_finalize (GSource *source)
+{
+	GnupgSource *gnupg_source = (GnupgSource*)source;
+	gint i;
+
+	if (gnupg_source->cancel_sig) {
+		g_source_remove (gnupg_source->cancel_sig);
+		gnupg_source->cancel_sig = 0;
+	}
+
+	for (i = 0; i < NUM_FDS; ++i)
+		close_fd (&gnupg_source->polls[i].fd);
+
+	g_object_unref (gnupg_source->process);
+	g_string_free (gnupg_source->error_buf, TRUE);
+	g_string_free (gnupg_source->status_buf, TRUE);
+
+	g_assert (!gnupg_source->child_pid);
+	g_assert (!gnupg_source->child_sig);
+}
+
+static gboolean
+read_output (int fd, GByteArray *buffer)
+{
+	guchar block[1024];
+	gssize result;
+
+	g_return_val_if_fail (fd >= 0, -1);
+
+	do {
+		result = read (fd, block, sizeof (block));
+		if (result < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			return FALSE;
+		} else {
+			g_byte_array_append (buffer, block, result);
+		}
+	} while (result == sizeof (block));
+
+	return TRUE;
+}
+
+static void
+emit_status_for_each_line (const gchar *line, gpointer user_data)
+{
+	GcrRecord *record;
+
+	if (g_str_has_prefix (line, "[GNUPG:] ")) {
+		_gcr_debug ("received status line: %s", line);
+		line += 9;
+	} else {
+		g_message ("gnupg status record was not prefixed appropriately: %s", line);
+		return;
+	}
+
+	record = _gcr_record_parse_spaces (line, -1);
+	if (!record) {
+		g_message ("couldn't parse status record: %s", line);
+		return;
+	}
+
+	g_signal_emit (GCR_GNUPG_PROCESS (user_data), signals[STATUS_RECORD], 0, record);
+	_gcr_record_free (record);
+}
+
+static void
+emit_error_for_each_line (const gchar *line, gpointer user_data)
+{
+	_gcr_debug ("received error line: %s", line);
+	g_signal_emit (GCR_GNUPG_PROCESS (user_data), signals[ERROR_LINE], 0, line);
+}
+
+static gboolean
+on_gnupg_source_dispatch (GSource *source, GSourceFunc unused, gpointer user_data)
+{
+	GnupgSource *gnupg_source = (GnupgSource*)source;
+	GByteArray *buffer;
+	GPollFD *poll;
+
+	/* Standard input, no suport yet */
+	poll = &gnupg_source->polls[FD_INPUT];
+	if (poll->fd >= 0 && poll->revents != 0) {
+		close_poll (source, poll);
+	}
+
+	/* Status output */
+	poll = &gnupg_source->polls[FD_STATUS];
+	if (poll->fd >= 0) {
+		if (poll->revents & G_IO_IN) {
+			buffer = g_byte_array_new ();
+			if (!read_output (poll->fd, buffer)) {
+				g_warning ("couldn't read status data from gnupg process");
+			} else {
+				g_string_append_len (gnupg_source->status_buf, (gchar*)buffer->data, buffer->len);
+				_gcr_util_parse_lines (gnupg_source->status_buf, buffer->len == 0,
+				                       emit_status_for_each_line, gnupg_source->process);
+			}
+			g_byte_array_unref (buffer);
+		}
+		if (poll->revents & G_IO_HUP)
+			close_poll (source, poll);
+		poll->revents = 0;
+	}
+
+	/* Attribute output */
+	poll = &gnupg_source->polls[FD_ATTRIBUTE];
+	if (poll->fd >= 0) {
+		if (poll->revents & G_IO_IN) {
+			buffer = g_byte_array_new ();
+			if (!read_output (poll->fd, buffer)) {
+				g_warning ("couldn't read attribute data from gnupg process");
+			} else if (buffer->len > 0) {
+				_gcr_debug ("received %d bytes of attribute data", (gint)buffer->len);
+				g_signal_emit (gnupg_source->process, signals[ATTRIBUTE_DATA], 0, buffer);
+			}
+			g_byte_array_unref (buffer);
+		}
+		if (poll->revents & G_IO_HUP)
+			close_poll (source, poll);
+		poll->revents = 0;
+	}
+
+	/* Standard output */
+	poll = &gnupg_source->polls[FD_OUTPUT];
+	if (poll->fd >= 0) {
+		if (poll->revents & G_IO_IN) {
+			buffer = g_byte_array_new ();
+			if (!read_output (poll->fd, buffer)) {
+				g_warning ("couldn't read output data from gnupg process");
+			} else if (buffer->len > 0) {
+				_gcr_debug ("received %d bytes of attribute data", (gint)buffer->len);
+				g_signal_emit (gnupg_source->process, signals[OUTPUT_DATA], 0, buffer);
+			}
+			g_byte_array_unref (buffer);
+		}
+		if (poll->revents & G_IO_HUP)
+			close_poll (source, poll);
+		poll->revents = 0;
+	}
+
+	/* Standard error */
+	poll = &gnupg_source->polls[FD_ERROR];
+	if (poll->fd >= 0) {
+		if (poll->revents & G_IO_IN) {
+			buffer = g_byte_array_new ();
+			if (!read_output (poll->fd, buffer)) {
+				g_warning ("couldn't read error data from gnupg process");
+			} else {
+				g_string_append_len (gnupg_source->error_buf, (gchar*)buffer->data, buffer->len);
+				_gcr_util_parse_lines (gnupg_source->error_buf, (poll->revents & G_IO_HUP) ? TRUE : FALSE,
+						       emit_error_for_each_line, gnupg_source->process);
+			}
+			g_byte_array_unref (buffer);
+		}
+		if (poll->revents & G_IO_HUP)
+			close_poll (source, poll);
+		poll->revents = 0;
+	}
+
+	if (complete_if_source_is_done (gnupg_source))
+		return FALSE; /* Disconnect this source */
+
+	return TRUE;
+}
+
+static GSourceFuncs gnupg_source_funcs = {
+	on_gnupg_source_prepare,
+	on_gnupg_source_check,
+	on_gnupg_source_dispatch,
+	on_gnupg_source_finalize,
+};
+
+static void
+on_gnupg_process_child_exited (GPid pid, gint status, gpointer user_data)
+{
+	GnupgSource *gnupg_source = user_data;
+	GcrGnupgProcess *self = gnupg_source->process;
+	GError *error = NULL;
+	gint code;
+
+	_gcr_debug ("process exited: %d", (int)pid);
+
+	g_spawn_close_pid (gnupg_source->child_pid);
+	gnupg_source->child_pid = 0;
+	gnupg_source->child_sig = 0;
+
+	if (WIFEXITED (status)) {
+		code = WEXITSTATUS (status);
+		if (code != 0) {
+			error = g_error_new (G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+			                     "Gnupg process exited with code: %d", code);
+		}
+	} else if (WIFSIGNALED (status)) {
+		code = WTERMSIG (status);
+		/* Ignore cases where we've signaled the process because we were cancelled */
+		if (!g_error_matches (self->pv->error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			error = g_error_new (G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+			                     "Gnupg process was terminated with signal: %d", code);
+	}
+
+	/* Take this as the async result error */
+	if (error && !self->pv->error) {
+		_gcr_debug ("%s", error->message);
+		self->pv->error = error;
+
+	/* Already have an error, just print out message */
+	} else if (error) {
+		g_message ("%s", error->message);
+		g_error_free (error);
+	}
+
+	complete_if_source_is_done (gnupg_source);
+}
+
+static void
+on_gnupg_process_child_setup (gpointer user_data)
+{
+	int *child_fds = user_data;
+	long val;
+	guint i;
+
+	/*
+	 * Clear close-on-exec flag for these file descriptors, so that
+	 * gnupg can write to them
+	 */
+
+	for (i = 0; i < NUM_FDS; i++) {
+		if (child_fds[i] >= 0) {
+			val = fcntl (child_fds[i], F_GETFD);
+			fcntl (child_fds[i], F_SETFD, val & ~FD_CLOEXEC);
+		}
+	}
+}
+
+static void
+on_cancellable_cancelled (GCancellable *cancellable, gpointer user_data)
+{
+	GnupgSource *gnupg_source = user_data;
+
+	g_assert (gnupg_source->process);
+
+	_gcr_debug ("process cancelled");
+
+	/* Try and kill the child process */
+	if (gnupg_source->child_pid) {
+		_gcr_debug ("sending term signal to process: %d",
+		            (int)gnupg_source->child_pid);
+		kill (gnupg_source->child_pid, SIGTERM);
+	}
+
+	/* Set an error, which is respected when this actually completes. */
+	if (gnupg_source->process->pv->error == NULL)
+		gnupg_source->process->pv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,
+		                                                        "The operation was cancelled");
+
+	complete_if_source_is_done (gnupg_source);
+}
+
+/**
+ * _gcr_gnupg_process_run_async:
+ * @self: The process
+ * @argv: The arguments for the process, not including executable
+ * @envp: (allow-none): The environment for new process.
+ * @flags: Flags for starting the process.
+ * @cancellable: (allow-none): Cancellation object
+ * @callback: Will be called when operation completes.
+ *
+ * Run the gpg process. Only one 'run' operation can run per GcrGnupgProcess
+ * object. The GcrGnupgProcess:output_data and GcrGnupgProcess:error_line
+ * signals will be emitted when data is received from the gpg process.
+ *
+ * Unless the %GCR_GNUPG_PROCESS_RESPECT_LOCALE flag is specified, the process
+ * will be run in the 'C' locale. If the %GCR_GNUPG_PROCESS_WITH_STATUS or
+ * %GCR_GNUPG_PROCESS_WITH_ATTRIBUTES flags are set, then the gpg process
+ * will be status and attribute output respectively. The
+ * GcrGnupgProcess:status_record and GcrGnupgProcess:attribute_data signals
+ * will provide this data.
+ */
+void
+_gcr_gnupg_process_run_async (GcrGnupgProcess *self, const gchar **argv, const gchar **envp,
+                              GcrGnupgProcessFlags flags, GCancellable *cancellable,
+                              GAsyncReadyCallback callback, gpointer user_data)
+{
+	GError *error = NULL;
+	GPtrArray *args;
+	GPtrArray *envs;
+	int child_fds[NUM_FDS];
+	int status_fds[2] = { -1, -1 };
+	int attribute_fds[2] = { -1, -1 };
+	int output_fd = -1;
+	int error_fd = -1;
+	GnupgSource *gnupg_source;
+	GSource *source;
+	GPid pid;
+	guint i;
+
+	g_return_if_fail (GCR_IS_GNUPG_PROCESS (self));
+	g_return_if_fail (argv);
+	g_return_if_fail (callback);
+
+	g_return_if_fail (self->pv->running == FALSE);
+	g_return_if_fail (self->pv->complete == FALSE);
+	g_return_if_fail (self->pv->executable);
+
+	self->pv->async_callback = callback;
+	self->pv->user_data = user_data;
+
+	for (i = 0; i < NUM_FDS; i++)
+		child_fds[i] = -1;
+
+	/* The command needs to be updated with these status and attribute fds */
+	args = g_ptr_array_new_with_free_func (g_free);
+	g_ptr_array_add (args, g_strdup (self->pv->executable));
+
+	/* Spawn/child will close all other attributes, besides thesthose in child_fds */
+	child_fds[FD_OUTPUT] = 1;
+	child_fds[FD_ERROR] = 2;
+
+	if (flags & GCR_GNUPG_PROCESS_WITH_STATUS) {
+		if (pipe (status_fds) < 0)
+			g_return_if_reached ();
+		child_fds[FD_STATUS] = status_fds[1];
+		g_ptr_array_add (args, g_strdup ("--status-fd"));
+		g_ptr_array_add (args, g_strdup_printf ("%d", child_fds[FD_STATUS]));
+	}
+	if (flags & GCR_GNUPG_PROCESS_WITH_ATTRIBUTES) {
+		if (pipe (attribute_fds) < 0)
+			g_return_if_reached ();
+		child_fds[FD_ATTRIBUTE] = attribute_fds[1];
+		g_ptr_array_add (args, g_strdup ("--attribute-fd"));
+		g_ptr_array_add (args, g_strdup_printf ("%d", child_fds[FD_ATTRIBUTE]));
+	}
+
+	if (self->pv->directory) {
+		g_ptr_array_add (args, g_strdup ("--homedir"));
+		g_ptr_array_add (args, g_strdup (self->pv->directory));
+	}
+
+	/* All the remaining arguments */
+	for (i = 0; argv[i] != NULL; i++)
+		g_ptr_array_add (args, g_strdup (argv[i]));
+	g_ptr_array_add (args, NULL);
+
+	envs = g_ptr_array_new ();
+	for (i = 0; envp && envp[i] != NULL; i++) {
+		if (flags & GCR_GNUPG_PROCESS_RESPECT_LOCALE ||
+		    !g_str_has_prefix (envp[i], "LOCALE="))
+			g_ptr_array_add (envs, (gpointer)envp[i]);
+	}
+	if (!(flags & GCR_GNUPG_PROCESS_RESPECT_LOCALE))
+		g_ptr_array_add (envs, (gpointer)"LOCALE=C");
+	g_ptr_array_add (envs, NULL);
+
+	if (_gcr_debugging) {
+		gchar *command = g_strjoinv (" ", (gchar**)args->pdata);
+		gchar *environ = g_strjoinv (", ", (gchar**)envs->pdata);
+		_gcr_debug ("running command: %s", command);
+		_gcr_debug ("process environment: %s", environ);
+		g_free (command);
+		g_free (environ);
+	}
+
+	g_spawn_async_with_pipes (self->pv->directory, (gchar**)args->pdata,
+	                          (gchar**)envs->pdata, G_SPAWN_DO_NOT_REAP_CHILD,
+	                          on_gnupg_process_child_setup,
+	                          child_fds, &pid, NULL, &output_fd, &error_fd, &error);
+
+	g_ptr_array_free (args, TRUE);
+	g_ptr_array_free (envs, TRUE);
+
+	/* Close 'wrong' ends of extra file descriptors */
+	close_fd (&(status_fds[1]));
+	close_fd (&(attribute_fds[1]));
+
+	self->pv->complete = FALSE;
+	self->pv->running = TRUE;
+
+	if (error) {
+		close_fd (&(status_fds[0]));
+		close_fd (&(attribute_fds[0]));
+		g_assert (!self->pv->error);
+		self->pv->error = error;
+		complete_run_process (self);
+		run_async_ready_callback_later (self);
+		return;
+	}
+
+	_gcr_debug ("process started: %d", (int)pid);
+
+	source = g_source_new (&gnupg_source_funcs, sizeof (GnupgSource));
+
+	/* Initialize the source */
+	gnupg_source = (GnupgSource*)source;
+	for (i = 0; i < NUM_FDS; i++)
+		gnupg_source->polls[i].fd = -1;
+	gnupg_source->error_buf = g_string_sized_new (128);
+	gnupg_source->status_buf = g_string_sized_new (128);
+	gnupg_source->process = g_object_ref (self);
+	gnupg_source->child_pid = pid;
+
+	gnupg_source->polls[FD_OUTPUT].fd = output_fd;
+	if (output_fd >= 0) {
+		gnupg_source->polls[FD_OUTPUT].events = G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &gnupg_source->polls[FD_OUTPUT]);
+	}
+	gnupg_source->polls[FD_ERROR].fd = error_fd;
+	if (error_fd >= 0) {
+		gnupg_source->polls[FD_ERROR].events = G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &gnupg_source->polls[FD_ERROR]);
+	}
+	gnupg_source->polls[FD_STATUS].fd = status_fds[0];
+	if (status_fds[0] >= 0) {
+		gnupg_source->polls[FD_STATUS].events = G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &gnupg_source->polls[FD_STATUS]);
+	}
+	gnupg_source->polls[FD_ATTRIBUTE].fd = attribute_fds[0];
+	if (attribute_fds[0] >= 0) {
+		gnupg_source->polls[FD_ATTRIBUTE].events = G_IO_HUP | G_IO_IN;
+		g_source_add_poll (source, &gnupg_source->polls[FD_ATTRIBUTE]);
+	}
+
+	if (cancellable) {
+		gnupg_source->cancel_sig = g_cancellable_connect (cancellable,
+		                                                  G_CALLBACK (on_cancellable_cancelled),
+		                                                  g_source_ref (source),
+		                                                  (GDestroyNotify)g_source_unref);
+	}
+
+	g_assert (!self->pv->source_sig);
+	g_source_set_callback (source, unused_callback, NULL, NULL);
+	self->pv->source_sig = g_source_attach (source, g_main_context_default ());
+
+	/* This assumes the outstanding reference to source */
+	g_assert (!gnupg_source->child_sig);
+	gnupg_source->child_sig = g_child_watch_add_full (G_PRIORITY_DEFAULT, pid,
+	                                                  on_gnupg_process_child_exited,
+	                                                  g_source_ref (source),
+	                                                  (GDestroyNotify)g_source_unref);
+
+	/* source is unreffed in complete_if_source_is_done() */
+}
+
+/**
+ * _gcr_gnupg_process_run_finish:
+ * @self: The process
+ * @result: The result passed to the callback
+ * @error: Location to raise an error on failure.
+ *
+ * Get the result of running a gnupg process.
+ */
+gboolean
+_gcr_gnupg_process_run_finish (GcrGnupgProcess *self, GAsyncResult *result,
+                               GError **error)
+{
+	g_return_val_if_fail (GCR_IS_GNUPG_PROCESS (self), FALSE);
+	g_return_val_if_fail (!error || !*error, FALSE);
+	g_return_val_if_fail (G_ASYNC_RESULT (self) == result, FALSE);
+	g_return_val_if_fail (self->pv->complete, FALSE);
+
+	/* This allows the process to run again... */
+	self->pv->complete = FALSE;
+
+	g_assert (!self->pv->running);
+	g_assert (!self->pv->async_callback);
+	g_assert (!self->pv->user_data);
+	g_assert (!self->pv->source_sig);
+
+	if (self->pv->error) {
+		g_propagate_error (error, self->pv->error);
+		self->pv->error = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
diff --git a/gcr/gcr-gnupg-process.h b/gcr/gcr-gnupg-process.h
new file mode 100644
index 0000000..d3d0532
--- /dev/null
+++ b/gcr/gcr-gnupg-process.h
@@ -0,0 +1,88 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program 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.
+ *
+ * This program 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.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#ifndef GCR_GNUPG_PROCESS_H
+#define GCR_GNUPG_PROCESS_H
+
+#include "gcr.h"
+#include "gcr-record.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_GNUPG_PROCESS               (_gcr_gnupg_process_get_type ())
+#define GCR_GNUPG_PROCESS(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_GNUPG_PROCESS, GcrGnupgProcess))
+#define GCR_GNUPG_PROCESS_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_GNUPG_PROCESS, GcrGnupgProcessClass))
+#define GCR_IS_GNUPG_PROCESS(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_GNUPG_PROCESS))
+#define GCR_IS_GNUPG_PROCESS_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_GNUPG_PROCESS))
+#define GCR_GNUPG_PROCESS_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_GNUPG_PROCESS, GcrGnupgProcessClass))
+
+typedef struct _GcrGnupgProcess GcrGnupgProcess;
+typedef struct _GcrGnupgProcessClass GcrGnupgProcessClass;
+typedef struct _GcrGnupgProcessPrivate GcrGnupgProcessPrivate;
+
+struct _GcrGnupgProcess {
+	GObject parent;
+	GcrGnupgProcessPrivate *pv;
+};
+
+struct _GcrGnupgProcessClass {
+	GObjectClass parent_class;
+
+	/* signals */
+	gboolean (*output_data) (GcrGnupgProcess *self, GByteArray *output);
+
+	gboolean (*error_line) (GcrGnupgProcess *self, const gchar *line);
+
+	gboolean (*status_record) (GcrGnupgProcess *self, GcrRecord *record);
+
+	gboolean (*attribute_data) (GcrGnupgProcess *self, GByteArray *output);
+};
+
+typedef enum {
+	GCR_GNUPG_PROCESS_RESPECT_LOCALE    = 1 << 0,
+	GCR_GNUPG_PROCESS_WITH_STATUS       = 1 << 1,
+	GCR_GNUPG_PROCESS_WITH_ATTRIBUTES   = 1 << 2
+} GcrGnupgProcessFlags;
+
+GType               _gcr_gnupg_process_get_type                (void);
+
+GcrGnupgProcess*    _gcr_gnupg_process_new                     (const gchar *directory,
+                                                                const gchar *executable);
+
+void                _gcr_gnupg_process_run_async               (GcrGnupgProcess *self,
+                                                                const gchar **argv,
+                                                                const gchar **envp,
+                                                                GcrGnupgProcessFlags flags,
+                                                                GCancellable *cancellable,
+                                                                GAsyncReadyCallback callback,
+                                                                gpointer user_data);
+
+gboolean            _gcr_gnupg_process_run_finish              (GcrGnupgProcess *self,
+                                                                GAsyncResult *result,
+                                                                GError **error);
+
+G_END_DECLS
+
+#endif /* GCR_GNUPG_PROCESS_H */
diff --git a/gcr/gcr-marshal.list b/gcr/gcr-marshal.list
index a448d71..9a842f2 100644
--- a/gcr/gcr-marshal.list
+++ b/gcr/gcr-marshal.list
@@ -1,2 +1,4 @@
 BOOLEAN:INT
 VOID:STRING,BOXED
+VOID:BOXED
+VOID:STRING
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index 29324d9..e2ece38 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -30,7 +30,8 @@ TEST_PROGS = \
 	test-parser \
 	test-record \
 	test-gnupg-key \
-	test-gnupg-collection
+	test-gnupg-collection \
+	test-gnupg-process
 
 check_PROGRAMS = $(TEST_PROGS)
 
diff --git a/gcr/tests/files/gnupg-mock/mock-arguments-environ b/gcr/tests/files/gnupg-mock/mock-arguments-environ
new file mode 100755
index 0000000..272c76e
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-arguments-environ
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+while getopts '1:2:' arg; do
+	case $arg in
+	1)
+		echo $OPTARG
+		;;
+	2)
+		echo $OPTARG
+		;;
+	*)
+		invalid argument: $arg
+		exit 2
+		;;
+	esac
+done
+
+echo -n $ENVIRON1 >&2
+echo $ENVIRON2 >&2
diff --git a/gcr/tests/files/gnupg-mock/mock-fail-exit b/gcr/tests/files/gnupg-mock/mock-fail-exit
new file mode 100755
index 0000000..4d5634b
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-fail-exit
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+exit $1
diff --git a/gcr/tests/files/gnupg-mock/mock-fail-signal b/gcr/tests/files/gnupg-mock/mock-fail-signal
new file mode 100755
index 0000000..decf8a4
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-fail-signal
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+echo $1 > /tmp/xxx
+kill -s $1 $$
+sleep 5
diff --git a/gcr/tests/files/gnupg-mock/mock-simple-error b/gcr/tests/files/gnupg-mock/mock-simple-error
new file mode 100755
index 0000000..30bb6a9
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-simple-error
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+echo -n "line 1: " >&2
+echo "more line 1" >&2
+echo "line 2" >&2
+echo "line 3" >&2
\ No newline at end of file
diff --git a/gcr/tests/files/gnupg-mock/mock-simple-output b/gcr/tests/files/gnupg-mock/mock-simple-output
new file mode 100755
index 0000000..0d145fc
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-simple-output
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+echo -n "simple-"
+echo -n "outp"
+echo "ut"
\ No newline at end of file
diff --git a/gcr/tests/files/gnupg-mock/mock-status-and-attribute b/gcr/tests/files/gnupg-mock/mock-status-and-attribute
new file mode 100755
index 0000000..4df073a
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-status-and-attribute
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# This script is used with test-gnupg-process
+# Needs to be run with /bin/bash in order to handle two digit
+# file descripter redirects
+
+set -euf
+
+SFD=
+AFD=
+
+# Not real 'long' option parsing, but good enough for this
+while [ $# -gt 1 ]; do
+	if [ "$1" = --status-fd ]; then
+		SFD=$2
+		shift
+	elif [ "$1" = --attribute-fd ]; then
+		AFD=$2
+		shift
+	fi
+	shift
+done
+
+# No FD passed :(
+if [ -z "$AFD" ]; then
+	exit 22
+fi
+if [ -z "$SFD" ]; then
+	exit 23
+fi
+
+echo -n "1lc923g4laoeurc23rc2" >&$AFD
+echo "[GNUPG:] SCHEMA one two three four " >&$SFD
+echo -n "41lcg2r23c4gr3" >&$AFD
diff --git a/gcr/tests/files/gnupg-mock/mock-status-and-output b/gcr/tests/files/gnupg-mock/mock-status-and-output
new file mode 100755
index 0000000..0fd544b
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-status-and-output
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+FD=
+
+# Not real 'long' option parsing, but good enough for this
+while [ $# -gt 1 ]; do
+	if [ "$1" = --status-fd ]; then
+		FD=$2
+		shift
+	fi
+	shift
+done
+
+# No FD passed :(
+if [ -z "$FD" ]; then
+	exit 22
+fi
+
+echo "Here's some output"
+echo "[GNUPG:] SCHEMA one two three four " >&$FD
+echo "More output"
diff --git a/gcr/tests/files/gnupg-mock/mock-with-homedir b/gcr/tests/files/gnupg-mock/mock-with-homedir
new file mode 100755
index 0000000..c537e03
--- /dev/null
+++ b/gcr/tests/files/gnupg-mock/mock-with-homedir
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# This script is used with test-gnupg-process
+set -euf
+
+HOMEDIR=
+
+# Not real 'long' option parsing, but good enough for this
+while [ $# -gt 1 ]; do
+	if [ "$1" = --homedir ]; then
+		HOMEDIR=$2
+		shift
+	fi
+	shift
+done
+
+# No homedir passed :(
+if [ -z "$HOMEDIR" ]; then
+	exit 22
+fi
+
+echo "DIR: $HOMEDIR"
diff --git a/gcr/tests/test-gnupg-collection.c b/gcr/tests/test-gnupg-collection.c
index e76b29a..c164ea6 100644
--- a/gcr/tests/test-gnupg-collection.c
+++ b/gcr/tests/test-gnupg-collection.c
@@ -106,7 +106,6 @@ teardown (Test *test, gconstpointer unused)
 		g_object_unref (test->result);
 
 	g_object_unref (test->collection);
-	g_assert (!GCR_IS_GNUPG_COLLECTION (test->collection));
 }
 
 static void
diff --git a/gcr/tests/test-gnupg-process.c b/gcr/tests/test-gnupg-process.c
new file mode 100644
index 0000000..80dfe04
--- /dev/null
+++ b/gcr/tests/test-gnupg-process.c
@@ -0,0 +1,446 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd
+
+   The Gnome Keyring Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The Gnome Keyring Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the Gnome Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr.h"
+#include "gcr/gcr-gnupg-process.h"
+
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+#include <errno.h>
+#include <string.h>
+
+typedef struct {
+	GcrGnupgProcess *process;
+	GAsyncResult *result;
+	GString *output_buf;
+	GString *error_buf;
+	GString *attribute_buf;
+	GcrRecord *record;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+	test->output_buf = g_string_new ("");
+	test->error_buf = g_string_new ("");
+	test->attribute_buf = g_string_new ("");
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+	if (test->result)
+		g_object_unref (test->result);
+	if (test->process)
+		g_object_unref (test->process);
+	if (test->output_buf)
+		g_string_free (test->output_buf, TRUE);
+	if (test->error_buf)
+		g_string_free (test->error_buf, TRUE);
+	if (test->attribute_buf)
+		g_string_free (test->attribute_buf, TRUE);
+	_gcr_record_free (test->record);
+}
+
+static void
+test_create (Test *test, gconstpointer unused)
+{
+	gchar *value;
+
+	test->process = _gcr_gnupg_process_new ("/the/directory", "/path/to/executable");
+
+	g_object_get (test->process, "directory", &value, NULL);
+	g_assert_cmpstr (value, ==, "/the/directory");
+	g_free (value);
+
+	g_object_get (test->process, "executable", &value, NULL);
+	g_assert_cmpstr (value, ==, "/path/to/executable");
+	g_free (value);
+}
+
+static void
+on_async_ready (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (G_OBJECT (test->process) == source);
+	g_assert (test->result == NULL);
+	g_assert (g_async_result_get_source_object (result) == source);
+
+	test->result = g_object_ref (result);
+	egg_test_wait_stop ();
+}
+
+static gchar*
+build_script_path (const gchar *name)
+{
+	gchar *directory;
+	gchar *path;
+
+	directory = g_get_current_dir ();
+	path = g_build_filename (directory, "files", "gnupg-mock", name, NULL);
+	g_free (directory);
+
+	return path;
+}
+
+static void
+on_process_output_data (GcrGnupgProcess *process, GByteArray *buffer, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (process == test->process);
+	g_assert (buffer);
+
+	g_string_append_len (test->output_buf, (gchar*)buffer->data, buffer->len);
+}
+
+static void
+on_process_attribute_data (GcrGnupgProcess *process, GByteArray *buffer, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (process == test->process);
+	g_assert (buffer);
+
+	g_string_append_len (test->attribute_buf, (gchar*)buffer->data, buffer->len);
+}
+
+static void
+on_process_error_line (GcrGnupgProcess *process, const gchar *line, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (process == test->process);
+	g_assert (line);
+	g_assert (!strchr (line, '\n'));
+
+	g_string_append_printf (test->error_buf, "%s\n", line);
+}
+
+static void
+on_process_status_record (GcrGnupgProcess *process, GcrRecord *record, gpointer user_data)
+{
+	Test *test = user_data;
+
+	g_assert (process == test->process);
+	g_assert (record);
+
+	g_assert (!test->record);
+	test->record = _gcr_record_copy (record);
+}
+
+static void
+test_run_simple_output (Test *test, gconstpointer unused)
+{
+	const gchar *argv[] = { NULL };
+	GError *error = NULL;
+	gchar *script;
+
+	script = build_script_path ("mock-simple-output");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "output-data", G_CALLBACK (on_process_output_data), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	g_assert_cmpstr ("simple-output\n", ==, test->output_buf->str);
+}
+
+static void
+test_run_simple_error (Test *test, gconstpointer unused)
+{
+	const gchar *argv[] = { NULL };
+	GError *error = NULL;
+	gchar *script;
+
+	script = build_script_path ("mock-simple-error");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "error-line", G_CALLBACK (on_process_error_line), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	g_assert_cmpstr ("line 1: more line 1\nline 2\nline 3\n", ==, test->error_buf->str);
+}
+
+static void
+test_run_status_and_output (Test *test, gconstpointer unused)
+{
+	const gchar *argv[] = { NULL };
+	GError *error = NULL;
+	gchar *script;
+
+	script = build_script_path ("mock-status-and-output");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "output-data", G_CALLBACK (on_process_output_data), test);
+	g_signal_connect (test->process, "status-record", G_CALLBACK (on_process_status_record), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, GCR_GNUPG_PROCESS_WITH_STATUS,
+	                              NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	g_assert (test->record);
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 0), ==, "SCHEMA");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 1), ==, "one");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 2), ==, "two");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 3), ==, "three");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 4), ==, "four");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 5), ==, NULL);
+	g_assert_cmpstr ("Here's some output\nMore output\n", ==, test->output_buf->str);
+}
+
+static void
+test_run_status_and_attribute (Test *test, gconstpointer unused)
+{
+	const gchar *argv[] = { NULL };
+	GError *error = NULL;
+	gchar *script;
+
+	script = build_script_path ("mock-status-and-attribute");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "attribute-data", G_CALLBACK (on_process_attribute_data), test);
+	g_signal_connect (test->process, "status-record", G_CALLBACK (on_process_status_record), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL,
+	                              GCR_GNUPG_PROCESS_WITH_STATUS | GCR_GNUPG_PROCESS_WITH_ATTRIBUTES,
+	                              NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	g_assert (test->record);
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 0), ==, "SCHEMA");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 1), ==, "one");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 2), ==, "two");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 3), ==, "three");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 4), ==, "four");
+	g_assert_cmpstr (_gcr_record_get_raw (test->record, 5), ==, NULL);
+	g_assert_cmpstr ("1lc923g4laoeurc23rc241lcg2r23c4gr3", ==, test->attribute_buf->str);
+}
+
+
+static void
+test_run_arguments_and_environment (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	gchar *script;
+
+	const gchar *argv[] = {
+		"-1", "value1",
+		"-2", "value2",
+		NULL
+	};
+
+	const gchar *envp[] = {
+		"ENVIRON1=VALUE1",
+		"ENVIRON2=VALUE2",
+		NULL
+	};
+
+	script = build_script_path ("mock-arguments-environ");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "output-data", G_CALLBACK (on_process_output_data), test);
+	g_signal_connect (test->process, "error-line", G_CALLBACK (on_process_error_line), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, envp, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	g_assert_cmpstr ("value1\nvalue2\n", ==, test->output_buf->str);
+	g_assert_cmpstr ("VALUE1VALUE2\n", ==, test->error_buf->str);
+}
+
+static void
+test_run_with_homedir (Test *test, gconstpointer unused)
+{
+	const gchar *argv[] = { NULL };
+	GError *error = NULL;
+	gchar *script;
+	gchar *directory;
+	gchar *check;
+
+	directory = g_get_current_dir ();
+
+	script = build_script_path ("mock-with-homedir");
+	test->process = _gcr_gnupg_process_new (directory, script);
+	g_free (script);
+
+	g_signal_connect (test->process, "output-data", G_CALLBACK (on_process_output_data), test);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_no_error (error);
+
+	check = g_strdup_printf ("DIR: %s\n", directory);
+	g_assert_cmpstr (check, ==, test->output_buf->str);
+	g_free (check);
+	g_free (directory);
+}
+
+static void
+test_run_bad_executable (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	gchar *script;
+	const gchar *argv[] = { NULL };
+
+	script = build_script_path ("mock-invalid");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT);
+	g_clear_error (&error);
+}
+
+static void
+test_run_fail_exit (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	gchar *script;
+	const gchar *argv[] = { "55" };
+
+	script = build_script_path ("mock-fail-exit");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED);
+	g_assert_cmpstr (error->message, ==, "Gnupg process exited with code: 55");
+	g_clear_error (&error);
+}
+
+static void
+test_run_fail_signal (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	gchar *script;
+	const gchar *argv[] = { "15" };
+
+	script = build_script_path ("mock-fail-signal");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, NULL, on_async_ready, test);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED);
+	g_assert_cmpstr (error->message, ==, "Gnupg process was terminated with signal: 15");
+	g_clear_error (&error);
+}
+
+static void
+test_run_and_cancel (Test *test, gconstpointer unused)
+{
+	GError *error = NULL;
+	gchar *script;
+	const gchar *argv[] = { "15" };
+	GCancellable *cancellable;
+
+	cancellable = g_cancellable_new ();
+
+	script = build_script_path ("mock-simple-output");
+	test->process = _gcr_gnupg_process_new (NULL, script);
+	g_free (script);
+
+	_gcr_gnupg_process_run_async (test->process, argv, NULL, 0, cancellable, on_async_ready, test);
+	g_cancellable_cancel (cancellable);
+	egg_test_wait_until (500);
+
+	g_assert (test->result);
+	_gcr_gnupg_process_run_finish (test->process, test->result, &error);
+	g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+	g_clear_error (&error);
+}
+
+int
+main (int argc, char **argv)
+{
+	const gchar *srcdir;
+
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+	g_set_prgname ("test-gnupg-process");
+
+	srcdir = g_getenv ("SRCDIR");
+	if (srcdir && chdir (srcdir) < 0)
+		g_error ("couldn't change directory to: %s: %s", srcdir, g_strerror (errno));
+
+	g_test_add ("/gcr/gnupg-process/create", Test, NULL, setup, test_create, teardown);
+	g_test_add ("/gcr/gnupg-process/run_simple_output", Test, NULL, setup, test_run_simple_output, teardown);
+	g_test_add ("/gcr/gnupg-process/run_simple_error", Test, NULL, setup, test_run_simple_error, teardown);
+	g_test_add ("/gcr/gnupg-process/run_status_and_output", Test, NULL, setup, test_run_status_and_output, teardown);
+	g_test_add ("/gcr/gnupg-process/run_status_and_attribute", Test, NULL, setup, test_run_status_and_attribute, teardown);
+	g_test_add ("/gcr/gnupg-process/run_arguments_and_environment", Test, NULL, setup, test_run_arguments_and_environment, teardown);
+	g_test_add ("/gcr/gnupg-process/run_with_homedir", Test, NULL, setup, test_run_with_homedir, teardown);
+	g_test_add ("/gcr/gnupg-process/run_bad_executable", Test, NULL, setup, test_run_bad_executable, teardown);
+	g_test_add ("/gcr/gnupg-process/run_fail_exit", Test, NULL, setup, test_run_fail_exit, teardown);
+	g_test_add ("/gcr/gnupg-process/run_fail_signal", Test, NULL, setup, test_run_fail_signal, teardown);
+	g_test_add ("/gcr/gnupg-process/run_and_cancel", Test, NULL, setup, test_run_and_cancel, teardown);
+
+	return egg_tests_run_in_thread_with_loop ();
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]