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