From 32e75879103a943ac682cbde7b37ea4e4fbc24ab Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 2 Jun 2012 09:07:44 -0700 Subject: add some tools --- tools/hi-q.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 tools/hi-q.c (limited to 'tools/hi-q.c') diff --git a/tools/hi-q.c b/tools/hi-q.c new file mode 100644 index 0000000..31ec77b --- /dev/null +++ b/tools/hi-q.c @@ -0,0 +1,275 @@ +/////////////// +// lightweight connection from qmail to filters e.g. spamassassin +// (hi-q filter, get it?) + +// TODO: Panic stop should signal all children. +// TODO: Possibly: Wait for all kids in parallel? +// That's because they might finish out of order. + +#include +#include /* for malloc() */ +#include +#include +#include /* for fork(), wait() */ +#include +#include + +using namespace std; +#include +#include + +// error exit codes, as stated in qmail.c +const int ex_spam = 21; +const int ex_syserr = 71; +const int ex_comerr = 74; + +#define bufsize 16384 + +void panic(const int sts) { + // FIXME: stop other children + exit(sts); +} + +void slurp(const int inch, const int ouch){ + char buf[bufsize]; + ssize_t todo; + for (;;) { + ssize_t got = read(inch, buf, bufsize); + if (got == 0) { // EoF + break; + } + if (got < 0) { + fprintf(stderr, "hi-q: input error: "); + perror(0); + panic(ex_comerr); + } + + todo = got; + while (todo) { + ssize_t sent = write(ouch, buf, todo); + if (sent < 0 && errno != EINTR) { + fprintf(stderr, "hi-q: output error: "); + perror(0); + panic(ex_comerr); + } + todo -= sent; + } + } +} + + +void probe_fd(){ + int ii; + struct stat buf; + for (ii = 0; ii < 16; ii++) { + int rslt = fstat(ii, &buf); + fprintf(stderr, "fd %2d status %2d", ii, rslt); + if (rslt==0) + fprintf(stderr, " : %d", (int)buf.st_dev); + fprintf(stderr, "\n"); + } + fprintf(stderr, "============\n"); +} + + +void blurb(const int ii, const pid_t* kidpid) { + int kidstatus; + /*pid_t somekid = */ waitpid(kidpid[ii], &kidstatus, WUNTRACED); + if (WIFEXITED(kidstatus)) + fprintf(stderr, "kid #%d (%d) exited with status %d\n", + ii, kidpid[ii], WEXITSTATUS(kidstatus)); + if (WIFSIGNALED(kidstatus)) + fprintf(stderr, "kid #%d (%d) killed by signal %d\n", + ii, kidpid[ii], WTERMSIG(kidstatus)); + +} + +// We are fussy about the argument types because we want +// this to compile cleanly under g++ as well as gcc, +// and each is strict about different things, such that +// one or the other will complain unless everything is +// done just right. + +// This is the way execve really behaves: +// the characters are held constant +// and the (char*) pointers are held constant: +int Execve(char const * fn, + char const * const * argv, + char const * const * env) { +// coerce the arg types to match the unwise declaration in unistd.h : + return execve(fn, (char*const*) argv, (char*const*) env); +} + +//////////////////////////////////////// +// we have data coming in on fd 0. +// and control coming in on fd 1. + +int main(int argc, char** argv, char const * const * env) { + int kidstatus; + pid_t somekid; + + int rslt; + int loose_end = 0; + +#ifdef SpareStuff + char* slurp2_args[] = {"/home/jsd/hack/slurp2", 0}; + char* echo_args[] = {"/bin/echo", "hi there", 0}; + char* wc_args[] = {"/usr/bin/wc", 0}; + char* cat_args[] = {"/bin/cat", 0}; + char* spama_args[] = {"/usr/local/bin/spamassassin", "-e", 0}; + char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0}; + char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0}; + + const char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0}; + const char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0}; + + + const char** joblist[] = { + cat_args, + slurp2_args, + 0 // required: zero terminates the list + }; + +#endif + + const char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0}; + const char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0}; + const char** joblist[] = { + spamc_args, + qq_args, + 0 // required: zero terminates the list + }; + + vector > filters; + + int nkids; + pid_t* kidpid; // indexed by kid number + + for (nkids = 0; joblist[nkids]; nkids++) {} // count 'em + kidpid = (pid_t*) malloc(nkids * sizeof(pid_t)); + +// At this point, there is some loop invariants; +// (a) fd0 is open and ready for the next child to read, and +// (b) fd1 is open but is something children can (and should) +// throw away as soon as convenient. + + {int ii; for (ii=0; joblist[ii]; ii++){ /* loop over all kids */ + int datapipe[2]; + int kid_end; + int lastkid = !joblist[ii+1]; +#define flip(a,b) (lastkid ? b : a) + +//xx fprintf(stderr, "Top of loop %d loose: %d\n", ii, loose_end); + +// Create a pipe, which will be used to connect +// this child's fd1 to the next child's fd0 ... +// except for the last kid, which reads fd1 rather +// than writing it. + rslt = pipe(datapipe); + if (rslt < 0) { + fprintf(stderr, "hi-q: could not create datapipe: "); + perror(0); + panic(ex_syserr); + } + +//xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]); + if (loose_end) { + close(0); + dup2(loose_end, 0); + close(loose_end); + } + + loose_end = datapipe[flip(0,1)]; + kid_end = datapipe[flip(1,0)]; + + kidpid[ii] = fork(); + if (!kidpid[ii]) { /*** child code ***/ + const char ** prog; + +// Now that we are through creating pipes, get rid +// of the placeholder: + close(1); + + close(loose_end); // the reading end is none of our business + + rslt = dup2(kid_end, 1); + if (rslt < 0) { + fprintf(stderr, "hi-q: dup2(kid(%d),1) failed: ", kid_end); + perror(0); + exit(ex_syserr); + } + + close(kid_end); // use fd1 instead now + // OK, at this point we are set up to read fd0 + // and write fd1 (except last kid reads fd1). +//// probe_fd(); + + prog = joblist[ii]; + rslt = Execve(prog[0], prog, env); + fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]); + perror(0); + exit(ex_syserr); + } + + /*** parent code ***/ + if (kidpid[ii] < 0) { + fprintf(stderr, "hi-q: failure to fork kid#%d: ", ii); + perror(0); + panic(ex_syserr); + } + close(kid_end); +#ifdef more_testing + fprintf(stderr, "forked kid #%d (%d) piping %d\n", + ii, kidpid[ii], kid_end); +// sleep(1); /* let kid run a while */ +#endif + }} + +// here with the whole pipeline of kids running + + close(0); // the reading end of stdin was + // delegated to the first child + + + {int ii; for (ii=0; ii