s4:torture: Adapt KDC canon test to Heimdal upstream changes
[samba.git] / third_party / heimdal / lib / kadm5 / ipropd_common.c
1 /*
2  * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 #include "iprop.h"
35
36 #if defined(HAVE_FORK) && defined(HAVE_WAITPID)
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #endif
40
41 sig_atomic_t exit_flag;
42
43 static RETSIGTYPE
44 sigterm(int sig)
45 {
46     exit_flag = sig;
47 }
48
49 void
50 setup_signal(void)
51 {
52 #ifdef HAVE_SIGACTION
53     {
54         struct sigaction sa;
55
56         sa.sa_flags = 0;
57         sa.sa_handler = sigterm;
58         sigemptyset(&sa.sa_mask);
59
60         sigaction(SIGINT, &sa, NULL);
61         sigaction(SIGTERM, &sa, NULL);
62         sigaction(SIGXCPU, &sa, NULL);
63
64         sa.sa_handler = SIG_IGN;
65         sigaction(SIGPIPE, &sa, NULL);
66     }
67 #else
68     signal(SIGINT, sigterm);
69     signal(SIGTERM, sigterm);
70 #ifndef NO_SIGXCPU
71     signal(SIGXCPU, sigterm);
72 #endif
73 #ifndef NO_SIGPIPE
74     signal(SIGPIPE, SIG_IGN);
75 #endif
76 #endif
77 }
78
79 /*
80  * Fork a child to run the service, and restart it if it dies.
81  *
82  * Returns -1 if not supported, else a file descriptor that the service
83  * should select() for.  Any events on that file descriptor should cause
84  * the caller to exit immediately, as that means that the restarter
85  * exited.
86  *
87  * The service's normal exit status values should be should be taken
88  * from enum ipropd_exit_code.  IPROPD_FATAL causes the restarter to
89  * stop restarting the service and to exit.
90  *
91  * A count of restarts is output via the `countp' argument, if it is
92  * non-NULL.  This is useful for testing this function (e.g., kill the
93  * restarter after N restarts and check that the child gets the signal
94  * sent to it).
95  *
96  * This requires fork() and waitpid() (otherwise returns -1).  Ignoring
97  * SIGCHLD, of course, would be bad.
98  *
99  * We could support this on Windows by spawning a child with mostly the
100  * same arguments as the restarter process.
101  */
102 int
103 restarter(krb5_context context, size_t *countp)
104 {
105 #if defined(HAVE_FORK) && defined(HAVE_WAITPID)
106     struct timeval tmout;
107     pid_t pid = -1;
108     pid_t wpid = -1;
109     int status;
110     int fds[2];
111     int fds2[2];
112     size_t count = 0;
113     fd_set readset;
114
115     fds[0] = -1;
116     fds[1] = -1;
117     fds2[0] = -1;
118     fds2[1] = -1;
119
120     signal(SIGCHLD, SIG_DFL);
121
122     while (!exit_flag) {
123         /* Close the pipe ends we keep open */
124         if (fds[1] != -1)
125             (void) close(fds[1]);
126         if (fds2[0] != -1)
127             (void) close(fds2[1]);
128
129         /* A pipe so the child can detect the parent's death */
130         if (pipe(fds) == -1) {
131             krb5_err(context, 1, errno,
132                      "Could not setup pipes in service restarter");
133         }
134
135         /* A pipe so the parent can detect the child's death */
136         if (pipe(fds2) == -1) {
137             krb5_err(context, 1, errno,
138                      "Could not setup pipes in service restarter");
139         }
140
141         fflush(stdout);
142         fflush(stderr);
143
144         pid = fork();
145         if (pid == -1)
146             krb5_err(context, 1, errno, "Could not fork in service restarter");
147         if (pid == 0) {
148             if (countp != NULL)
149                 *countp = count;
150             (void) close(fds[1]);
151             (void) close(fds2[0]);
152             return fds[0];
153         }
154
155         count++;
156
157         (void) close(fds[0]);
158         (void) close(fds2[1]);
159
160         do {
161             wpid = waitpid(pid, &status, 0);
162         } while (wpid == -1 && errno == EINTR && !exit_flag);
163         if (wpid == -1 && errno == EINTR)
164             break; /* We were signaled; gotta kill the child and exit */
165         if (wpid == -1) {
166             if (errno != ECHILD) {
167                 warn("waitpid() failed; killing restarter's child process");
168                 kill(pid, SIGTERM);
169             }
170             krb5_err(context, 1, errno, "restarter failed waiting for child");
171         }
172
173         assert(wpid == pid);
174         wpid = -1;
175         pid = -1;
176         if (WIFEXITED(status)) {
177             switch (WEXITSTATUS(status)) {
178             case IPROPD_DONE:
179                 exit(0);
180             case IPROPD_RESTART_SLOW:
181                 if (exit_flag)
182                     exit(1);
183                 krb5_warnx(context, "Waiting 2 minutes to restart");
184                 sleep(120);
185                 continue;
186             case IPROPD_FATAL:
187                 krb5_errx(context, WEXITSTATUS(status),
188                          "Sockets and pipes not supported for "
189                          "iprop log files");
190             case IPROPD_RESTART:
191             default:
192                 if (exit_flag)
193                     exit(1);
194                 /* Add exponential backoff (with max backoff)? */
195                 krb5_warnx(context, "Waiting 30 seconds to restart");
196                 sleep(30);
197                 continue;
198             }
199         }
200         /* else */
201         krb5_warnx(context, "Child was killed; waiting 30 seconds to restart");
202         sleep(30);
203     }
204
205     if (pid == -1)
206         exit(0); /* No dead child to reap; done */
207
208     assert(pid > 0);
209     if (wpid != pid) {
210         warnx("Interrupted; killing child (pid %ld) with %d",
211               (long)pid, exit_flag);
212         krb5_warnx(context, "Interrupted; killing child (pid %ld) with %d",
213                    (long)pid, exit_flag);
214         kill(pid, exit_flag);
215
216         /* Wait up to one second for the child */
217         tmout.tv_sec = 1;
218         tmout.tv_usec = 0;
219         FD_ZERO(&readset);
220         FD_SET(fds2[0], &readset);
221         /* We don't care why select() returns */
222         (void) select(fds2[0] + 1, &readset, NULL, NULL, &tmout);
223         /*
224          * We haven't reaped the child yet; if it's a zombie, then
225          * SIGKILLing it won't hurt.  If it's not a zombie yet, well,
226          * we're out of patience.
227          */
228         kill(pid, SIGKILL);
229         do {
230             wpid = waitpid(pid, &status, 0);
231         } while (wpid != pid && errno == EINTR);
232         if (wpid == -1)
233             krb5_err(context, 1, errno, "restarter failed waiting for child");
234     }
235
236     /* Finally, the child is dead and reaped */
237     if (WIFEXITED(status))
238         exit(WEXITSTATUS(status));
239     if (WIFSIGNALED(status)) {
240         switch (WTERMSIG(status)) {
241         case SIGTERM:
242         case SIGXCPU:
243         case SIGINT:
244             exit(0);
245         default:
246             /*
247              * Attempt to set the same exit status for the parent as for
248              * the child.
249              */
250             kill(getpid(), WTERMSIG(status));
251             /*
252              * We can get past the self-kill if we inherited a SIG_IGN
253              * disposition that the child reset to SIG_DFL.
254              */
255         }
256     }
257     exit(1);
258 #else
259     if (countp != NULL)
260         *countp = 0;
261     errno = ENOTSUP;
262     return -1;
263 #endif
264 }
265