9edd4d4bc522e0f6e8b63d2dd284b6ce34ec40cb
galt
  Thu Aug 29 17:09:35 2013 -0700
hgsql and friends utils: trying to make it so that the temp file and the child mysql process gets terminated when various signals such as Control-c, SIGINT, SIGABRT, SIGTERM etc are received
diff --git src/hg/lib/sqlProg.c src/hg/lib/sqlProg.c
index a751004..8aa9ff4 100644
--- src/hg/lib/sqlProg.c
+++ src/hg/lib/sqlProg.c
@@ -1,23 +1,77 @@
 /* sqlProg - functions for building command line programs to deal with
  * sql databases.*/
 #include "common.h"
 #include "sqlProg.h"
 #include "hgConfig.h"
 #include "obscure.h"
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <signal.h>
+
+char *tempFileNameToRemove = NULL;
+int killChildPid = 0;
+
+/* trap signals to remove the temporary file and terminate the child process */
+
+static void sqlProgCatchSignal(int sigNum)
+/* handler for various terminal signals for removing the temp file */
+{
+switch (sigNum)
+    {
+    case SIGTERM:
+    case SIGHUP:
+    case SIGINT:  // ctrl-c
+    case SIGABRT:
+    case SIGSEGV:
+    case SIGFPE:
+    case SIGBUS:
+	if (tempFileNameToRemove)
+	    unlink(tempFileNameToRemove);
+	break;
+    }
+
+if (killChildPid != 0) // the child should be runing
+    {
+    if (sigNum == SIGINT)
+	{
+    	kill(killChildPid, SIGINT);  // sometimes redundant, but not always
+	sleep(5);
+	}
+    kill(killChildPid, SIGTERM);
+    } 
+
+if (sigNum == SIGTERM || sigNum == SIGHUP) 
+    exit(1);   // so that atexit cleanup get called
+
+raise(SIGKILL);
+}
+
+void sqlProgInitSigHandlers()
+/* set handler for various terminal signals for logging purposes.
+ * if dumpStack is TRUE, attempt to dump the stack. */
+{
+// SIGKILL is not trappable or ignorable
+signal(SIGTERM, sqlProgCatchSignal);
+signal(SIGHUP,  sqlProgCatchSignal);
+signal(SIGINT,  sqlProgCatchSignal);
+signal(SIGABRT, sqlProgCatchSignal);
+signal(SIGSEGV, sqlProgCatchSignal);
+signal(SIGFPE,  sqlProgCatchSignal);
+signal(SIGBUS,  sqlProgCatchSignal);
+}
+
 
 static int cntArgv(char **argv)
 /* count number of elements in a null terminated vector of strings. 
  * NULL is considered empty */
 {
 if (argv == NULL)
     return 0;
 else
     {
     int i;
     for (i = 0; argv[i] != NULL; i++)
         continue;
     return i;
     }
 }
@@ -48,30 +102,31 @@
  * profile.password values returned from cfgVal().  If group is not
  * client or mysql, a --defaults-group-suffix=group must be passed to
  * mysql to actually invoke the settings.
  * passFileName cannot be static, and the last 6 characters must
  * be XXXXXX.  Those characters will be modified to form a unique suffix.
  * Returns a file descriptor for the file or dies with an error */
 {
 int fileNo;
 char paddedGroup [256]; /* string with brackets around the group name */
 char fileData[256];  /* constructed variable=value data for the mysql config file */
 char field[256];  /* constructed profile.field name to pass to cfgVal */
 char path[1024];
 
 if ((fileNo=mkstemp(defaultFileName)) == -1)
     errAbort("Could not create unique temporary file %s", defaultFileName);
+tempFileNameToRemove = defaultFileName;
 
 /* pick up the global and local defaults
  *  -- the order matters: later settings over-ride earlier ones. */
 
 safef(path, sizeof path, "/etc/my.cnf");
 copyCnfToDefaultsFile(path, defaultFileName, fileNo);
 
 safef(path, sizeof path, "/etc/mysql/my.cnf");
 copyCnfToDefaultsFile(path, defaultFileName, fileNo);
 
 safef(path, sizeof path, "/var/lib/mysql/my.cnf");
 copyCnfToDefaultsFile(path, defaultFileName, fileNo);
 
 //  SYSCONFDIR/my.cnf should be next, but I do not think it matters. maybe it is just /var/lib/mysql anyways.
 
@@ -126,51 +181,59 @@
 sqlExecProgProfile("localDb", prog, progArgs, userArgc, userArgv);
 }
 
 void sqlExecProgProfile(char *profile, char *prog, char **progArgs, int userArgc, char *userArgv[])
 /* 
  * Exec one of the sql programs using user and password defined in localDb.XXX variables from ~/.hg.conf 
  * progArgs is NULL-terminate array of program-specific arguments to add,
  * which maybe NULL. userArgv are arguments passed in from the command line.
  * The program is execvp-ed, this function does not return. 
  */
 {
 int i, j = 0, nargc=cntArgv(progArgs)+userArgc+6, defaultFileNo, returnStatus;
 pid_t child_id;
 char **nargv, defaultFileName[256], defaultFileArg[256], *homeDir;
 
+// install cleanup signal handlers
+sqlProgInitSigHandlers();
+
 /* Assemble defaults file */
 if ((homeDir = getenv("HOME")) == NULL)
     errAbort("sqlExecProgProfile: HOME is not defined in environment; cannot create temporary password file");
 safef(defaultFileName, sizeof(defaultFileName), "%s/.hgsql.cnf-XXXXXX", homeDir);
 defaultFileNo=sqlMakeDefaultsFile(defaultFileName, profile, "client");
 safef(defaultFileArg, sizeof(defaultFileArg), "--defaults-file=%s", defaultFileName);
 
 AllocArray(nargv, nargc);
 
 nargv[j++] = prog;
 nargv[j++] = defaultFileArg;   /* --defaults-file must come before other options */
 if (progArgs != NULL)
     {
     for (i = 0; progArgs[i] != NULL; i++)
         nargv[j++] = progArgs[i];
     }
 for (i = 0; i < userArgc; i++)
     nargv[j++] = userArgv[i];
 nargv[j++] = NULL;
 
+// flush before forking so we can't accidentally get two copies of the output
+fflush(stdout);
+fflush(stderr);
+
 child_id = fork();
+killChildPid = child_id;
 if (child_id == 0)
     {
     execvp(nargv[0], nargv);
     _exit(42);  /* Why 42?  Why not?  Need something user defined that mysql isn't going to return */
     }
 else
     {
     /* Wait until the child process completes, then delete the temp file */
     wait(&returnStatus);
     unlink (defaultFileName);
     if (WIFEXITED(returnStatus))
         {
         if (WEXITSTATUS(returnStatus) == 42)
             errAbort("sqlExecProgProfile: exec failed");
         }