a84c746506cb8054fa40f364387aaf1a1d244a61
[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 #include <sys/wait.h>
31
32 #include "util.h"
33 #include "context.h"
34 #include "wait.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_queue()    do { } while (0)
47 #define unlock_queue()  do { } while (0)
48
49 struct wait_queue_item_s {
50     struct wait_queue_item_s *next;
51     volatile int used; 
52     volatile int active;
53     int (*handler)(void*,pid_t,int);
54     void *handler_value;
55     pid_t pid;
56     int   fd;  
57     int   inbound;       /* this is an inbound data handler fd */
58
59     int exited;
60     int exit_status;  
61     int exit_signal;
62
63     GpgmeCtx ctx;
64 };
65
66
67 static struct wait_queue_item_s wait_queue[SIZEOF_WAIT_QUEUE];
68
69 static int the_big_select ( void );
70
71
72 static void
73 init_wait_queue (void)
74 {
75     int i;
76     static int initialized = 0;
77
78     if ( initialized )  /* FIXME: This leads to a race */
79         return;
80
81     lock_queue ();
82     for (i=1; i < SIZEOF_WAIT_QUEUE; i++ )
83         wait_queue[i-1].next = &wait_queue[i];
84     initialized = 1;
85     unlock_queue();
86 }
87
88 static struct wait_queue_item_s *
89 queue_item_from_context ( GpgmeCtx ctx )
90 {
91     struct wait_queue_item_s *q;
92
93     for (q=wait_queue; q; q = q->next) {
94         if ( q->used && q->ctx == ctx )
95             return q;
96     }
97     return NULL;
98 }
99
100
101 static void
102 propagate_term_results ( const struct wait_queue_item_s *first_q )
103 {
104     struct wait_queue_item_s *q;
105     
106     for (q=wait_queue; q; q = q->next) {
107         if ( q->used && q != first_q && !q->exited
108              && q->pid == first_q->pid  ) {
109             q->exited = first_q->exited;
110             q->exit_status = first_q->exit_status;
111             q->exit_signal = first_q->exit_signal;
112         }
113     }
114 }
115
116 static int
117 count_active_fds ( pid_t pid )
118 {
119     struct wait_queue_item_s *q;
120     int count = 0;
121     
122     for (q=wait_queue; q; q = q->next) {
123         if ( q->used && q->active && q->pid == pid  ) 
124             count++;
125     }
126     return count;
127 }
128
129
130 /* remove the given process from the queue */
131 static void
132 remove_process ( pid_t pid )
133 {
134     struct wait_queue_item_s *q;
135     
136     for (q=wait_queue; q; q = q->next) {
137         if ( q->used ) {
138             close (q->fd);
139             q->handler = NULL;
140             q->ctx = NULL;
141             q->used = 0;
142         }
143     }
144 }
145
146
147
148 /**
149  * gpgme_wait:
150  * @c: 
151  * @hang: 
152  * 
153  * Wait for a finished request, if @c is given the function does only
154  * wait on a finsihed request for that context, otherwise it will return
155  * on any request.  When @hang is true the function will wait, otherwise
156  * it will return immediately when there is no pending finished request.
157  * 
158  * Return value: Context of the finished request or NULL if @hang is false
159  *  and no (or the given) request has finished.
160  **/
161 GpgmeCtx 
162 gpgme_wait ( GpgmeCtx c, int hang )
163 {
164     struct wait_queue_item_s *q;
165
166     init_wait_queue ();
167     do {
168         if ( !the_big_select() ) {
169             int status;
170
171             /* We did no read/write - see whether this process is still
172              * alive */
173             assert (c); /* !c is not yet implemented */
174             q = queue_item_from_context ( c );
175             assert (q);
176             
177             if (q->exited)
178                 ;
179             else if ( waitpid ( q->pid, &status, WNOHANG ) == q->pid ) {
180                 q->exited = 1;     
181                 if ( WIFSIGNALED (status) ) {
182                     q->exit_status = 4; /* Need some value here */
183                     q->exit_signal = WTERMSIG (status);
184                 }
185                 else if ( WIFEXITED (status) ) {
186                     q->exit_status = WEXITSTATUS (status);
187                 }
188                 else {
189                     q->exited++;
190                     q->exit_status = 4;
191                 }
192                 propagate_term_results (q);
193             }
194
195             if ( q->exited ) {
196                 if ( !count_active_fds (q->pid) ) {
197                     /* Hmmm, as long as we don't have a callback for
198                      * the exit status, we have no use for these
199                      * values and therefore we can remove this from
200                      * the queue */
201                     remove_process (q->pid);
202                     hang = 0;
203                 }
204             }
205         }
206     } while (hang);
207     return c;
208 }
209
210
211
212 /*
213  * We use this function to do the select stuff for all running
214  * gpgs.  A future version might provide a facility to delegate
215  * those selects to the GDK select stuff.
216  * This function must be called only by one thread!!
217  * FIXME: The data structures and  algorithms are stupid.
218  * Returns: 0 = nothing to run
219  *          1 = did run something 
220  */
221
222 static int
223 the_big_select ( void )
224 {
225     static fd_set readfds;
226     static fd_set writefds;
227     struct wait_queue_item_s *q;
228     int max_fd, n;
229     struct timeval timeout = { 1, 0 }; /* Use a one second timeout */
230     
231     FD_ZERO ( &readfds );
232     FD_ZERO ( &writefds );
233     max_fd = 0;
234     lock_queue ();
235     for ( q = wait_queue; q; q = q->next ) {
236         if ( q->used && q->active ) {
237             if (q->inbound) {
238                 assert ( !FD_ISSET ( q->fd, &readfds ) );
239                 FD_SET ( q->fd, &readfds );
240             }
241             else {
242                 assert ( !FD_ISSET ( q->fd, &writefds ) );
243                 FD_SET ( q->fd, &writefds );
244             }
245             if ( q->fd > max_fd )
246                 max_fd = q->fd;
247           }
248     }
249     unlock_queue ();
250
251
252     n = select ( max_fd+1, &readfds, &writefds, NULL, &timeout );
253     if ( n <= 0 ) {
254         if ( n && errno != EINTR ) {
255             fprintf (stderr, "the_big_select: select failed: %s\n",
256                      strerror (errno) );
257         }
258         return 0;
259     }
260
261     /* something has to be done.  Go over the queue and call
262      * the handlers */
263  restart:
264     while ( n ) {
265         lock_queue ();
266         for ( q = wait_queue; q; q = q->next ) {
267             if ( q->used && q->active 
268                  && FD_ISSET (q->fd, q->inbound? &readfds : &writefds ) ) {
269                 FD_CLR (q->fd, q->inbound? &readfds : &writefds );
270                 assert (n);
271                 n--;
272                 unlock_queue ();
273                 if ( q->handler (q->handler_value, q->pid, q->fd ) )
274                     q->active = 0;
275                 goto restart;
276             }
277         }
278         unlock_queue ();
279     }
280     return 1;
281 }
282
283
284
285 /* 
286  * called by rungpg.c to register something for select()
287  */
288 GpgmeError
289 _gpgme_register_pipe_handler( void *opaque, 
290                               int (*handler)(void*,pid_t,int),
291                               void *handler_value,
292                               pid_t pid, int fd, int inbound )
293 {
294     GpgmeCtx ctx = opaque;
295     struct wait_queue_item_s *q;
296
297     init_wait_queue();
298     assert (opaque);
299     assert (handler);
300     
301     lock_queue ();
302     for ( q = wait_queue; q; q = q->next ) {
303         if ( !q->used ) {
304             q->used = 1;
305             q->active = 0;
306             break;
307         }
308     }
309     unlock_queue ();
310     if ( !q ) 
311         return mk_error (Too_Many_Procs);
312
313     q->fd = fd;
314     q->inbound = inbound;
315     q->handler = handler;
316     q->handler_value = handler_value;
317     q->pid = pid;
318     q->ctx = ctx;
319     
320     /* and enable this entry for the next select */
321     q->exited = 0;
322     q->active = 1;
323     return 0;
324 }
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341