[gnome-keyring: 1/3] gcr: Refactor gnupg process execution into its own class.
- From: Stefan Walter <stefw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-keyring: 1/3] gcr: Refactor gnupg process execution into its own class.
- Date: Tue, 12 Jul 2011 10:56:10 +0000 (UTC)
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]