summaryrefslogtreecommitdiff
path: root/tools/hi-q.c
diff options
context:
space:
mode:
authorJohn Denker <jsd@av8n.com>2012-06-02 09:07:44 -0700
committerJohn Denker <jsd@av8n.com>2012-06-02 17:30:23 -0700
commit32e75879103a943ac682cbde7b37ea4e4fbc24ab (patch)
treee734de5ec0cb5ece57d0f5aa01f6731a11f86a9b /tools/hi-q.c
parent9c9c853dd2c7b4b174fd435d092bb43e737b8b35 (diff)
add some tools
Diffstat (limited to 'tools/hi-q.c')
-rw-r--r--tools/hi-q.c275
1 files changed, 275 insertions, 0 deletions
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 <unistd.h>
+#include <stdlib.h> /* for malloc() */
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h> /* for fork(), wait() */
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+using namespace std;
+#include <string>
+#include <vector>
+
+// 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<vector<string> > 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<nkids-1; ii++){ /* loop over N-1 kids */
+ /* not last kid */
+
+#ifdef testing
+ blurb(ii, kidpid);
+#else
+ somekid = waitpid(kidpid[ii], &kidstatus, WUNTRACED);
+ if (somekid) {} // avoid silly compiler warning
+ if (WIFEXITED(kidstatus)) {
+ if (WEXITSTATUS(kidstatus) == 1) panic(ex_spam);
+ if (WEXITSTATUS(kidstatus) != 0) panic(ex_syserr);
+ /* otherwise kidstatus==0 and we fall through */
+ }
+ else panic(ex_syserr); // any kill, not a normal exit
+#endif
+
+
+ }}
+
+//xx fprintf(stderr, "slurping %d %d\n", 1, loose_end);
+ slurp(1, loose_end);
+ close(1);
+ close(loose_end);
+
+ {
+ int ii = nkids-1;
+
+#ifdef moretesting
+fprintf(stderr, "About to wait for kid #%d (%d)\n",
+ ii, kidpid[ii]);
+ blurb(nkids-1, kidpid);
+#endif
+ somekid = waitpid(kidpid[ii], &kidstatus, WUNTRACED);
+ if (WIFEXITED(kidstatus)) return WEXITSTATUS(kidstatus);
+ }
+
+#ifdef testing
+ sleep(1);
+#endif
+ return ex_syserr; // any kill, not a normal exit
+
+}