/////////////// // 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 // 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); } //////////////////////////////////////// // Here with data coming in on fd 0. // and control coming in on ft 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}; #endif const char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0}; const char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0}; #ifdef testing const char** todo[] = { cat_args, slurp2_args, 0 // required: zero terminates the list }; #else const char** todo[] = { spamc_args, qq_args, 0 // required: zero terminates the list }; #endif int nkids; pid_t* kidpid; // indexed by kid number for (nkids = 0; todo[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; todo[ii]; ii++){ /* loop over all kids */ int datapipe[2]; int kid_end; int lastkid = !todo[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 = todo[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