2002-06-14 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 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 static void run_idle (void);
56
57 \f
58 void
59 _gpgme_fd_table_init (fd_table_t fdt)
60 {
61   INIT_LOCK (fdt->lock);
62   fdt->fds = NULL;
63   fdt->size = 0;
64 }
65
66 void
67 _gpgme_fd_table_deinit (fd_table_t fdt)
68 {
69   DESTROY_LOCK (fdt->lock);
70   if (fdt->fds)
71     xfree (fdt->fds);
72 }
73
74 /* XXX We should keep a marker and roll over for speed.  */
75 GpgmeError
76 _gpgme_fd_table_put (fd_table_t fdt, int fd, int dir, void *opaque, int *idx)
77 {
78   int i, j;
79   struct io_select_fd_s *new_fds;
80
81   LOCK (fdt->lock);
82   for (i = 0; i < fdt->size; i++)
83     {
84       if (fdt->fds[i].fd == -1)
85         break;
86     }
87   if (i == fdt->size)
88     {
89 #define FDT_ALLOCSIZE 10
90       new_fds = xtryrealloc (fdt->fds, (fdt->size + FDT_ALLOCSIZE)
91                              * sizeof (*new_fds));
92       if (!new_fds)
93         {
94           UNLOCK (fdt->lock);
95           return mk_error (Out_Of_Core);
96         }
97       
98       fdt->fds = new_fds;
99       fdt->size += FDT_ALLOCSIZE;
100       for (j = 0; j < FDT_ALLOCSIZE; j++)
101         fdt->fds[i + j].fd = -1;
102     }
103
104   fdt->fds[i].fd = fd;
105   fdt->fds[i].for_read = (dir == 1);
106   fdt->fds[i].for_write = (dir == 0);
107   fdt->fds[i].frozen = 0;
108   fdt->fds[i].signaled = 0;
109   fdt->fds[i].opaque = opaque;
110   UNLOCK (fdt->lock);
111   *idx = i;
112   return 0;
113 }
114
115 \f
116 /**
117  * gpgme_register_idle:
118  * @fnc: Callers idle function
119  * 
120  * Register a function with GPGME called by GPGME whenever it feels
121  * that is is idle.  NULL may be used to remove this function.
122  *
123  * Return value: The idle function pointer that was passed to the
124  * function at the last time it was invoked, or NULL if the function
125  * is invoked the first time.
126  **/
127 GpgmeIdleFunc
128 gpgme_register_idle (GpgmeIdleFunc idle)
129 {
130   GpgmeIdleFunc old_idle = idle_function;
131
132   idle_function = idle;
133   return old_idle;
134 }
135
136 static void
137 run_idle ()
138 {
139   _gpgme_engine_housecleaning ();
140   if (idle_function)
141     idle_function ();
142 }
143
144 \f
145 /* Wait on all file descriptors listed in FDT and process them using
146    the registered callbacks.  Returns -1 on error (with errno set), 0
147    if nothing to run and 1 if it did run something.  */
148 static int
149 do_select (fd_table_t fdt)
150 {
151   int i, n;
152   int any = 0;
153
154   LOCK (fdt->lock);
155   n = _gpgme_io_select (fdt->fds, fdt->size);
156
157   if (n <= 0) 
158     {
159       UNLOCK (fdt->lock);
160       return n; /* Error or timeout.  */
161     }
162
163   for (i = 0; i < fdt->size && n; i++)
164     {
165       if (fdt->fds[i].fd != -1 && fdt->fds[i].signaled)
166         {
167           struct wait_item_s *item;
168
169           assert (n);
170           n--;
171             
172           item = (struct wait_item_s *) fdt->fds[i].opaque;
173           assert (item);
174           any = 1;
175
176           fdt->fds[i].signaled = 0;
177           UNLOCK (fdt->lock);
178           item->handler (item->handler_value, fdt->fds[i].fd);
179           LOCK (fdt->lock);
180         }
181     }
182   UNLOCK (fdt->lock);
183     
184   return any;
185 }
186
187
188 \f
189 void
190 _gpgme_wait_event_cb (void *data, GpgmeEventIO type, void *type_data)
191 {
192   if (type != GPGME_EVENT_DONE)
193     return;
194
195   if (ctx_done_list_size == ctx_done_list_length)
196     {
197 #define CTX_DONE_LIST_SIZE_INITIAL 8
198       int new_size = ctx_done_list_size ? 2 * ctx_done_list_size
199         : CTX_DONE_LIST_SIZE_INITIAL;
200       GpgmeCtx *new_list = xtryrealloc (ctx_done_list,
201                                         new_size * sizeof (GpgmeCtx *));
202       assert (new_list);
203 #if 0
204       if (!new_list)
205         return mk_error (Out_Of_Core);
206 #endif
207       ctx_done_list = new_list;
208       ctx_done_list_size = new_size;
209     }
210   ctx_done_list[ctx_done_list_length++] = (GpgmeCtx) data;
211 }
212
213
214 /**
215  * gpgme_wait:
216  * @c: 
217  * @hang: 
218  * 
219  * Wait for a finished request, if @c is given the function does only
220  * wait on a finished request for that context, otherwise it will return
221  * on any request.  When @hang is true the function will wait, otherwise
222  * it will return immediately when there is no pending finished request.
223  * 
224  * Return value: Context of the finished request or NULL if @hang is false
225  *  and no (or not the given) request has finished.
226  **/
227 GpgmeCtx 
228 gpgme_wait (GpgmeCtx ctx, GpgmeError *status, int hang)
229 {
230   ctx = _gpgme_wait_on_condition (ctx, hang, NULL);
231   if (ctx && status)
232     *status = ctx->error;
233   return ctx;
234 }
235
236 GpgmeError
237 _gpgme_wait_one (GpgmeCtx ctx)
238 {
239   GpgmeError err = 0;
240   int hang = 1;
241   DEBUG1 ("waiting... ctx=%p", ctx);
242   do
243     {
244       if (do_select (&ctx->fdt) < 0)
245         {
246           err = mk_error (File_Error);
247           hang = 0;
248         }
249       else
250         {
251           int any = 0;
252           int i;
253
254           LOCK (ctx->fdt.lock);
255           for (i = 0; i < ctx->fdt.size; i++)
256             {
257               if (ctx->fdt.fds[i].fd != -1)
258                 {
259                   any = 1;
260                   break;
261                 }
262             }
263           if (!any)
264             hang = 0;
265           UNLOCK (ctx->fdt.lock);
266         }
267     }
268   while (hang && !ctx->cancel);
269   if (!err && ctx->cancel)
270     {
271       /* FIXME: Paranoia?  */
272       ctx->cancel = 0;
273       ctx->pending = 0;
274       ctx->error = mk_error (Canceled);
275     }
276   return err ? err : ctx->error;
277 }
278
279
280 GpgmeCtx 
281 _gpgme_wait_on_condition (GpgmeCtx ctx, int hang, volatile int *cond)
282 {
283   DEBUG3 ("waiting... ctx=%p hang=%d cond=%p", ctx, hang, cond);
284   do
285     {
286       /* XXX We are ignoring all errors from select here.  */
287       do_select (&fdt_global);
288       
289       if (cond && *cond)
290         hang = 0;
291       else
292         {
293           int i;
294
295           LOCK (ctx_done_list_lock);
296           /* A process that is done is eligible for election if it is
297              the requested context or if it was not yet reported.  */
298           for (i = 0; i < ctx_done_list_length; i++)
299             if (!ctx || ctx == ctx_done_list[i])
300               break;
301           if (i < ctx_done_list_length)
302             {
303               if (!ctx)
304                 ctx = ctx_done_list[i];
305               hang = 0;
306               ctx->pending = 0;
307               if (--ctx_done_list_length)
308                 memcpy (&ctx_done_list[i],
309                         &ctx_done_list[i + 1],
310                         (ctx_done_list_length - i) * sizeof (GpgmeCtx *));
311             }
312           UNLOCK (ctx_done_list_lock);
313         }
314       if (hang)
315         run_idle ();
316     }
317   while (hang && (!ctx || !ctx->cancel));
318   if (ctx && ctx->cancel)
319     {
320       /* FIXME: Paranoia?  */
321       ctx->cancel = 0;
322       ctx->pending = 0;
323       ctx->error = mk_error (Canceled);
324     }
325   return ctx;
326 }
327
328 \f
329 struct tag
330 {
331   fd_table_t fdt;
332   int idx;
333 };
334
335 void *
336 _gpgme_add_io_cb (void *data, int fd, int dir,
337                   GpgmeIOCb fnc, void *fnc_data)
338 {
339   GpgmeError err;
340   fd_table_t fdt = (fd_table_t) (data ? data : &fdt_global);
341   struct wait_item_s *item;
342   struct tag *tag;
343
344   assert (fdt);
345   assert (fnc);
346
347   tag = xtrymalloc (sizeof *tag);
348   if (!tag)
349     return NULL;
350   tag->fdt = fdt;
351
352   /* Allocate a structure to hold info about the handler.  */
353   item = xtrycalloc (1, sizeof *item);
354   if (!item)
355     {
356       xfree (tag);
357       return NULL;
358     }
359   item->dir = dir;
360   item->handler = fnc;
361   item->handler_value = fnc_data;
362
363   err = _gpgme_fd_table_put (fdt, fd, dir, item, &tag->idx);
364   if (err)
365     {
366       xfree (tag);
367       xfree (item);
368       errno = ENOMEM;
369       return 0;
370     }
371   
372   return tag;
373 }
374
375 void
376 _gpgme_remove_io_cb (void *data)
377 {
378   struct tag *tag = data;
379   fd_table_t fdt = tag->fdt;
380   int idx = tag->idx;
381
382   LOCK (fdt->lock);
383   DEBUG2 ("setting fd %d (item=%p) done", fdt->fds[idx].fd,
384           fdt->fds[idx].opaque);
385   xfree (fdt->fds[idx].opaque);
386   xfree (tag);
387
388   /* Free the table entry.  */
389   fdt->fds[idx].fd = -1;
390   fdt->fds[idx].for_read = 0;
391   fdt->fds[idx].for_write = 0;
392   fdt->fds[idx].opaque = NULL;
393 }
394