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