e2c46364795bd6e5e3695de44ef52809186c6767
[gpgme.git] / gpgme / wait.c
1 /* wait.c 
2  *      Copyright (C) 2000 Werner Koch (dd9jn)
3  *
4  * This file is part of GPGME.
5  *
6  * GPGME is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GPGME is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <assert.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include "syshdr.h"
29
30 #include "util.h"
31 #include "context.h"
32 #include "ops.h"
33 #include "wait.h"
34 #include "io.h"
35
36 /* Fixme: implement the following stuff to make the code MT safe.
37  * To avoid the need to link against a specific threads lib, such
38  * an implementation should require the caller to register a function
39  * which does this task.
40  * enter_crit() and leave_crit() are used to embrace an area of code
41  * which should be executed only by one thread at a time.
42  * lock_xxxx() and unlock_xxxx()  protect access to an data object.
43  *  */
44 #define enter_crit()    do { } while (0)
45 #define leave_crit()    do { } while (0)
46 #define lock_table()    do { } while (0)
47 #define unlock_table()  do { } while (0)
48
49
50 struct wait_item_s {
51     volatile int active;
52     int (*handler)(void*,int,int);
53     void *handler_value;
54     int pid;
55     int inbound;       /* this is an inbound data handler fd */
56     GpgmeCtx ctx;
57 };
58
59 static int fd_table_size;
60 static struct io_select_fd_s *fd_table;
61
62 static void (*idle_function) (void);
63
64 static int do_select ( void );
65 static void run_idle (void);
66
67
68 static struct wait_item_s *
69 queue_item_from_context ( GpgmeCtx ctx )
70 {
71     struct wait_item_s *q;
72     int i;
73
74     for (i=0; i < fd_table_size; i++ ) {
75         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->ctx == ctx )
76             return q;
77     }
78     return NULL;
79 }
80
81
82 static int
83 count_active_and_thawed_fds ( int pid )
84 {
85     struct wait_item_s *q;
86     int i, count = 0;
87     
88     for (i=0; i < fd_table_size; i++ ) {
89         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
90              && q->active && !fd_table[i].frozen && q->pid == pid  ) 
91             count++;
92     }
93     return count;
94 }
95
96 /* remove the given process from the queue */
97 /* FIXME: We should do this on demand from rungpg.c */
98 static void
99 remove_process ( int pid )
100 {
101     struct wait_item_s *q;
102     int i;
103
104     for (i=0; i < fd_table_size; i++ ) {
105         if (fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->pid == pid ) {
106             xfree (q);
107             fd_table[i].opaque = NULL;
108             fd_table[i].fd = -1;
109         }
110     }
111 }
112
113
114
115 /**
116  * gpgme_wait:
117  * @c: 
118  * @hang: 
119  * 
120  * Wait for a finished request, if @c is given the function does only
121  * wait on a finsihed request for that context, otherwise it will return
122  * on any request.  When @hang is true the function will wait, otherwise
123  * it will return immediately when there is no pending finished request.
124  * 
125  * Return value: Context of the finished request or NULL if @hang is false
126  *  and no (or the given) request has finished.
127  **/
128 GpgmeCtx 
129 gpgme_wait ( GpgmeCtx c, int hang ) 
130 {
131     return _gpgme_wait_on_condition ( c, hang, NULL );
132 }
133
134 GpgmeCtx 
135 _gpgme_wait_on_condition ( GpgmeCtx c, int hang, volatile int *cond )
136 {
137     struct wait_item_s *q;
138
139     do {
140         int did_work = do_select();
141
142         if ( cond && *cond )
143             hang = 0;
144
145         if ( !did_work ) {
146             /* We did no read/write - see whether the process is still
147              * alive */
148             assert (c); /* !c is not yet implemented */
149             q = queue_item_from_context ( c );
150             if (q) {
151                 if ( !count_active_and_thawed_fds (q->pid) ) {
152                     remove_process (q->pid);
153                     hang = 0;
154                 }
155             }
156             else
157                 hang = 0;
158         }
159         if (hang)
160             run_idle ();
161     } while (hang && !c->cancel );
162     c->cancel = 0; /* fixme: fix all functions, to return a cancel error */
163     return c;
164 }
165
166
167
168 /*
169  * We use this function to do the select stuff for all running
170  * gpgs.  A future version might provide a facility to delegate
171  * those selects to the GDK select stuff.
172  * This function must be called only by one thread!!
173  * Returns: 0 = nothing to run
174  *          1 = did run something 
175  */
176
177 static int
178 do_select ( void )
179 {
180     struct wait_item_s *q;
181     int i, n;
182     int any=0;
183     
184     n = _gpgme_io_select ( fd_table, fd_table_size );
185     if ( n <= 0 ) 
186         return 0; /* error or timeout */
187
188     for (i=0; i < fd_table_size && n; i++ ) {
189         if ( fd_table[i].fd != -1 && fd_table[i].signaled 
190              && !fd_table[i].frozen ) {
191             q = fd_table[i].opaque;
192             assert (n);
193             n--;
194             if ( q->active )
195                 any = 1;
196             if ( q->active && q->handler (q->handler_value,
197                                           q->pid, fd_table[i].fd ) ) {
198                 DEBUG1 ("setting fd %d inactive", fd_table[i].fd );
199                 q->active = 0;
200                 fd_table[i].for_read = 0;
201                 fd_table[i].for_write = 0;
202                 fd_table[i].fd = -1;
203             }
204         }
205     }
206     
207     return any;
208 }
209
210
211
212 /* 
213  * called by rungpg.c to register something for select()
214  */
215 GpgmeError
216 _gpgme_register_pipe_handler ( void *opaque, 
217                               int (*handler)(void*,int,int),
218                               void *handler_value,
219                               int pid, int fd, int inbound )
220 {
221     GpgmeCtx ctx = opaque;
222     struct wait_item_s *q;
223     int i;
224
225     assert (opaque);
226     assert (handler);
227     
228     q = xtrycalloc ( 1, sizeof *q );
229     if ( !q )
230         return mk_error (Out_Of_Core);
231     q->inbound = inbound;
232     q->handler = handler;
233     q->handler_value = handler_value;
234     q->pid = pid;
235     q->ctx = ctx;
236     q->active = 1;
237
238     lock_table ();
239  again:  
240     for (i=0; i < fd_table_size; i++ ) {
241         if ( fd_table[i].fd == -1 ) {
242             fd_table[i].fd = fd;
243             fd_table[i].for_read = inbound;    
244             fd_table[i].for_write = !inbound;    
245             fd_table[i].signaled = 0;
246             fd_table[i].frozen = 0;
247             fd_table[i].opaque = q;
248             unlock_table ();
249             return 0;
250         }
251     }
252     if ( fd_table_size < 50 ) {
253         /* FIXME: We have to wait until there are no other readers of the 
254          * table, i.e that the io_select is not active in another thread */
255         struct io_select_fd_s *tmp;
256
257         tmp = xtryrealloc ( fd_table, (fd_table_size + 10) * sizeof *tmp );
258         if ( tmp ) {
259             for (i=0; i < 10; i++ )
260                 tmp[fd_table_size+i].fd = -1;
261             fd_table_size += i;
262             fd_table = tmp;
263             goto again;
264         }
265     }
266
267     unlock_table ();
268     xfree (q);
269     return mk_error (Too_Many_Procs);
270 }
271
272
273 void
274 _gpgme_freeze_fd ( int fd )
275 {
276     int i;
277
278     lock_table ();
279     for (i=0; i < fd_table_size; i++ ) {
280         if ( fd_table[i].fd == fd ) {
281             fd_table[i].frozen = 1;
282             DEBUG1 ("fd %d frozen", fd );
283             break;
284         }
285     }
286     unlock_table ();
287 }
288
289 void
290 _gpgme_thaw_fd ( int fd )
291 {
292     int i;
293
294     lock_table ();
295     for (i=0; i < fd_table_size; i++ ) {
296         if ( fd_table[i].fd == fd ) {
297             fd_table[i].frozen = 0;
298             DEBUG1 ("fd %d thawed", fd );
299             break;
300         }
301     }
302     unlock_table ();
303 }
304
305
306 /**
307  * gpgme_register_idle:
308  * @fnc: Callers idle function
309  * 
310  * Register a function with GPGME called by GPGME whenever it feels
311  * that is is idle.  NULL may be used to remove this function.
312  **/
313 void
314 gpgme_register_idle ( void (*fnc)(void) )
315 {
316     idle_function = fnc;
317 }
318
319
320 static void
321 run_idle ()
322 {
323     _gpgme_gpg_housecleaning ();
324     if (idle_function)
325         idle_function ();
326 }
327