typo fix in comment.
[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   DEBUG2 ("waiting... ctx=%p hang=%d", ctx, hang);
231   do
232     {
233       int i;
234
235       /* XXX We are ignoring all errors from select here.  */
236       do_select (&fdt_global);
237       
238       LOCK (ctx_done_list_lock);
239       /* A process that is done is eligible for election if it is the
240          requested context or if it was not yet reported.  */
241       for (i = 0; i < ctx_done_list_length; i++)
242         if (!ctx || ctx == ctx_done_list[i])
243           break;
244       if (i < ctx_done_list_length)
245         {
246           if (!ctx)
247             ctx = ctx_done_list[i];
248           hang = 0;
249           ctx->pending = 0;
250           if (--ctx_done_list_length)
251             memcpy (&ctx_done_list[i],
252                     &ctx_done_list[i + 1],
253                     (ctx_done_list_length - i) * sizeof (GpgmeCtx *));
254         }
255       UNLOCK (ctx_done_list_lock);
256
257       if (hang)
258         run_idle ();
259     }
260   while (hang && (!ctx || !ctx->cancel));
261
262   if (ctx && ctx->cancel)
263     {
264       /* FIXME: Paranoia?  */
265       ctx->cancel = 0;
266       ctx->pending = 0;
267       ctx->error = mk_error (Canceled);
268     }
269
270   if (ctx && status)
271     *status = ctx->error;
272   return ctx;
273 }
274
275
276 GpgmeError
277 _gpgme_wait_one (GpgmeCtx ctx)
278 {
279   return _gpgme_wait_on_condition (ctx, NULL);
280 }
281
282
283 GpgmeError
284 _gpgme_wait_on_condition (GpgmeCtx ctx, volatile int *cond)
285 {
286   GpgmeError err = 0;
287   int hang = 1;
288   DEBUG1 ("waiting... ctx=%p", ctx);
289   do
290     {
291       if (do_select (&ctx->fdt) < 0)
292         {
293           err = mk_error (File_Error);
294           hang = 0;
295         }
296       else if (cond && *cond)
297         hang = 0;
298       else        
299         {
300           int any = 0;
301           int i;
302
303           LOCK (ctx->fdt.lock);
304           for (i = 0; i < ctx->fdt.size; i++)
305             {
306               if (ctx->fdt.fds[i].fd != -1)
307                 {
308                   any = 1;
309                   break;
310                 }
311             }
312           if (!any)
313             hang = 0;
314           UNLOCK (ctx->fdt.lock);
315         }
316     }
317   while (hang && !ctx->cancel);
318   if (!err && ctx->cancel)
319     {
320       /* FIXME: Paranoia?  */
321       ctx->cancel = 0;
322       ctx->pending = 0;
323       ctx->error = mk_error (Canceled);
324     }
325   return err ? err : ctx->error;
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   UNLOCK (fdt->lock);
394 }
395