Sanity, take 2.
[platform/upstream/rpm.git] / rpmio / rpmsq.c
1 /** \ingroup rpmio
2  * \file rpmio/rpmsq.c
3  */
4
5 #include "system.h"
6                                                                                 
7 #if defined(HAVE_PTHREAD_H) && !defined(__LCLINT__)
8
9 #include <pthread.h>
10
11 #define DO_LOCK()       pthread_mutex_lock(&rpmsigTbl_lock);
12 #define DO_UNLOCK()     pthread_mutex_unlock(&rpmsigTbl_lock);
13 #define INIT_LOCK()     \
14      {  pthread_mutexattr_t attr; \
15         pthread_mutexattr_init(&attr); \
16         pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
17         pthread_mutex_init (&rpmsigTbl_lock, &attr); \
18         pthread_mutexattr_destroy(&attr); \
19         rpmsigTbl_sigchld->active = 0; \
20      }
21 #define ADD_REF(__tbl)  (__tbl)->active++
22 #define SUB_REF(__tbl)  --(__tbl)->active
23 #define CLEANUP_HANDLER(__handler, __arg, __oldtypeptr) \
24         pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, (__oldtypeptr)); \
25         pthread_cleanup_push((__handler), (__arg));
26 #define CLEANUP_RESET(__execute, __oldtype) \
27         pthread_cleanup_pop(__execute); \
28         pthread_setcanceltype ((__oldtype), &(__oldtype));
29
30 #define SAME_THREAD(_a, _b)     pthread_equal(((pthread_t)_a), ((pthread_t)_b))
31
32 #define ME()    ((void *)pthread_self())
33
34 #else
35
36 #define DO_LOCK()
37 #define DO_UNLOCK()
38 #define INIT_LOCK()
39 #define ADD_REF(__tbl)
40 #define SUB_REF(__tbl)
41 #define CLEANUP_HANDLER(__handler, __arg, __oldtypeptr)
42 #define CLEANUP_RESET(__execute, __oldtype)
43
44 #define SAME_THREAD(_a, _b)     (42)
45
46 #define ME()    (((void *))getpid())
47
48 #endif  /* HAVE_PTHREAD_H */
49
50 #include <rpmsq.h>
51
52 #include "debug.h"
53
54 #define _RPMSQ_DEBUG    0
55 /*@unchecked@*/
56 int _rpmsq_debug = _RPMSQ_DEBUG;
57
58 /*@unchecked@*/
59 static struct rpmsqElem rpmsqRock;
60 /*@unchecked@*/
61 rpmsq rpmsqQueue = &rpmsqRock;
62
63 int rpmsqInsert(void * elem, void * prev)
64 {
65     rpmsq sq = (rpmsq) elem;
66     int ret = -1;
67
68     if (sq != NULL) {
69 #ifdef _RPMSQ_DEBUG
70 /*@-modfilesys@*/
71 if (_rpmsq_debug)
72 fprintf(stderr, "    Insert(%p): %p\n", ME(), sq);
73 /*@=modfilesys@*/
74 #endif
75         ret = sighold(SIGCHLD);
76         if (ret == 0) {
77             sq->child = 0;
78             sq->reaped = 0;
79             sq->status = 0;
80             sq->reaper = 1;
81             sq->pipes[0] = sq->pipes[1] = -1;
82
83             sq->id = ME();
84             ret = pthread_mutex_init(&sq->mutex, NULL);
85             ret = pthread_cond_init(&sq->cond, NULL);
86             insque(elem, (prev ? prev : rpmsqQueue));
87             ret = sigrelse(SIGCHLD);
88         }
89     }
90     return ret;
91 }
92
93 int rpmsqRemove(void * elem)
94 {
95     rpmsq sq = (rpmsq) elem;
96     int ret = -1;
97
98     if (elem != NULL) {
99
100 #ifdef _RPMSQ_DEBUG
101 /*@-modfilesys@*/
102 if (_rpmsq_debug)
103 fprintf(stderr, "    Remove(%p): %p\n", ME(), sq);
104 /*@=modfilesys@*/
105 #endif
106         ret = sighold (SIGCHLD);
107         if (ret == 0) {
108             remque(elem);
109             ret = pthread_cond_destroy(&sq->cond);
110             ret = pthread_mutex_destroy(&sq->mutex);
111             sq->id = NULL;
112             if (sq->pipes[1])   close(sq->pipes[1]);
113             if (sq->pipes[0])   close(sq->pipes[0]);
114             sq->pipes[0] = sq->pipes[1] = -1;
115             sq->reaper = 1;
116             sq->status = 0;
117             sq->reaped = 0;
118             sq->child = 0;
119             ret = sigrelse(SIGCHLD);
120         }
121     }
122     return ret;
123 }
124
125 /*@unchecked@*/
126 sigset_t rpmsqCaught;
127
128 /*@unchecked@*/
129 static pthread_mutex_t rpmsigTbl_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
130
131 /*@unchecked@*/
132 /*@-fullinitblock@*/
133 static struct rpmsig_s {
134     int signum;
135     void (*handler) (int signum, siginfo_t * info, void * context);
136     int active;
137     struct sigaction oact;
138 } rpmsigTbl[] = {
139     { SIGINT,   rpmsqAction },
140 #define rpmsigTbl_sigint        (&rpmsigTbl[0])
141     { SIGQUIT,  rpmsqAction },
142 #define rpmsigTbl_sigquit       (&rpmsigTbl[1])
143     { SIGCHLD,  rpmsqAction },
144 #define rpmsigTbl_sigchld       (&rpmsigTbl[2])
145     { SIGHUP,   rpmsqAction },
146 #define rpmsigTbl_sighup        (&rpmsigTbl[3])
147     { SIGTERM,  rpmsqAction },
148 #define rpmsigTbl_sigterm       (&rpmsigTbl[4])
149     { SIGPIPE,  rpmsqAction },
150 #define rpmsigTbl_sigpipe       (&rpmsigTbl[5])
151     { -1,       NULL },
152 };
153 /*@=fullinitblock@*/
154
155 /*@-incondefs@*/
156 void rpmsqAction(int signum, siginfo_t * info, void * context)
157 {
158     int save = errno;
159     rpmsig tbl;
160
161     for (tbl = rpmsigTbl; tbl->signum >= 0; tbl++) {
162         if (tbl->signum != signum)
163             continue;
164
165         (void) sigaddset(&rpmsqCaught, signum);
166
167         switch (signum) {
168         case SIGCHLD:
169             while (1) {
170                 rpmsq sq;
171                 int status = 0;
172                 pid_t reaped = waitpid(0, &status, WNOHANG);
173
174                 /* XXX errno set to ECHILD/EINVAL/EINTR. */
175                 if (reaped <= 0)
176                     /*@innerbreak@*/ break;
177
178                 /* XXX insque(3)/remque(3) are dequeue, not ring. */
179                 for (sq = rpmsqQueue->q_forw;
180                      sq != NULL && sq != rpmsqQueue;
181                      sq = sq->q_forw)
182                 {
183                     if (sq->child != reaped)
184                         /*@innercontinue@*/ continue;
185                     sq->reaped = reaped;
186                     sq->status = status;
187                     (void) pthread_cond_signal(&sq->cond);
188                     /*@innerbreak@*/ break;
189                 }
190             }
191             /*@switchbreak@*/ break;
192         default:
193             /*@switchbreak@*/ break;
194         }
195         break;
196     }
197     errno = save;
198 }
199 /*@=incondefs@*/
200
201 int rpmsqEnable(int signum, /*@null@*/ rpmsqAction_t handler)
202 {
203     int tblsignum = (signum >= 0 ? signum : -signum);
204     struct sigaction sa;
205     rpmsig tbl;
206     int ret = -1;
207
208     DO_LOCK ();
209     if (rpmsqQueue->id == NULL)
210         rpmsqQueue->id = ME();
211     for (tbl = rpmsigTbl; tbl->signum >= 0; tbl++) {
212         if (tblsignum != tbl->signum)
213             continue;
214
215         if (signum >= 0) {                      /* Enable. */
216             if (ADD_REF(tbl) <= 0) {
217                 (void) sigdelset(&rpmsqCaught, tbl->signum);
218                 sigemptyset (&sa.sa_mask);
219                 sa.sa_flags = SA_SIGINFO;
220                 sa.sa_sigaction = (handler != NULL ? handler : tbl->handler);
221                 if (sigaction(tbl->signum, &sa, &tbl->oact) < 0) {
222                     SUB_REF(tbl);
223                     break;
224                 }
225                 tbl->active = 1;                /* XXX just in case */
226                 if (handler != NULL)
227                     tbl->handler = handler;
228             }
229         } else {                                /* Disable. */
230             if (SUB_REF(tbl) <= 0) {
231                 if (sigaction(tbl->signum, &tbl->oact, NULL) < 0)
232                     break;
233                 tbl->active = 0;                /* XXX just in case */
234                 tbl->handler = (handler != NULL ? handler : rpmsqAction);
235             }
236         }
237         ret = tbl->active;
238         break;
239     }
240     DO_UNLOCK ();
241     return ret;
242 }
243
244 pid_t rpmsqFork(rpmsq sq)
245 {
246     pid_t pid;
247     int xx;
248
249     if (sq->reaper) {
250         xx = rpmsqInsert(sq, NULL);
251 #ifdef _RPMSQ_DEBUG
252 /*@-modfilesys@*/
253 if (_rpmsq_debug)
254 fprintf(stderr, "    Enable(%p): %p\n", ME(), sq);
255 /*@=modfilesys@*/
256 #endif
257         xx = rpmsqEnable(SIGCHLD, NULL);
258     }
259
260     xx = pipe(sq->pipes);
261
262     xx = sighold(SIGCHLD);
263
264     pid = fork();
265     if (pid < (pid_t) 0) {              /* fork failed.  */
266         xx = close(sq->pipes[0]);
267         xx = close(sq->pipes[1]);
268         sq->pipes[0] = sq->pipes[1] = -1;
269         goto out;
270     } else if (pid == (pid_t) 0) {      /* Child. */
271         int yy;
272
273         /* Block to permit parent to wait. */
274         xx = close(sq->pipes[1]);
275         xx = read(sq->pipes[0], &yy, sizeof(yy));
276         xx = close(sq->pipes[0]);
277         sq->pipes[0] = sq->pipes[1] = -1;
278
279 #ifdef _RPMSQ_DEBUG
280 /*@-modfilesys@*/
281 if (_rpmsq_debug)
282 fprintf(stderr, "     Child(%p): %p child %d\n", ME(), sq, getpid());
283 /*@=modfilesys@*/
284 #endif
285
286     } else {                            /* Parent. */
287
288         sq->child = pid;
289
290 #ifdef _RPMSQ_DEBUG
291 /*@-modfilesys@*/
292 if (_rpmsq_debug)
293 fprintf(stderr, "    Parent(%p): %p child %d\n", ME(), sq, sq->child);
294 /*@=modfilesys@*/
295 #endif
296
297         /* Unblock child. */
298         xx = close(sq->pipes[0]);
299         xx = close(sq->pipes[1]);
300         sq->pipes[0] = sq->pipes[1] = -1;
301
302     }
303
304 out:
305     xx = sigrelse(SIGCHLD);
306     return sq->child;
307 }
308
309 /**
310  * Wait for child process to be reaped, and unregister SIGCHLD handler.
311  * @param sq            scriptlet queue element
312  * @return              0 on success
313  */
314 static int rpmsqWaitUnregister(rpmsq sq)
315         /*@globals fileSystem, internalState @*/
316         /*@modifies fileSystem, internalState @*/
317 {
318     int same_thread = 0;
319     int ret = 0;
320     int xx;
321
322     if (same_thread) ret = sighold(SIGCHLD);
323
324     /*@-infloops@*/
325     while (ret == 0 && sq->reaped != sq->child) {
326         if (same_thread) {
327             ret = sigpause(SIGCHLD);
328         } else {
329             ret = pthread_mutex_lock(&sq->mutex);
330             ret = pthread_cond_wait(&sq->cond, &sq->mutex);
331             xx = pthread_mutex_unlock(&sq->mutex);
332         }
333     }
334     /*@=infloops@*/
335
336     if (same_thread) xx = sigrelse(SIGCHLD);
337
338 #ifdef _RPMSQ_DEBUG
339 /*@-modfilesys@*/
340 if (_rpmsq_debug)
341 fprintf(stderr, "      Wake(%p): %p child %d reaper %d ret %d\n", ME(), sq, sq->child, sq->reaper, ret);
342 /*@=modfilesys@*/
343 #endif
344
345     xx = rpmsqRemove(sq);
346     xx = rpmsqEnable(-SIGCHLD, NULL);
347 #ifdef _RPMSQ_DEBUG
348 /*@-modfilesys@*/
349 if (_rpmsq_debug)
350 fprintf(stderr, "   Disable(%p): %p\n", ME(), sq);
351 /*@=modfilesys@*/
352 #endif
353
354     return ret;
355 }
356
357 pid_t rpmsqWait(rpmsq sq)
358 {
359
360 #ifdef _RPMSQ_DEBUG
361 /*@-modfilesys@*/
362 if (_rpmsq_debug)
363 fprintf(stderr, "      Wait(%p): %p child %d reaper %d\n", ME(), sq, sq->child, sq->reaper);
364 /*@=modfilesys@*/
365 #endif
366
367     if (sq->reaper) {
368         (void) rpmsqWaitUnregister(sq);
369     } else {
370         pid_t reaped;
371         int status;
372         do {
373             reaped = waitpid(sq->child, &status, 0);
374         } while (reaped >= 0 && reaped != sq->child);
375         sq->reaped = reaped;
376         sq->status = status;
377 #ifdef _RPMSQ_DEBUG
378 /*@-modfilesys@*/
379 if (_rpmsq_debug)
380 fprintf(stderr, "   Waitpid(%p): %p child %d reaped %d\n", ME(), sq, sq->child, sq->reaped);
381 /*@=modfilesys@*/
382 #endif
383     }
384
385 #ifdef _RPMSQ_DEBUG
386 /*@-modfilesys@*/
387 if (_rpmsq_debug)
388 fprintf(stderr, "      Fini(%p): %p child %d status 0x%x\n", ME(), sq, sq->child, sq->status);
389 /*@=modfilesys@*/
390 #endif
391
392     return sq->reaped;
393 }
394
395 int rpmsqThread(void * (*start) (void * arg), void * arg)
396 {
397     pthread_t pth;
398     int ret;
399
400     ret = pthread_create(&pth, NULL, start, arg);
401     if (ret == 0) {
402 #if 0
403 fprintf(stderr, "    Thread(%p): %p\n", ME(), pth);
404 #endif
405         ret = pthread_join(pth, NULL);
406     }
407     return ret;
408 }
409
410 /**
411  * SIGCHLD cancellation handler.
412  */
413 static void
414 sigchld_cancel (void *arg)
415 {
416     pid_t child = *(pid_t *) arg;
417     pid_t result;
418
419     (void) kill(child, SIGKILL);
420
421     do {
422         result = waitpid(child, NULL, 0);
423     } while (result == (pid_t)-1 && errno == EINTR);
424
425     DO_LOCK ();
426     if (SUB_REF (rpmsigTbl_sigchld) == 0) {
427         (void) rpmsqEnable(-SIGQUIT, NULL);
428         (void) rpmsqEnable(-SIGINT, NULL);
429     }
430     DO_UNLOCK ();
431 }
432
433 /**
434  * Execute a command, returning its status.
435  */
436 int
437 rpmsqExecve (const char ** argv)
438 {
439     int oldtype;
440     int status = -1;
441     pid_t pid;
442     pid_t result;
443     sigset_t newMask, oldMask;
444     rpmsq sq = memset(alloca(sizeof(*sq)), 0, sizeof(*sq));
445
446     DO_LOCK ();
447     if (ADD_REF (rpmsigTbl_sigchld) == 0) {
448         if (rpmsqEnable(SIGINT, NULL) < 0) {
449             SUB_REF (rpmsigTbl_sigchld);
450             goto out;
451         }
452         if (rpmsqEnable(SIGQUIT, NULL) < 0) {
453             SUB_REF (rpmsigTbl_sigchld);
454             goto out_restore_sigint;
455         }
456     }
457     DO_UNLOCK ();
458
459     sigemptyset (&newMask);
460     sigaddset (&newMask, SIGCHLD);
461     if (sigprocmask (SIG_BLOCK, &newMask, &oldMask) < 0) {
462         DO_LOCK ();
463         if (SUB_REF (rpmsigTbl_sigchld) == 0)
464             goto out_restore_sigquit_and_sigint;
465         goto out;
466     }
467
468     CLEANUP_HANDLER(sigchld_cancel, &pid, &oldtype);
469
470     pid = fork ();
471     if (pid < (pid_t) 0) {              /* fork failed.  */
472         goto out;
473     } else if (pid == (pid_t) 0) {      /* Child. */
474
475         /* Restore the signals.  */
476         (void) sigaction (SIGINT, &rpmsigTbl_sigint->oact, NULL);
477         (void) sigaction (SIGQUIT, &rpmsigTbl_sigquit->oact, NULL);
478         (void) sigprocmask (SIG_SETMASK, &oldMask, NULL);
479
480         /* Reset rpmsigTbl lock and refcnt. */
481         INIT_LOCK ();
482
483         (void) execve (argv[0], (char *const *) argv, environ);
484         _exit (127);
485     } else {                            /* Parent. */
486         do {
487             result = waitpid(pid, &status, 0);
488         } while (result == (pid_t)-1 && errno == EINTR);
489         if (result != pid)
490             status = -1;
491     }
492
493     CLEANUP_RESET(0, oldtype);
494
495     DO_LOCK ();
496     if ((SUB_REF (rpmsigTbl_sigchld) == 0 &&
497         (rpmsqEnable(-SIGINT, NULL) < 0 || rpmsqEnable (-SIGQUIT, NULL) < 0))
498       || sigprocmask (SIG_SETMASK, &oldMask, NULL) != 0)
499     {
500         status = -1;
501     }
502     goto out;
503
504 out_restore_sigquit_and_sigint:
505     (void) rpmsqEnable(-SIGQUIT, NULL);
506 out_restore_sigint:
507     (void) rpmsqEnable(-SIGINT, NULL);
508 out:
509     DO_UNLOCK ();
510     return status;
511 }