[PATCH] daemon/parachute & co for all boxes

Alexander Malysh <[email protected]>
Newsgroups gmane.comp.mobile.kannel.devel
Organization Centrium GmbH
Message-ID <[email protected]>
Hi List,

attached you can find a patch that adds some usefull command line parameters 
for all boxes.
1) -P/--parachute : start watcher father process that catch and restart 
crashed child process
2) -X/--panic-script : execute this script if child process crashed
3) -u/--user : change process user-id (security)
4) -p/--pid-file : write PID into the given file
5) -d/--daemonize : daemonize the process

Comments and votes please!

-- 
Best regards / Mit besten Grüßen aus Düsseldorf

Dipl.-Ing.
Alexander Malysh
___________________________________________

Centrium GmbH
Vogelsanger Weg 80
40470 Düsseldorf

Fon: +49 (0211) 74 84 51 80
Fax: +49 (0211) 277 49 109

email: [email protected]
web: www.centrium.de
msn: [email protected]
icq: 98063111
___________________________________________

Please avoid sending me Word, Excel or PowerPoint attachments.
See http://www.fsf.org/philosophy/no-word-attachments.html
parachute-daemon.patch (text/x-diff, 17.1 KB)
Index: gw/bearerbox.c
===================================================================
RCS file: /home/cvs/gateway/gw/bearerbox.c,v
retrieving revision 1.150
diff -a -u -r1.150 bearerbox.c
--- gw/bearerbox.c	24 Nov 2003 20:08:24 -0000	1.150
+++ gw/bearerbox.c	15 Jan 2004 13:49:41 -0000
@@ -335,10 +335,6 @@
 	bb_status = BB_SUSPENDED;
     else if (strcmp(argv[i], "-I")==0 || strcmp(argv[i], "--isolated")==0)
 	bb_status = BB_ISOLATED;
-    else if (strcmp(argv[i], "-g")==0 || strcmp(argv[i], "--generate")==0) {
-        cfg_dump_all();
-        exit(0);
-    }
     else
         return -1;
 
Index: gw/smsbox.c
===================================================================
RCS file: /home/cvs/gateway/gw/smsbox.c,v
retrieving revision 1.240
diff -a -u -r1.240 smsbox.c
--- gw/smsbox.c	8 Dec 2003 11:25:44 -0000	1.240
+++ gw/smsbox.c	15 Jan 2004 13:49:42 -0000
@@ -111,7 +111,6 @@
 static Octstr *sendota_url = NULL;
 static Octstr *xmlrpc_url = NULL;
 static Octstr *bb_host;
-static char *pid_file;
 static Octstr *accepted_chars = NULL;
 static int only_try_http = 0;
 static URLTranslationList *translations = NULL;
@@ -3003,17 +3002,6 @@
  * Main program. Configuration, signal handling, etc.
  */
 
