More changes and and some new functions
[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     int exited;
57     int exit_status;  
58     int exit_signal;
59     GpgmeCtx ctx;
60 };
61
62 static int fd_table_size;
63 static struct io_select_fd_s *fd_table;
64
65 static void (*idle_function) (void);
66
67 static int do_select ( void );
68 static void run_idle (void);
69
70
71 static struct wait_item_s *
72 queue_item_from_context ( GpgmeCtx ctx )
73 {
74     struct wait_item_s *q;
75     int i;
76
77     for (i=0; i < fd_table_size; i++ ) {
78         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->ctx == ctx )
79             return q;
80     }
81     return NULL;
82 }
83
84
85 static void
86 propagate_term_results ( const struct wait_item_s *first_q )
87 {
88     struct wait_item_s *q;
89     int i;
90     
91     for (i=0; i < fd_table_size; i++ ) {
92         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
93              && q != first_q && !q->exited
94              && q->pid == first_q->pid  ) {
95             q->exited = first_q->exited;
96             q->exit_status = first_q->exit_status;
97             q->exit_signal = first_q->exit_signal;
98         }
99     }
100 }
101
102 static int
103 count_active_fds ( int pid )
104 {
105     struct wait_item_s *q;
106     int i, count = 0;
107     
108     for (i=0; i < fd_table_size; i++ ) {
109         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
110              && q->active && q->pid == pid  ) 
111             count++;
112     }
113     return count;
114 }
115
116 static void
117 clear_active_fds ( int pid )
118 {
119     struct wait_item_s *q;
120     int i;
121     
122     for (i=0; i < fd_table_size; i++ ) {
123         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
124              && q->active && q->pid == pid  ) 
125             q->active = 0;
126     }
127 }
128
129
130 /* remove the given process from the queue */
131 static void
132 remove_process ( int pid )
133 {
134     struct wait_item_s *q;
135     int i;
136
137     for (i=0; i < fd_table_size; i++ ) {
138         if (fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->pid == pid ) {
139             xfree (q);
140             fd_table[i].opaque = NULL;
141             
142             if ( !fd_table[i].is_closed ) {
143                 _gpgme_io_close (fd_table[i].fd);
144                 fd_table[i].is_closed = 1;
145             }
146             fd_table[i].fd = -1;
147         }
148     }
149 }
150
151
152
153 /**
154  * gpgme_wait:
155  * @c: 
156  * @hang: 
157  * 
158  * Wait for a finished request, if @c is given the function does only
159  * wait on a finsihed request for that context, otherwise it will return
160  * on any request.  When @hang is true the function will wait, otherwise
161  * it will return immediately when there is no pending finished request.
162  * 
163  * Return value: Context of the finished request or NULL if @hang is false
164  *  and no (or the given) request has finished.
165  **/
166 GpgmeCtx 
167 gpgme_wait ( GpgmeCtx c, int hang ) 
168 {
169     return _gpgme_wait_on_condition ( c, hang, NULL );
170 }
171
172 GpgmeCtx 
173 _gpgme_wait_on_condition ( GpgmeCtx c, int hang, volatile int *cond )
174 {
175     struct wait_item_s *q;
176
177     do {
178         int did_work = do_select();
179
180         if ( cond && *cond )
181             hang = 0;
182
183         if ( !did_work ) {
184             /* We did no read/write - see whether the process is still
185              * alive */
186             assert (c); /* !c is not yet implemented */
187             q = queue_item_from_context ( c );
188             assert (q);
189             
190             if (q->exited) {
191                 /* this is the second time we reached this and we got no
192                  * more data from the pipe (which may happen to to buffering).
193                  * Set all FDs inactive.
194                  */
195                 clear_active_fds (q->pid);
196             }
197             else if ( _gpgme_io_waitpid (q->pid, 0,
198                                           &q->exit_status, &q->exit_signal)){
199                 q->exited = 1;     
200                 propagate_term_results (q);
201             }
202
203             if ( q->exited ) {
204                 if ( !count_active_fds (q->pid) ) {
205                     /* Hmmm, as long as we don't have a callback for
206                      * the exit status, we have no use for these
207                      * values and therefore we can remove this from
208                      * the queue */
209                     remove_process (q->pid);
210                     hang = 0;
211                 }
212             }
213         }
214         if (hang)
215             run_idle ();
216     } while (hang);
217     return c;
218 }
219
220
221
222 /*
223  * We use this function to do the select stuff for all running
224  * gpgs.  A future version might provide a facility to delegate
225  * those selects to the GDK select stuff.
226  * This function must be called only by one thread!!
227  * Returns: 0 = nothing to run
228  *          1 = did run something 
229  */
230
231 static int
232 do_select ( void )
233 {
234     struct wait_item_s *q;
235     int i, n;
236     int any=0;
237     
238     n = _gpgme_io_select ( fd_table, fd_table_size );
239     if ( n <= 0 ) 
240         return 0; /* error or timeout */
241
242     for (i=0; i < fd_table_size && n; i++ ) {
243         if ( fd_table[i].fd != -1 && fd_table[i].signaled 
244              && !fd_table[i].frozen ) {
245             q = fd_table[i].opaque;
246             assert (n);
247             n--;
248             if ( q->active )
249                 any = 1;
250             if ( q->active && q->handler (q->handler_value,
251                                           q->pid, fd_table[i].fd ) ) {
252                 q->active = 0;
253                 fd_table[i].for_read = 0;
254                 fd_table[i].for_write = 0;
255                 fd_table[i].is_closed = 1;
256             }
257         }
258     }
259     
260     return any;
261 }
262
263
264
265 /* 
266  * called by rungpg.c to register something for select()
267  */
268 GpgmeError
269 _gpgme_register_pipe_handler ( void *opaque, 
270                               int (*handler)(void*,int,int),
271                               void *handler_value,
272                               int pid, int fd, int inbound )
273 {
274     GpgmeCtx ctx = opaque;
275     struct wait_item_s *q;
276     int i;
277
278     assert (opaque);
279     assert (handler);
280     
281     q = xtrycalloc ( 1, sizeof *q );
282     if ( !q )
283         return mk_error (Out_Of_Core);
284     q->inbound = inbound;
285     q->handler = handler;
286     q->handler_value = handler_value;
287     q->pid = pid;
288     q->ctx = ctx;
289     q->active = 1;
290
291     lock_table ();
292  again:  
293     for (i=0; i < fd_table_size; i++ ) {
294         if ( fd_table[i].fd == -1 ) {
295             fd_table[i].fd = fd;
296             fd_table[i].is_closed = 0;
297             fd_table[i].for_read = inbound;    
298             fd_table[i].for_write = !inbound;    
299             fd_table[i].signaled = 0;
300             fd_table[i].frozen = 0;
301             fd_table[i].opaque = q;
302             unlock_table ();
303             return 0;
304         }
305     }
306     if ( fd_table_size < 50 ) {
307         /* FIXME: We have to wait until there are no other readers of the 
308          * table, i.e that the io_select is not active in another thread */
309         struct io_select_fd_s *tmp;
310
311         tmp = xtryrealloc ( fd_table, (fd_table_size + 10) * sizeof *tmp );
312         if ( tmp ) {
313             for (i=0; i < 10; i++ )
314                 tmp[fd_table_size+i].fd = -1;
315             fd_table_size += i;
316             fd_table = tmp;
317             goto again;
318         }
319     }
320
321     unlock_table ();
322     xfree (q);
323     return mk_error (Too_Many_Procs);
324 }
325
326
327 void
328 _gpgme_freeze_fd ( int fd )
329 {
330     int i;
331
332     lock_table ();
333     for (i=0; i < fd_table_size; i++ ) {
334         if ( fd_table[i].fd == fd ) {
335             fd_table[i].frozen = 1;
336             /*fprintf (stderr, "** FD %d frozen\n", fd );*/
337             break;
338         }
339     }
340     unlock_table ();
341 }
342
343 void
344 _gpgme_thaw_fd ( int fd )
345 {
346     int i;
347
348     lock_table ();
349     for (i=0; i < fd_table_size; i++ ) {
350         if ( fd_table[i].fd == fd ) {
351             fd_table[i].frozen = 0;
352             /*fprintf (stderr, "** FD %d thawed\n", fd );*/
353             break;
354         }
355     }
356     unlock_table ();
357 }
358
359
360 /**
361  * gpgme_register_idle:
362  * @fnc: Callers idle function
363  * 
364  * Register a function with GPGME called by GPGME whenever it feels
365  * that is is idle.  NULL may be used to remove this function.
366  **/
367 void
368 gpgme_register_idle ( void (*fnc)(void) )
369 {
370     idle_function = fnc;
371 }
372
373
374 static void
375 run_idle ()
376 {
377     if (idle_function)
378         idle_function ();
379 }