
This patch has gotten a bit large... mmh. The main thing that happens here is that error values are now not determined in the operation function after gpgme_wait completed, but in the status handler when EOF is received. It should always be the case that either an error is flagged or EOF is received, so that after a gpgme_wait you should never have the situation that no error is flagged and EOF is not received. One problem is that the engine status handlers don't have access to the context, a horrible kludge works around this for now. All errors that happen during a pending operation should be catched and reported in ctx->error, including out-of-core and cancellation. This rounds up neatly a couple of loose ends, and makes it possible to pass up any errors in the communication with the backend as well. As a bonus, there will be a function to access gpgme->wait, so that the operations can truly be implemented with their _start function. * engine-gpgsm.c (gpgsm_status_handler): Horrible kludge to report error back to the context. * rungpg.c (gpg_status_handler): Same horrible kludge applied here. * engine-gpgsm.c (gpgsm_assuan_simple_command): Add error checking. * wait.c (_gpgme_wait_on_condition): If canceled, set CTX->error to a value indication that. * verify.c (add_notation): Set error, not out_of_core. (finish_sig): Likewise. (gpgme_op_verify_start): Don't clear out_of_core. (_gpgme_verify_status_handler): At EOF, clean up the notation data. (gpgme_op_verify): And don't do it here. * trustlist.c (trustlist_status_handler): Check error, not out_of_core. (gpgme_op_trustlist_start): Don't clear out_of_core. (gpgme_op_trustlist_next): Check error, not out_of_core. (gpgme_op_trustlist_end): Likewise. * ops.h (test_and_allocate_result): New macro. (_gpgme_passphrase_result): Remove prototype. * delete.c (gpgme_op_delete): Return error from context. (delete_status_handler): Use macro test_and_allocate_result. Perform error checking at EOF. (gpgme_op_delete_start): Release result. * passphrase.c (_gpgme_passphrase_status_handler): Use macro test_and_allocate_result, and perform error checking here. (_gpgme_passphrase_result): Function removed. * sign.c (gpgme_op_sign_start): Do not set out_of_core to zero. (gpgme_op_sign): Just return the error value from the context. (sign_status_handler): Only progress if no error is set yet. If we process an EOF, set the resulting error value (if any). * decrypt.c (_gpgme_decrypt_result): Function removed. (create_result_struct): Function removed. (_gpgme_decrypt_status_handler): Use macro test_and_allocate_result, caclulate error on EOF, do not progress with errors. (_gpgme_decrypt_start): Do not set out_of_core to zero. (gpgme_op_decrypt): Just return the error value from the context. * encrypt.c (encrypt_status_handler): Perform the error checking here. (gpgme_op_encrypt_start): Do not clear out_of_core. * export.c (export_status_handler): Return if error is set in context. (gpgme_op_export_start): Release result. (gpgme_op_export): Return error from context. * decrypt-verify.c (gpgme_op_decrypt_verify): Return the error in the context. * genkey.c (genkey_status_handler): Use macro test_and_allocate_result. Perform error checking at EOF. (gpgme_op_genkey): Just return the error from context. * import.c (gpgme_op_import): Return the error from context. (import_status_handler): Use macro test_and_allocate_result. * keylist.c (gpgme_op_keylist_start): Do not clear out_of_core. (gpgme_op_keylist_next): Return error of context. (keylist_colon_handler): Set error instead out_of_code. (finish_key): Likewise. * context.h: Remove member out_of_core, add member error. * gpgme.c (_gpgme_release_result): Clear error flag. * engine.h (_gpgme_engine_get_error): New prototype. * engine.c (_gpgme_engine_get_error): New function. * engine-gpgsm.c (_gpgme_gpgsm_get_error): New function. * engine-gpgsm.c (map_assuan_error): New function. (gpgsm_assuan_simple_command): Change return type to GpgmeError, use the new function to map error values. (gpgsm_set_fd): Change return type tp GpgmeError. (_gpgme_gpgsm_op_decrypt): Change type of ERR to GpgmeError. (gpgsm_set_recipients): Likewise. Change type of return value equivalently. Adjust error values. (_gpgme_gpgsm_op_import): Likewise. (_gpgme_gpgsm_op_sign): Likewise. (struct gpgsm_object_s): New member error. (gpgsm_status_handler): Set error if error occurs. Determine error number from ERR line received. If assuan_read_line fails, terminate the connection.
427 lines
9.2 KiB
C
427 lines
9.2 KiB
C
/* wait.c
|
|
* Copyright (C) 2000 Werner Koch (dd9jn)
|
|
* Copyright (C) 2001, 2001 g10 Code GmbH
|
|
*
|
|
* This file is part of GPGME.
|
|
*
|
|
* GPGME is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GPGME is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU 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
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include "syshdr.h"
|
|
|
|
#include "util.h"
|
|
#include "context.h"
|
|
#include "ops.h"
|
|
#include "wait.h"
|
|
#include "sema.h"
|
|
#include "io.h"
|
|
#include "engine.h"
|
|
|
|
struct wait_item_s;
|
|
struct proc_s;
|
|
|
|
static struct proc_s *proc_queue;
|
|
DEFINE_STATIC_LOCK (proc_queue_lock);
|
|
|
|
static int fd_table_size;
|
|
static struct io_select_fd_s *fd_table;
|
|
DEFINE_STATIC_LOCK (fd_table_lock);
|
|
|
|
static GpgmeIdleFunc idle_function;
|
|
|
|
|
|
struct proc_s {
|
|
struct proc_s *next;
|
|
int pid;
|
|
GpgmeCtx ctx;
|
|
struct wait_item_s *handler_list;
|
|
int done;
|
|
};
|
|
|
|
struct wait_item_s {
|
|
struct wait_item_s *next;
|
|
int (*handler)(void*,int,int);
|
|
void *handler_value;
|
|
int inbound; /* this is an inbound data handler fd */
|
|
struct proc_s *proc; /* backlink */
|
|
int done;
|
|
int frozen; /* copy of the frozen flag from the fd_table */
|
|
};
|
|
|
|
|
|
|
|
static int do_select ( void );
|
|
static void run_idle (void);
|
|
|
|
|
|
/* only to be called with a locked proc_queue */
|
|
static int
|
|
count_running_fds (struct proc_s *proc)
|
|
{
|
|
struct wait_item_s *q;
|
|
int count = 0;
|
|
|
|
for (q = proc->handler_list; q; q=q->next)
|
|
{
|
|
if (!q->frozen && !q->done)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* only to be called with a locked proc_queue */
|
|
static void
|
|
set_process_done (struct proc_s *proc)
|
|
{
|
|
struct wait_item_s *q, *q2;
|
|
int i;
|
|
|
|
assert (proc);
|
|
DEBUG2 ("set_process_done(%p) pid=%d", proc, proc->pid);
|
|
LOCK (fd_table_lock);
|
|
for (q = proc->handler_list; q; q=q2)
|
|
{
|
|
q2 = q->next;
|
|
for (i = 0; i < fd_table_size; i++)
|
|
{
|
|
if (fd_table[i].fd != -1 && q == fd_table[i].opaque)
|
|
{
|
|
fd_table[i].opaque = NULL;
|
|
fd_table[i].fd = -1;
|
|
}
|
|
}
|
|
xfree (q);
|
|
}
|
|
UNLOCK (fd_table_lock);
|
|
proc->handler_list = NULL;
|
|
proc->done = 1;
|
|
}
|
|
|
|
void
|
|
_gpgme_remove_proc_from_wait_queue (int pid)
|
|
{
|
|
struct proc_s *proc, *last;
|
|
|
|
DEBUG1 ("removing process %d", pid);
|
|
LOCK (proc_queue_lock);
|
|
for (last = NULL, proc = proc_queue; proc; last = proc, proc = proc->next)
|
|
{
|
|
if (proc->pid == pid)
|
|
{
|
|
set_process_done (proc);
|
|
if (!last)
|
|
proc_queue = proc->next;
|
|
else
|
|
last->next = proc->next;
|
|
xfree (proc);
|
|
break;
|
|
}
|
|
}
|
|
UNLOCK (proc_queue_lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* gpgme_wait:
|
|
* @c:
|
|
* @hang:
|
|
*
|
|
* Wait for a finished request, if @c is given the function does only
|
|
* wait on a finished request for that context, otherwise it will return
|
|
* on any request. When @hang is true the function will wait, otherwise
|
|
* it will return immediately when there is no pending finished request.
|
|
*
|
|
* Return value: Context of the finished request or NULL if @hang is false
|
|
* and no (or the given) request has finished.
|
|
**/
|
|
GpgmeCtx
|
|
gpgme_wait (GpgmeCtx ctx, int hang)
|
|
{
|
|
return _gpgme_wait_on_condition (ctx, hang, NULL);
|
|
}
|
|
|
|
GpgmeCtx
|
|
_gpgme_wait_on_condition (GpgmeCtx ctx, int hang, volatile int *cond)
|
|
{
|
|
DEBUG3 ("waiting... ctx=%p hang=%d cond=%p", ctx, hang, cond);
|
|
do
|
|
{
|
|
int any = 0;
|
|
struct proc_s *proc;
|
|
|
|
do_select ();
|
|
|
|
if (cond && *cond)
|
|
hang = 0;
|
|
else
|
|
{
|
|
LOCK (proc_queue_lock);
|
|
for (proc = proc_queue; proc; proc = proc->next)
|
|
{
|
|
if (!proc->done && !count_running_fds (proc))
|
|
set_process_done (proc);
|
|
if (ctx && proc->done && proc->ctx == ctx)
|
|
{
|
|
hang = 0;
|
|
ctx->pending = 0;
|
|
}
|
|
if (!proc->done)
|
|
any = 1;
|
|
}
|
|
UNLOCK (proc_queue_lock);
|
|
if (!any)
|
|
hang = 0;
|
|
}
|
|
/* fixme: We should check here for hanging processes. */
|
|
|
|
if (hang)
|
|
run_idle ();
|
|
}
|
|
while (hang && !ctx->cancel);
|
|
if (ctx->cancel)
|
|
{
|
|
ctx->cancel = 0;
|
|
ctx->error = mk_error (Canceled);
|
|
ctx->pending = 0;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
|
|
/*
|
|
* We use this function to do the select stuff for all running
|
|
* gpgs. A future version might provide a facility to delegate
|
|
* those selects to the GDK select stuff.
|
|
* This function must be called only by one thread!!
|
|
* Returns: 0 = nothing to run
|
|
* 1 = did run something
|
|
*/
|
|
|
|
static int
|
|
do_select (void)
|
|
{
|
|
int i, n;
|
|
int any = 0;
|
|
|
|
n = _gpgme_io_select (fd_table, fd_table_size);
|
|
if (n <= 0)
|
|
return 0; /* error or timeout */
|
|
|
|
for (i = 0; i < fd_table_size && n; i++)
|
|
{
|
|
if (fd_table[i].fd != -1 && fd_table[i].signaled
|
|
&& !fd_table[i].frozen)
|
|
{
|
|
struct wait_item_s *q;
|
|
|
|
assert (n);
|
|
n--;
|
|
|
|
q = fd_table[i].opaque;
|
|
assert (q);
|
|
assert (q->proc);
|
|
assert (!q->done);
|
|
any = 1;
|
|
if (q->handler (q->handler_value,
|
|
q->proc->pid, fd_table[i].fd))
|
|
{
|
|
DEBUG2 ("setting fd %d (q=%p) done", fd_table[i].fd, q);
|
|
q->done = 1;
|
|
/* Free the table entry. */
|
|
LOCK (fd_table_lock);
|
|
fd_table[i].for_read = 0;
|
|
fd_table[i].for_write = 0;
|
|
fd_table[i].fd = -1;
|
|
fd_table[i].opaque = NULL;
|
|
UNLOCK (fd_table_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
return any;
|
|
}
|
|
|
|
|
|
/*
|
|
* called by rungpg.c to register something for select()
|
|
*/
|
|
GpgmeError
|
|
_gpgme_register_pipe_handler (void *opaque,
|
|
int (*handler)(void*,int,int),
|
|
void *handler_value,
|
|
int pid, int fd, int inbound)
|
|
{
|
|
GpgmeCtx ctx = opaque;
|
|
struct wait_item_s *q;
|
|
struct proc_s *proc;
|
|
int i;
|
|
|
|
assert (opaque);
|
|
assert (handler);
|
|
|
|
/* Allocate a structure to hold info about the handler. */
|
|
q = xtrycalloc (1, sizeof *q);
|
|
if (!q)
|
|
return mk_error (Out_Of_Core);
|
|
q->inbound = inbound;
|
|
q->handler = handler;
|
|
q->handler_value = handler_value;
|
|
|
|
/* Put this into the process queue. */
|
|
LOCK (proc_queue_lock);
|
|
for (proc = proc_queue; proc && proc->pid != pid; proc = proc->next)
|
|
;
|
|
if (!proc)
|
|
{
|
|
/* A new process. */
|
|
proc = xtrycalloc (1, sizeof *proc);
|
|
if (!proc)
|
|
{
|
|
UNLOCK (proc_queue_lock);
|
|
return mk_error (Out_Of_Core);
|
|
}
|
|
proc->pid = pid;
|
|
proc->ctx = ctx;
|
|
proc->next = proc_queue;
|
|
proc_queue = proc;
|
|
}
|
|
assert (proc->ctx == ctx);
|
|
q->proc = proc;
|
|
q->next = proc->handler_list;
|
|
proc->handler_list = q;
|
|
UNLOCK (proc_queue_lock);
|
|
|
|
LOCK (fd_table_lock);
|
|
again:
|
|
for (i=0; i < fd_table_size; i++)
|
|
{
|
|
if (fd_table[i].fd == -1)
|
|
{
|
|
fd_table[i].fd = fd;
|
|
fd_table[i].for_read = inbound;
|
|
fd_table[i].for_write = !inbound;
|
|
fd_table[i].signaled = 0;
|
|
fd_table[i].frozen = 0;
|
|
fd_table[i].opaque = q;
|
|
UNLOCK (fd_table_lock);
|
|
return 0;
|
|
}
|
|
}
|
|
if ( fd_table_size < 50 ) {
|
|
/* FIXME: We have to wait until there are no other readers of the
|
|
* table, i.e that the io_select is not active in another thread */
|
|
struct io_select_fd_s *tmp;
|
|
|
|
tmp = xtryrealloc (fd_table, (fd_table_size + 10) * sizeof *tmp);
|
|
if (tmp)
|
|
{
|
|
for (i = 0; i < 10; i++)
|
|
tmp[fd_table_size+i].fd = -1;
|
|
fd_table_size += i;
|
|
fd_table = tmp;
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
UNLOCK (fd_table_lock);
|
|
xfree (q);
|
|
/* FIXME: Remove the proc table entry. */
|
|
return mk_error (Too_Many_Procs);
|
|
}
|
|
|
|
|
|
void
|
|
_gpgme_freeze_fd (int fd)
|
|
{
|
|
int i;
|
|
|
|
LOCK (fd_table_lock);
|
|
for (i = 0; i < fd_table_size; i++)
|
|
{
|
|
if (fd_table[i].fd == fd)
|
|
{
|
|
struct wait_item_s *q;
|
|
|
|
fd_table[i].frozen = 1;
|
|
q = fd_table[i].opaque;
|
|
if (q)
|
|
q->frozen = 1;
|
|
DEBUG2 ("fd %d frozen (q=%p)", fd, q);
|
|
break;
|
|
}
|
|
}
|
|
UNLOCK (fd_table_lock);
|
|
}
|
|
|
|
void
|
|
_gpgme_thaw_fd (int fd)
|
|
{
|
|
int i;
|
|
|
|
LOCK (fd_table_lock);
|
|
for (i = 0; i < fd_table_size; i++)
|
|
{
|
|
if (fd_table[i].fd == fd)
|
|
{
|
|
struct wait_item_s *q;
|
|
|
|
fd_table[i].frozen = 0;
|
|
q = fd_table[i].opaque;
|
|
if (q)
|
|
q->frozen = 0;
|
|
DEBUG2 ("fd %d thawed (q=%p)", fd, q);
|
|
break;
|
|
}
|
|
}
|
|
UNLOCK (fd_table_lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* gpgme_register_idle:
|
|
* @fnc: Callers idle function
|
|
*
|
|
* Register a function with GPGME called by GPGME whenever it feels
|
|
* that is is idle. NULL may be used to remove this function.
|
|
*
|
|
* Return value: The idle function pointer that was passed to the
|
|
* function at the last time it was invoked, or NULL if the function
|
|
* is invoked the first time.
|
|
**/
|
|
GpgmeIdleFunc
|
|
gpgme_register_idle (GpgmeIdleFunc idle)
|
|
{
|
|
GpgmeIdleFunc old_idle = idle_function;
|
|
|
|
idle_function = idle;
|
|
return old_idle;
|
|
}
|
|
|
|
|
|
static void
|
|
run_idle ()
|
|
{
|
|
_gpgme_engine_housecleaning ();
|
|
if (idle_function)
|
|
idle_function ();
|
|
}
|