-static void write_pid_file(void) {
-    FILE *f;
-        
-    if (pid_file != NULL) {
-	f = fopen(pid_file, "w");
-	fprintf(f, "%d\n", (int)getpid());
-	fclose(f);
-    }
-}
-
-
 static void signal_handler(int signum) {
     /* On some implementations (i.e. linuxthreads), signals are delivered
      * to all threads.  We only want to handle each signal once for the
@@ -3299,7 +3287,6 @@
 
     debug("sms", 0, "----------------------------------------------");
     debug("sms", 0, GW_NAME " smsbox version %s starting", GW_VERSION);
-    write_pid_file();
 
     translations = urltrans_create();
     if (translations == NULL)
Index: gwlib/utils.c
===================================================================
RCS file: /home/cvs/gateway/gwlib/utils.c,v
retrieving revision 1.41
diff -a -u -r1.41 utils.c
--- gwlib/utils.c	17 Dec 2003 16:34:26 -0000	1.41
+++ gwlib/utils.c	15 Jan 2004 13:49:42 -0000
@@ -68,9 +68,307 @@
 #include <time.h>
 #include <unistd.h>
 #include <termios.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pwd.h>
 
 #include "gwlib.h"
 
+/* pid of child process when parachute is used */
+static pid_t child_pid = -1;
+/* saved child signal handlers */
+static struct sigaction child_actions[32];
+/* just a flag that child signal handlers are stored */
+static int child_actions_init = 0;
+/* our pid file name */
+static char *pid_file = NULL;
+static volatile sig_atomic_t parachute_shutdown = 0;
+
+
+static void parachute_sig_handler(int signum)
+{
+    info(0, "Signal %d received, forward to child pid (%ld)", signum, (long) child_pid);
+
+    /* we do not handle any signal, just forward these to child process */
+    if (child_pid != -1 && getpid() != child_pid)
+        kill(child_pid, signum);
+
+    /* if signal received and no child there, terminating */
+    switch(signum) {
+        case SIGTERM:
+        case SIGINT:
+        case SIGABRT:
+            if (child_pid == -1)
+                exit(0);
+            else
+                parachute_shutdown = 1;
+    }
+}
+
+static void parachute_init_signals(int child)
+{
+    struct sigaction sa;
+
+    if (child_actions_init && child) {
+        sigaction(SIGTERM, &child_actions[SIGTERM], NULL);
+        sigaction(SIGQUIT, &child_actions[SIGQUIT], NULL);
+        sigaction(SIGINT,  &child_actions[SIGINT], NULL);
+        sigaction(SIGABRT, &child_actions[SIGABRT], NULL);
+        sigaction(SIGHUP,  &child_actions[SIGHUP], NULL);
+        sigaction(SIGALRM, &child_actions[SIGALRM], NULL);
+        sigaction(SIGUSR1, &child_actions[SIGUSR1], NULL);
+        sigaction(SIGUSR2, &child_actions[SIGUSR2], NULL);
+        sigaction(SIGPIPE, &child_actions[SIGPIPE], NULL);
+    }
+    else if (!child && !child_actions_init) {
+        sa.sa_flags = 0;
+        sigemptyset(&sa.sa_mask);
+        sa.sa_handler = parachute_sig_handler;
+        sigaction(SIGTERM, &sa, &child_actions[SIGTERM]);
+        sigaction(SIGQUIT, &sa, &child_actions[SIGQUIT]);
+        sigaction(SIGINT,  &sa, &child_actions[SIGINT]);
+        sigaction(SIGABRT, &sa, &child_actions[SIGABRT]);
+        sigaction(SIGHUP,  &sa, &child_actions[SIGHUP]);
+        sigaction(SIGALRM, &sa, &child_actions[SIGALRM]);
+        sigaction(SIGUSR1, &sa, &child_actions[SIGUSR1]);
+        sigaction(SIGUSR2, &sa, &child_actions[SIGUSR2]);
+        sa.sa_handler = SIG_IGN;
+        sigaction(SIGPIPE, &sa, &child_actions[SIGPIPE]);
+        sigaction(SIGTTOU, &sa, NULL);
+        sigaction(SIGTTIN, &sa, NULL);
+        sigaction(SIGTSTP, &sa, NULL);
+        child_actions_init = 1;
+    }
+    else
+        panic(0, "Child process signal handlers not initialized before.");
+}
+
+static int is_executable(const char *filename)
+{
+    struct stat buf;
+
+    if (stat(filename, &buf)) {
+        error(errno, "Error while stat of file `%s'", filename);
+        return 0;
+    }
+    if (!S_ISREG(buf.st_mode) && !S_ISLNK(buf.st_mode)) {
+        error(0, "File `%s' is not a regular file.", filename);
+        return 0;
+    }
+    /* others has exec permission */
+    if (S_IXOTH & buf.st_mode) return 1;
+    /* group has exec permission */
+    if ((S_IXGRP & buf.st_mode) && buf.st_gid == getgid())
+        return 1;
+    /* owner has exec permission */
+    if ((S_IXUSR & buf.st_mode) && buf.st_uid == getuid())
+        return 1;
+
+    return 0;
+}
+
+/*
+ * become daemon.
+ * returns 0 for father process; 1 for child process
+ */
+static int become_daemon(void)
+{
+    if (getppid() != 1) {
+       signal(SIGTTOU, SIG_IGN);
+       signal(SIGTTIN, SIG_IGN);
+       signal(SIGTSTP, SIG_IGN);
+       if (fork())
+          return 0;
+       setsid();
+    }
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+
+    /* XXX chdir breaks restart of boxes when
+       started w/o a full path to binary */
+    /* chdir("/"); */
+    return 1;
+}
+
+#define PANIC_SCRIPT_MAX_LEN 4096
+
+static void execute_panic_script(const char *panic_script, const char *format, ...)
+{
+    char *args[3];
+    char buf[PANIC_SCRIPT_MAX_LEN + 1];
+    va_list ap;
+
+    va_start(ap, format);
+    vsnprintf(buf, PANIC_SCRIPT_MAX_LEN, format, ap);
+    va_end(ap);
+
+    if (fork())
+       return;
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+
+    args[0] = (char*) panic_script;
+    args[1] = buf;
+    args[2] = NULL;
+
+    execv(args[0], args);
+}
+
+
+static void parachute_start(const char *myname, const char *panic_script) {
+    time_t last_start = 0, last_panic = 0;
+    long respawn_count = 0;
+    int status;
+
+
+    if (panic_script && !is_executable(panic_script))
+        panic(0, "Panic script `%s' is not executable for us.", panic_script);
+
+    /* setup sighandler */
+    parachute_init_signals(0);
+
+    for (;;) {
+        if (respawn_count > 0 && difftime(time(NULL), last_start) < 10) {
+            error(0, "Child process died too fast, disabling for 30 sec.");
+            gwthread_sleep(30.0);
+        }
+        if (!(child_pid = fork())) { /* child process */
+            child_pid = getpid();
+            parachute_init_signals(1); /* reset sighandlers */
+	    return;
+        }
+	else if (child_pid < 0) {
+	    error(errno, "Couldnot start child process ! Will retry in 5 sec.");
+	    gwthread_sleep(5.0);
+            continue;
+	}
+	else { /* father process */
+	    time(&last_start);
+            info(0, "Child process with PID (%ld) started.", (long) child_pid);
+            do {
+                if (waitpid(child_pid, &status, 0) == child_pid) {
+                    /* check here why child terminated */
+                   if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+                       info(0, "Child process exited gracefully, exit...");
+                       gwlib_shutdown();
+                       exit(0);
+                   }
+                   else if (WIFEXITED(status)) {
+                       error(0, "Caught child %d which died with return code %d",
+                           child_pid, WEXITSTATUS(status));
+                       child_pid = -1;
+                   }
+                   else if (WIFSIGNALED(status)) {
+                       error(0, "Caught child %d which died due to signal %d",
+                           child_pid, WTERMSIG(status));
+                       child_pid = -1;
+                   }
+                }
+                else if (errno != EINTR) {
+                    error(errno, "Error while waiting of child process.");
+                }
+            } while(child_pid > 0);
+
+            if (parachute_shutdown) {
+                /* may only happens if child process crashed while shutdown */
+                info(0, "Child process crashed while shutdown. Exiting due to signal...");
+                gwlib_shutdown();
+                exit(WIFEXITED(status) ? WEXITSTATUS(status) : 0);
+            }
+
+            respawn_count++;
+            if (panic_script && myname && difftime(time(NULL), last_panic) > 300) {
+                time(&last_panic);
+                debug("kannel", 0, "Executing panic script: %s %s %ld", panic_script, myname, respawn_count);
+                execute_panic_script(panic_script, "%s %ld", myname, respawn_count);
+            }
+            /* sleep a while to get e.g. sockets released */
+            gwthread_sleep(5.0);
+	}
+    }
+}
+
+
+static void write_pid_file(void)
+{
+    int fd;
+    FILE *file;
+
+    if (!pid_file)
+        return;
+
+    fd = open(pid_file, O_WRONLY|O_NOCTTY|O_TRUNC|O_CREAT|O_EXCL, 0644);
+    if (fd == -1)
+        panic(errno, "Couldnot open pid-file `%s'", pid_file);
+
+    file = fdopen(fd, "w");
+    if (!file)
+        panic(errno, "Couldnot open file-stream `%s'", pid_file);
+
+    fprintf(file, "%ld\n", (long) getpid());
+    fclose(file);
+}
+
+static void remove_pid_file(void)
+{
+    if (!pid_file)
+        return;
+
+    /* ensure we don't called from child process */
+    if (child_pid != -1 && child_pid == getpid())
+        return;
+
+    if (-1 == unlink(pid_file))
+        error(errno, "Couldnot unlink pid-file `%s'", pid_file);
+}
+
+static int change_user(const char *user)
+{
+    struct passwd *pass;
+
+    if (!user)
+        return -1;
+
+    pass = getpwnam(user);
+    if (!pass) {
+        error(0, "Couldnot find a user `%s' in system.", user);
+        return -1;
+    }
+    gw_claim_area(pass);
+    gw_claim_area(pass->pw_name);
+    gw_claim_area(pass->pw_passwd);
+    gw_claim_area(pass->pw_gecos);
+    gw_claim_area(pass->pw_dir);
+    gw_claim_area(pass->pw_shell);
+
+    if (-1 == setgid(pass->pw_gid)) {
+        error(errno, "Couldnot change group id %ld -> %ld.", (long) getgid(), (long) pass->pw_gid);
+        goto out;
+    }
+
+    if (-1 == setuid(pass->pw_uid)) {
+        error(errno, "Couldnot change user id %ld -> %ld.", (long) getuid(), (long) pass->pw_uid);
+        goto out;
+    }
+
+    return 0;
+
+out:
+    gw_free(pass->pw_name);
+    gw_free(pass->pw_passwd);
+    gw_free(pass->pw_gecos);
+    gw_free(pass->pw_dir);
+    gw_free(pass->pw_shell);
+    gw_free(pass);
+    return -1;
+}
 
 /*
  * new datatype functions
@@ -136,6 +434,8 @@
     int file_lvl = GW_DEBUG;
     char *log_file = NULL;
     char *debug_places = NULL;
+    char *panic_script = NULL, *user = NULL;
+    int parachute = 0, daemonize = 0;
     
     for(i=1; i < argc; i++) {
 	if (strcmp(argv[i],"-v")==0 ||
@@ -145,28 +445,60 @@
 		debug_lvl = atoi(argv[i+1]);
 		i++;
 	    } else
-		fprintf(stderr, "Missing argument for option %s\n", argv[i]); 
+		panic(0, "Missing argument for option %s\n", argv[i]); 
 	} else if (strcmp(argv[i],"-F")==0 ||
 		   strcmp(argv[i],"--logfile")==0) {
 	    if (i+1 < argc && *(argv[i+1]) != '-') {
 		log_file = argv[i+1];
 		i++;
 	    } else
-		fprintf(stderr, "Missing argument for option %s\n", argv[i]); 
+		panic(0, "Missing argument for option %s\n", argv[i]); 
 	} else if (strcmp(argv[i],"-V")==0 ||
 		   strcmp(argv[i],"--fileverbosity")==0) {
 	    if (i+1 < argc) {
 		file_lvl = atoi(argv[i+1]);
 		i++;
 	    } else
-		fprintf(stderr, "Missing argument for option %s\n", argv[i]); 
+		panic(0, "Missing argument for option %s\n", argv[i]); 
 	} else if (strcmp(argv[i],"-D")==0 ||
 		   strcmp(argv[i],"--debug")==0) {
 	    if (i+1 < argc) {
 		debug_places = argv[i+1];
 		i++;
 	    } else
-		fprintf(stderr, "Missing argument for option %s\n", argv[i]); 
+		panic(0, "Missing argument for option %s\n", argv[i]); 
+	} else if (strcmp(argv[i], "-X")==0 ||
+                   strcmp(argv[i], "--panic-script")==0) {
+	    if (i+1 < argc) {
+		panic_script = argv[i+1];
+		i++;
+	    }
+	    else
+		panic(0, "Missing argument for option %s\n", argv[i]);
+	} else if (strcmp(argv[i], "-P")==0 ||
+                   strcmp(argv[i], "--parachute")==0) {
+	    parachute = 1;
+	} else if (strcmp(argv[i], "-d")==0 ||
+	           strcmp(argv[i], "--daemonize")==0) {
+	    daemonize = 1;
+        } else if (strcmp(argv[i], "-p")==0 ||
+                   strcmp(argv[i], "--pid-file")==0) {
+            if (i+1 < argc) {
+                pid_file = argv[i+1];
+                i++;
+            } else
+                panic(0, "Missing argument for option %s\n", argv[i]);
+	} else if (strcmp(argv[i], "-u")==0 ||
+                   strcmp(argv[i], "--user")==0) {
+	    if (i+1 < argc) {
+	        user = argv[i+1];
+                i++;
+	    } else
+	        panic(0, "Missing argument for option %s\n", argv[i]);
+	} else if (strcmp(argv[i], "-g")==0 ||
+	           strcmp(argv[i], "--generate")==0) {
+	    cfg_dump_all();
+	    exit(0);
 	} else if (strcmp(argv[i],"--")==0) {
 	    i++;
 	    break;
@@ -178,12 +510,45 @@
 	    }
 	    if (ret < 0) {
 		fprintf(stderr, "Unknown option %s, exiting.\n", argv[i]);
-		panic(0, "Option parsing failed");
+                panic(0, "Option parsing failed");
 	    }
 	    else
 		i += ret;	/* advance additional args */
 	}
     }
