Okay, it runs at least on Windows 95
[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/time.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30
31 #include "util.h"
32 #include "context.h"
33 #include "ops.h"
34 #include "wait.h"
35 #include "io.h"
36
37 /* Fixme: implement the following stuff to make the code MT safe.
38  * To avoid the need to link against a specific threads lib, such
39  * an implementation should require the caller to register a function
40  * which does this task.
41  * enter_crit() and leave_crit() are used to embrace an area of code
42  * which should be executed only by one thread at a time.
43  * lock_xxxx() and unlock_xxxx()  protect access to an data object.
44  *  */
45 #define enter_crit()    do { } while (0)
46 #define leave_crit()    do { } while (0)
47 #define lock_table()    do { } while (0)
48 #define unlock_table()  do { } while (0)
49
50
51 struct wait_item_s {
52     volatile int active;
53     int (*handler)(void*,int,int);
54     void *handler_value;
55     int pid;
56     int inbound;       /* this is an inbound data handler fd */
57     int exited;
58     int exit_status;  
59     int exit_signal;
60     GpgmeCtx ctx;
61 };
62
63 static int fd_table_size;
64 static struct io_select_fd_s *fd_table;
65
66 static int do_select ( void );
67
68
69 static struct wait_item_s *
70 queue_item_from_context ( GpgmeCtx ctx )
71 {
72     struct wait_item_s *q;
73     int i;
74
75     for (i=0; i < fd_table_size; i++ ) {
76         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->ctx == ctx )
77             return q;
78     }
79     return NULL;
80 }
81
82
83 static void
84 propagate_term_results ( const struct wait_item_s *first_q )
85 {
86     struct wait_item_s *q;
87     int i;
88     
89     for (i=0; i < fd_table_size; i++ ) {
90         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
91              && q != first_q && !q->exited
92              && q->pid == first_q->pid  ) {
93             q->exited = first_q->exited;
94             q->exit_status = first_q->exit_status;
95             q->exit_signal = first_q->exit_signal;
96         }
97     }
98 }
99
100 static int
101 count_active_fds ( int pid )
102 {
103     struct wait_item_s *q;
104     int i, count = 0;
105     
106     for (i=0; i < fd_table_size; i++ ) {
107         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
108              && q->active && q->pid == pid  ) 
109             count++;
110     }
111     return count;
112 }
113
114 static void
115 clear_active_fds ( int pid )
116 {
117     struct wait_item_s *q;
118     int i;
119     
120     for (i=0; i < fd_table_size; i++ ) {
121         if ( fd_table[i].fd != -1 && (q=fd_table[i].opaque)
122              && q->active && q->pid == pid  ) 
123             q->active = 0;
124     }
125 }
126
127
128 /* remove the given process from the queue */
129 static void
130 remove_process ( int pid )
131 {
132     struct wait_item_s *q;
133     int i;
134
135     for (i=0; i < fd_table_size; i++ ) {
136         if (fd_table[i].fd != -1 && (q=fd_table[i].opaque) && q->pid == pid ) {
137             xfree (q);
138             fd_table[i].opaque = NULL;
139             
140             if ( !fd_table[i].is_closed ) {
141                 _gpgme_io_close (fd_table[i].fd);
142                 fd_table[i].is_closed = 1;
143             }
144             fd_table[i].fd = -1;
145         }
146     }
147 }
148
149
150
151 /**
152  * gpgme_wait:
153  * @c: 
154  * @hang: 
155  * 
156  * Wait for a finished request, if @c is given the function does only
157  * wait on a finsihed request for that context, otherwise it will return
158  * on any request.  When @hang is true the function will wait, otherwise
159  * it will return immediately when there is no pending finished request.
160  * 
161  * Return value: Context of the finished request or NULL if @hang is false
162  *  and no (or the given) request has finished.
163  **/
164 GpgmeCtx 
165 gpgme_wait ( GpgmeCtx c, int hang ) 
166 {
167     return _gpgme_wait_on_condition ( c, hang, NULL );
168 }
169
170 GpgmeCtx 
171 _gpgme_wait_on_condition ( GpgmeCtx c, int hang, volatile int *cond )
172 {
173     struct wait_item_s *q;
174
175     do {
176         int did_work = do_select();
177
178         if ( cond && *cond )
179             hang = 0;
180
181         if ( !did_work ) {
182             /* We did no read/write - see whether the process is still
183              * alive */
184             assert (c); /* !c is not yet implemented */
185             q = queue_item_from_context ( c );
186             assert (q);
187             
188             if (q->exited) {
189                 /* this is the second time we reached this and we got no
190                  * more data from the pipe (which may happen to to buffering).
191                  * Set all FDs inactive.
192                  */
193                 clear_active_fds (q->pid);
194             }
195             else if ( _gpgme_io_waitpid (q->pid, 0,
196                                           &q->exit_status, &q->exit_signal)){
197                 q->exited = 1;     
198                 propagate_term_results (q);
199             }
200
201             if ( q->exited ) {
202                 if ( !count_active_fds (q->pid) ) {
203                     /* Hmmm, as long as we don't have a callback for
204                      * the exit status, we have no use for these
205                      * values and therefore we can remove this from
206                      * the queue */
207                     remove_process (q->pid);
208                     hang = 0;
209                 }
210             }
211         }
212     } while (hang);
213     return c;
214 }
215
216
217
218 /*
219  * We use this function to do the select stuff for all running
220  * gpgs.  A future version might provide a facility to delegate
221  * those selects to the GDK select stuff.
222  * This function must be called only by one thread!!
223  * Returns: 0 = nothing to run
224  *          1 = did run something 
225  */
226
227 static int
228 do_select ( void )
229 {
230     struct wait_item_s *q;
231     int i, n;
232     int any=0;
233     
234     n = _gpgme_io_select ( fd_table, fd_table_size );
235     if ( n <= 0 ) 
236         return 0; /* error or timeout */
237
238     for (i=0; i < fd_table_size /*&& n*/; i++ ) {
239         if ( fd_table[i].fd != -1 && fd_table[i].signaled ) {
240             q = fd_table[i].opaque;
241             assert (n);
242             n--;
243             if ( q->active )
244                 any = 1;
245             if ( q->active && q->handler (q->handler_value,
246                                           q->pid, fd_table[i].fd ) ) {
247                 q->active = 0;
248                 fd_table[i].for_read = 0;
249                 fd_table[i].for_write = 0;
250                 fd_table[i].is_closed = 1;
251             }
252         }
253     }
254     
255     return any;
256 }
257
258
259
260 /* 
261  * called by rungpg.c to register something for select()
262  */
263 GpgmeError
264 _gpgme_register_pipe_handler( void *opaque, 
265                               int (*handler)(void*,int,int),
266                               void *handler_value,
267                               int pid, int fd, int inbound )
268 {
269     GpgmeCtx ctx = opaque;
270     struct wait_item_s *q;
271     int i;
272
273     assert (opaque);
274     assert (handler);
275     
276     q = xtrycalloc ( 1, sizeof *q );
277     if ( !q )
278         return mk_error (Out_Of_Core);
279     q->inbound = inbound;
280     q->handler = handler;
281     q->handler_value = handler_value;
282     q->pid = pid;
283     q->ctx = ctx;
284     q->active = 1;
285
286     lock_table ();
287  again:  
288     for (i=0; i < fd_table_size; i++ ) {
289         if ( fd_table[i].fd == -1 ) {
290             fd_table[i].fd = fd;
291             fd_table[i].is_closed = 0;
292             fd_table[i].for_read = inbound;    
293             fd_table[i].for_write = !inbound;    
294             fd_table[i].signaled = 0;
295             fd_table[i].opaque = q;
296             unlock_table ();
297             return 0;
298         }
299     }
300     if ( fd_table_size < 50 ) {
301         /* FIXME: We have to wait until there are no other readers of the 
302          * table, i.e that the io_select is not active in another thread */
303         struct io_select_fd_s *tmp;
304
305         tmp = xtryrealloc ( fd_table, (fd_table_size + 10) * sizeof *tmp );
306         if ( tmp ) {
307             for (i=0; i < 10; i++ )
308                 tmp[fd_table_size+i].fd = -1;
309             fd_table_size += i;
310             fd_table = tmp;
311             goto again;
312         }
313     }
314
315     unlock_table ();
316     xfree (q);
317     return mk_error (Too_Many_Procs);
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335