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 #include +#include + +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"); }