+
+    if (user && -1 == change_user(user)) {
+        panic(0, "Couldnot change to user `%s'.", user);
+    }
+
+    /* deamonize */
+    if (daemonize && !become_daemon())
+       exit(0);
+
+    if (pid_file) {
+        write_pid_file();
+        atexit(remove_pid_file);
+    }
+
+    if (parachute) {
+        /*
+         * if we are running as daemon so open syslog
+         * in order not to deal with i.e. log rotate.
+         */
+        if (daemonize) {
+            char *ident = strrchr(argv[0], '/');
+            if (!ident)
+                ident = argv[0];
+            else
+                ident++;
+            log_set_syslog(ident, (debug_lvl > -1 ? debug_lvl : 0));
+        }
+        parachute_start(argv[0], panic_script);
+        /* now we are in child process so close syslog */
+        if (daemonize)
+            log_close_all();
+    }
+
     if (debug_lvl > -1)
 	log_set_output_level(debug_lvl);
     if (debug_places != NULL)
Index: doc/userguide/userguide.xml
===================================================================
RCS file: /home/cvs/gateway/doc/userguide/userguide.xml,v
retrieving revision 1.261
diff -a -u -r1.261 userguide.xml
--- doc/userguide/userguide.xml	14 Jan 2004 13:55:32 -0000	1.261
+++ doc/userguide/userguide.xml	15 Jan 2004 13:49:42 -0000
@@ -1600,9 +1600,43 @@
    <row><entry><literal>-g</literal></entry>
         <entry morerows="1" valign="bottom">
              Dump all known config groups and config keys to stdout
