2002-10-10 Marcus Brinkmann <marcus@g10code.de>
[gpgme.git] / gpgme / wait.c
1 /* wait.c 
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *      Copyright (C) 2001, 2002 g10 Code GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <assert.h>
27 #include <errno.h>
28 #include <sys/types.h>
29
30 #include "util.h"
31 #include "context.h"
32 #include "ops.h"
33 #include "wait.h"
34 #include "sema.h"
35 #include "io.h"
36 #include "engine.h"
37
38 static struct fd_table fdt_global;
39
40 static GpgmeCtx *ctx_done_list;
41 static int ctx_done_list_size;
42 static int ctx_done_list_length;
43 DEFINE_STATIC_LOCK (ctx_done_list_lock);
44
45 static GpgmeIdleFunc idle_function;
46
47 struct wait_item_s
48 {
49   struct wait_item_s *next;
50   GpgmeIOCb handler;
51   void *handler_value;
52   int dir;
53 };
54
55 \f
56 void
57 _gpgme_fd_table_init (fd_table_t fdt)
58 {
59   INIT_LOCK (fdt->lock);
60   fdt->fds = NULL;
61   fdt->size = 0;
62 }
63
64 void
65 _gpgme_fd_table_deinit (fd_table_t fdt)
66 {
67   DESTROY_LOCK (fdt->lock);
68   if (fdt->fds)
69     free (fdt->fds);
70 }
71
72 /* XXX We should keep a marker and roll over for speed.  */
73 GpgmeError
74 _gpgme_fd_table_put (fd_table_t fdt, int fd, int dir, void *opaque, int *idx)
75 {
76   int i, j;
77   struct io_select_fd_s *new_fds;
78
79   LOCK (fdt->lock);
80   for (i = 0; i < fdt->size; i++)
81     {
82       if (fdt->fds[i].fd == -1)
83         break;
84     }
85   if (i == fdt->size)
86     {
87 #define FDT_ALLOCSIZE 10
88       new_fds = realloc (fdt->fds, (fdt->size + FDT_ALLOCSIZE)
89                          * sizeof (*new_fds));
90       if (!new_fds)
91         {
92           UNLOCK (fdt->lock);
93           return mk_error (Out_Of_Core);
94         }
95       
96       fdt->fds = new_fds;
97       fdt->size += FDT_ALLOCSIZE;
98       for (j = 0; j < FDT_ALLOCSIZE; j++)
99         fdt->fds[i + j].fd = -1;
100     }
101
102   fdt->fds[i].fd = fd;
103   fdt->fds[i].for_read = (dir == 1);
104   fdt->fds[i].for_write = (dir == 0);
105   fdt->fds[i].frozen = 0;
106   fdt->fds[i].signaled = 0;
107   fdt->fds[i].opaque = opaque;
108   UNLOCK (fdt->lock);
109   *idx = i;
110   return 0;
111 }
112
113 \f
114 /**
115  * gpgme_register_idle:
116  * @fnc: Callers idle function
117  * 
118  * Register a function with GPGME called by GPGME whenever it feels
119  * that is is idle.  NULL may be used to remove this function.
120  *
121  * Return value: The idle function pointer that was passed to the
122  * function at the last time it was invoked, or NULL if the function
123  * is invoked the first time.
124  **/
125 GpgmeIdleFunc
126 gpgme_register_idle (GpgmeIdleFunc idle)
127 {
128   GpgmeIdleFunc old_idle = idle_function;
129
130   idle_function = idle;
131   return old_idle;
132 }
133
134 \f
135 /* Wait on all file descriptors listed in FDT and process them using
136    the registered callbacks.  Returns -1 on error (with errno set), 0
137    if nothing to run and 1 if it did run something.  */
138 static int
139 do_select (fd_table_t fdt)
140 {
141   int i, n;
142   int any = 0;
143
144   LOCK (fdt->lock);
145   n = _gpgme_io_select (fdt->fds, fdt->size, 0);
146
147   if (n <= 0) 
148     {
149       UNLOCK (fdt->lock);
150       return n; /* Error or timeout.  */
151     }
152
153   for (i = 0; i < fdt->size && n; i++)
154     {
155       if (fdt->fds[i].fd != -1 && fdt->fds[i].signaled)
156         {
157           struct wait_item_s *item;
158
159           assert (n);
160           n--;
161             
162           item = (struct wait_item_s *) fdt->fds[i].opaque;
163           assert (item);
164           any = 1;
165
166           fdt->fds[i].signaled = 0;
167           UNLOCK (fdt->lock);
168           item->handler (item->handler_value, fdt->fds[i].fd);
169           LOCK (fdt->lock);
170         }
171     }
172   UNLOCK (fdt->lock);
173     
174   return any;
175 }
176
177
178 \f
179 void
180 _gpgme_wait_event_cb (void *data, GpgmeEventIO type, void *type_data)
181 {
182   if (type != GPGME_EVENT_DONE)
183     return;
184
185   if (ctx_done_list_size == ctx_done_list_length)
186     {
187 #define CTX_DONE_LIST_SIZE_INITIAL 8
188       int new_size = ctx_done_list_size ? 2 * ctx_done_list_size
189         : CTX_DONE_LIST_SIZE_INITIAL;
190       GpgmeCtx *new_list = realloc (ctx_done_list,
191                                     new_size * sizeof (GpgmeCtx *));
192       assert (new_list);
193 #if 0
194       if (!new_list)
195         return mk_error (Out_Of_Core);
196 #endif
197       ctx_done_list = new_list;
198       ctx_done_list_size = new_size;
199     }
200   ctx_done_list[ctx_done_list_length++] = (GpgmeCtx) data;
201 }
202
203
204 /**
205  * gpgme_wait:
206  * @c: 
207  * @hang: 
208  * 
209  * Wait for a finished request, if @c is given the function does only
210  * wait on a finished request for that context, otherwise it will return
211  * on any request.  When @hang is true the function will wait, otherwise
212  * it will return immediately when there is no pending finished request.
213  * 
214  * Return value: Context of the finished request or NULL if @hang is false
215  *  and no (or not the given) request has finished.
216  **/
217 GpgmeCtx 
218 gpgme_wait (GpgmeCtx ctx, GpgmeError *status, int hang)
219 {
220   DEBUG2 ("waiting... ctx=%p hang=%d", ctx, hang);
221   do
222     {
223       int i;
224
225       /* XXX We are ignoring all errors from select here.  */
226       do_select (&fdt_global);
227       
228       LOCK (ctx_done_list_lock);
229       /* A process that is done is eligible for election if it is the
230          requested context or if it was not yet reported.  */
231       for (i = 0; i < ctx_done_list_length; i++)
232         if (!ctx || ctx == ctx_done_list[i])
233           break;
234       if (i < ctx_done_list_length)
235         {
236           if (!ctx)
237             ctx = ctx_done_list[i];
238           hang = 0;
239           ctx->pending = 0;
240           if (--ctx_done_list_length)
241             memcpy (&ctx_done_list[i],
242                     &ctx_done_list[i + 1],
243                     (ctx_done_list_length - i) * sizeof (GpgmeCtx *));
244         }
245       UNLOCK (ctx_done_list_lock);
246
247       if (hang && idle_function)
248         idle_function ();
249     }
250   while (hang && (!ctx || !ctx->cancel));
251
252   if (ctx && ctx->cancel)
253     {
254       /* FIXME: Paranoia?  */
255       ctx->cancel = 0;
256       ctx->pending = 0;
257       ctx->error = mk_error (Canceled);
258     }
259
260   if (ctx && status)
261     *status = ctx->error;
262   return ctx;
263 }
264
265
266 GpgmeError
267 _gpgme_wait_one (GpgmeCtx ctx)
268 {
269   return _gpgme_wait_on_condition (ctx, NULL);
270 }
271
272
273 GpgmeError
274 _gpgme_wait_on_condition (GpgmeCtx ctx, volatile int *cond)
275 {
276   GpgmeError err = 0;
277   int hang = 1;
278   DEBUG1 ("waiting... ctx=%p", ctx);
279   do
280     {
281       if (do_select (&ctx->fdt) < 0)
282         {
283           err = mk_error (File_Error);
284           hang = 0;
285         }
286       else if (cond && *cond)
287         hang = 0;
288       else        
289         {
290           int any = 0;
291           int i;
292
293           LOCK (ctx->fdt.lock);
294           for (i = 0; i < ctx->fdt.size; i++)
295             {
296               if (ctx->fdt.fds[i].fd != -1)
297                 {
298                   any = 1;
299                   break;
300                 }
301             }
302           if (!any)
303             hang = 0;
304           UNLOCK (ctx->fdt.lock);
305         }
306     }
307   while (hang && !ctx->cancel);
308   if (!err && ctx->cancel)
309     {
310       /* FIXME: Paranoia?  */
311       ctx->cancel = 0;
312       ctx->pending = 0;
313       ctx->error = mk_error (Canceled);
314     }
315   return err ? err : ctx->error;
316 }
317
318 \f
319 struct tag
320 {
321   fd_table_t fdt;
322   int idx;
323 };
324
325 GpgmeError
326 _gpgme_add_io_cb (void *data, int fd, int dir,
327                   GpgmeIOCb fnc, void *fnc_data, void **r_tag)
328 {
329   GpgmeError err;
330   fd_table_t fdt = (fd_table_t) (data ? data : &fdt_global);
331   struct wait_item_s *item;
332   struct tag *tag;
333
334   assert (fdt);
335   assert (fnc);
336
337   *r_tag = NULL;
338   tag = malloc (sizeof *tag);
339   if (!tag)
340     return mk_error (Out_Of_Core);
341   tag->fdt = fdt;
342
343   /* Allocate a structure to hold info about the handler.  */
344   item = calloc (1, sizeof *item);
345   if (!item)
346     {
347       free (tag);
348       return mk_error (Out_Of_Core);
349     }
350   item->dir = dir;
351   item->handler = fnc;
352   item->handler_value = fnc_data;
353
354   err = _gpgme_fd_table_put (fdt, fd, dir, item, &tag->idx);
355   if (err)
356     {
357       free (tag);
358       free (item);
359       return mk_error (Out_Of_Core);
360     }
361
362   *r_tag = tag;
363   return 0;
364 }
365
366 void
367 _gpgme_remove_io_cb (void *data)
368 {
369   struct tag *tag = data;
370   fd_table_t fdt = tag->fdt;
371   int idx = tag->idx;
372
373   LOCK (fdt->lock);
374   DEBUG2 ("setting fd %d (item=%p) done", fdt->fds[idx].fd,
375           fdt->fds[idx].opaque);
376   free (fdt->fds[idx].opaque);
377   free (tag);
378
379   /* Free the table entry.  */
380   fdt->fds[idx].fd = -1;
381   fdt->fds[idx].for_read = 0;
382   fdt->fds[idx].for_write = 0;
383   fdt->fds[idx].opaque = NULL;
384   UNLOCK (fdt->lock);
385 }
386