-             and exit. (bearerbox only)
+             and exit.
         </entry></row>
    <row><entry><literal>--generate</literal></entry></row>
+
+   <row><entry><literal>-u &lt;username&gt;</literal></entry>
+        <entry morerows="1" valign="bottom">
+             Change process user-id to the given.
+        </entry></row>
+   <row><entry><literal>--user &lt;username&gt;</literal></entry></row>
+
+   <row><entry><literal>-p &lt;filename&gt;</literal></entry>
+        <entry morerows="1" valign="bottom">
+             Write process PID to the given file.
+        </entry></row>
+   <row><entry><literal>--pid-file &lt;filename&gt;</literal></entry></row>
+
+   <row><entry><literal>-d</literal></entry>
+        <entry morerows="1" valign="bottom">
+             Start process in as daemon (detached from a current shell session).
+        </entry></row>
+   <row><entry><literal>--daemonize</literal></entry></row>
+
+   <row><entry><literal>-P</literal></entry>
+        <entry morerows="1" valign="bottom">
+             Start watcher process. This process watch a child process and if child process
+             crached will restart them automaticaly.
+        </entry></row>
+   <row><entry><literal>--parachute</literal></entry></row>
+
+   <row><entry><literal>-X &lt;scriptname&gt;</literal></entry>
+        <entry morerows="1" valign="bottom">
+             Execute a given shell script or binary when child process crash detected. This option
+             is usable only with <literal>--parachute/-P</literal>.
+             Script will be executed with 2 arguments:
+                 scriptname 'processname' 'respawn-count'.
+        </entry></row>
+   <row><entry><literal>--panic-script &lt;scriptname&gt;</literal></entry></row>
 
  </tbody>
  </tgroup>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.