tttm

git clone https://orangeshoelaces.net/git/tttm.git

1f5e0cd12c9ea966d5ee75ff1383ed6ccec024ad

Author: Vasily Kolobkov on 05/26/2016

Committer: Vasily Kolobkov on 05/26/2016

Fetch, delete, rinse and repeat

Stats

errors.c  |   1 +
errors.h  |   1 +
imap.c    | 189 +++++++-
imap.h    |   9 +
parser.c  |  26 +-
parser.h  |   1 +
pshades.c |   1 +
tttm.c    | 113 ++++-
8 files changed, 300 insertions(+), 41 deletions(-)

Patch

diff --git a/errors.c b/errors.c
index a864212..44e1d23 100644
--- a/errors.c
+++ b/errors.c
@@ -13,6 +13,7 @@ const char *errmsgs[] = {
 	[TE_NO]         = "got NO result",
 	[TE_BAD]        = "got BAD result",
 	[TE_BYE]        = "server terminated the session",
+	[TE_NIL]        = "got NIL message",
 	[TE_IN]         = "input error",
 	[TE_OUT]        = "output error",
 	[TE_VM]         = "cache vm mapping",
diff --git a/errors.h b/errors.h
index 13ef768..46fc1fb 100644
--- a/errors.h
+++ b/errors.h
@@ -15,6 +15,7 @@ enum {
 	TE_NO,
 	TE_BAD,
 	TE_BYE,
+	TE_NIL,
 
 	TE_ERRNO,
 	TE_IN = TE_ERRNO,
diff --git a/imap.c b/imap.c
index 550fd3d..958a6bf 100644
--- a/imap.c
+++ b/imap.c
@@ -1,7 +1,11 @@
 #include <stdarg.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
+#include <string.h>
 #include <strings.h>
+#include <unistd.h>
+#include <sys/mman.h>
 
 #include "errors.h"
 #include "laxsrc.h"
@@ -9,6 +13,7 @@
 #include "imap.h"
 
 #define LEN(x) (sizeof(x) / sizeof((x)[0]))
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
 
 static struct capmap {
 	int    cap;
@@ -24,18 +29,25 @@ static struct capmap {
 };
 
 struct respcard {
-	int    sel;
-	int  (*handler)(struct imapctx *, int, void *);
-	int    req:1;
-	int    met:1;
-	void  *ctx;
+	int   sel;
+	int (*handler)(struct imapctx *, int, void *);
+	int   req:1;
+	int   met:1;
+	void *ctx;
+};
+
+struct fetargs {
+	uint32_t lo;
+	uint32_t hi;
+	int      stor;
+	struct msgd *bag;
 };
 
 static int imap_cmd(struct imapctx *, int, const char *, struct respcard *,
     struct respcard *, int, ...);
 static int imap_cooloff(struct imapctx *);
 static int imap_matchres(struct imapctx *, int, int *);
-static int imap_matchresp(struct imapctx *, struct respcard *, int, int, int *);
+static int imap_matchresp(struct imapctx *, struct respcard *, int, int *);
 static int imap_parcaps(struct imapctx *, union parnode *);
 static int imap_readln(struct imapctx *);
 static int imap_readlnc(struct imapctx *, int, int *);
@@ -46,7 +58,9 @@ static int dig_uxbye(struct imapctx *, int, void *);
 static int dig_caps(struct imapctx *, int, void *);
 static int dig_exists(struct imapctx *, int, void *);
 static int dig_expunge(struct imapctx *, int, void *);
+static int dig_fetch(struct imapctx *, int, void *);
 
+static int deqmsg(int, struct msgd *);
 static int state2res(union parnode *);
 static int str2cap(struct capmap *, char *, size_t);
 static int isprod(union parnode *, int);
@@ -74,6 +88,7 @@ static struct respcard unisexp[] = {
 
 static struct respcard capresp[] = { { IP_CAPDATA, dig_caps, 1 }, { -1 } };
 static struct respcard selresp[] = { { IP_EXISTS, dig_exists, 1 }, { -1 } };
+static struct respcard fetresp[] = { { IP_FETCH, dig_fetch }, { -1 } };
 static struct respcard byeresp[] = { { IP_RESPBYE, dig_bye, 1 }, { -1 } };
 
 int
@@ -114,6 +129,54 @@ imap_select(struct imapctx *con, const char *mb)
 	    -1, con->tag++, mb);
 }
 
+int
+imap_fetch(struct imapctx *con, uint32_t lo, uint32_t hi, int stor,
+    struct msgd *bag)
+{
+	int e;
+	struct msgd *m, *bend;
+	struct fetargs args;
+
+	bend = bag + (hi - lo + 1);
+	for (m = bag; m < bend; m++) {
+		m->off = -1;
+	}
+	args = (struct fetargs){ lo, hi, stor, bag };
+	fetresp->ctx = &args;
+	e = imap_cmd(con, -1, "%u FETCH %u:%u RFC822\r\n", fetresp,
+	    unisexp, stor, con->tag++, (unsigned)lo, (unsigned)hi);
+	if (e)
+		goto exit;
+	for (m = bag; m < bend; m++) {
+		if (m->off == -1)
+			goto eproto;
+	}
+ exit:
+	return e;
+ eproto:
+	e = TE_PROTO;
+	goto exit;
+}
+
+int
+imap_delete(struct imapctx *con, uint32_t lo, uint32_t hi)
+{
+	return imap_cmd(con, -1, "%u STORE %u:%u +FLAGS.SILENT (\\Deleted)\r\n",
+	    0, unisexp, -1, con->tag++, (unsigned)lo, (unsigned)hi);
+}
+
+int
+imap_expunge(struct imapctx *con)
+{
+	return imap_cmd(con, -1, "%u EXPUNGE\r\n", 0, unilat, -1, con->tag++);
+}
+
+int
+imap_nop(struct imapctx *con)
+{
+	return imap_cmd(con, -1, "%u NOOP\r\n", 0, unilat, -1, con->tag++);
+}
+
 int
 imap_close(struct imapctx *con)
 {
@@ -151,8 +214,8 @@ imap_cmd(struct imapctx *con, int nstate, const char *cmd,
 		if (imap_matchres(con, b, &e)) {
 			break;
 		}
-		if ((imap_matchresp(con, cmdresp, cache, b, &e) ||
-		    imap_matchresp(con, unilat, cache, b, &e)) && e) {
+		if ((imap_matchresp(con, cmdresp, b, &e) ||
+		    imap_matchresp(con, unilat, b, &e)) && e) {
 			break;
 		}
 	}
@@ -183,7 +246,7 @@ imap_cooloff(struct imapctx *con)
 	while (!(e = laxsrc_ishot(&con->in))) {
 		if ((e = imap_readln(con)))
 			goto exit;
-		if (imap_matchresp(con, freeresp, -1, 1, &e) && e)
+		if (imap_matchresp(con, freeresp, 1, &e) && e)
 			goto exit;
 	}
 	if (e == TE_TIMEOUT) {
@@ -209,8 +272,7 @@ imap_matchres(struct imapctx *con, int buf, int *e)
 }
 
 int
-imap_matchresp(struct imapctx *con, struct respcard *set, int cache,
-    int buf, int *e)
+imap_matchresp(struct imapctx *con, struct respcard *set, int buf, int *e)
 {
 	int match;
 	struct respcard *c;
@@ -332,6 +394,111 @@ dig_expunge(struct imapctx *con, int buf, void *ctx)
 	return 0;
 }
 
+int
+dig_fetch(struct imapctx *con, int buf, void *ctx)
+{
+	int e;
+	struct fetargs *args;
+	union parnode *f, *b, *nstr;
+	uint32_t msn;
+	struct msgd *m;
+
+	e = 0;
+	args = ctx;
+	f = par_sel(con->par, IP_RESPDATA, IP_FETCH, -1);
+	msn = (f + 1)->num.val;
+	if (msn < args->lo || msn > args->hi)
+		goto exit;
+	if ((b = par_seld(f, IP_822BODY, -1)))
+		goto exit;
+	nstr = b + 3;
+	if (islit(nstr, IL_NIL))
+		goto enil;
+	m = args->bag + (msn - args->lo);
+	m->off = nstr->str.off;
+	m->len = nstr->str.len;
+	if (buf) {
+		if ((m->off = lseek(args->stor, 0, SEEK_END)) == -1)
+			goto eio;
+		if (write(args->stor, con->rep + nstr->str.off,
+		    nstr->str.len) == -1)
+			goto eio;
+	}
+	if (nstr->type == PN_QSTR)
+		e = deqmsg(args->stor, m);
+ exit:
+	return e;
+ eio:
+	e = TE_CACHEIO;
+	goto exit;
+ enil:
+	e = TE_NIL;
+	goto exit;
+}
+
+int
+deqmsg(int stor, struct msgd *m)
+{
+	int e, quot;
+	char *rwnd, *r, *rend;
+	char *wwnd, *w, *wend;
+	size_t rlen, wlen, page;
+	size_t roff, woff;
+	size_t mend;
+	long svar;
+
+	e = quot = 0;
+	mend = m->off + m->len;
+	wwnd = 0;
+
+	if ((svar = sysconf(_SC_PAGE_SIZE)) == -1)
+		goto eio;
+	page = SIZE_MAX & svar;
+
+	for (roff = woff = m->off; roff < mend;) {
+		rlen = MIN(page, mend - roff);
+		rwnd = mmap(0, rlen, PROT_READ, MAP_PRIVATE, stor, roff);
+		if (rwnd == MAP_FAILED)
+			goto eio;
+		rend = rwnd + rlen;
+		for (r = rwnd; r < rend; r++) {
+			if (wwnd && w == wend) {
+				if (munmap(wwnd, wlen) == -1)
+					goto eio;
+				woff += wlen;
+				wwnd = 0;
+			}
+			if (!wwnd) {
+				wlen = MIN(page, mend - woff);
+				wwnd = mmap(0, wlen, PROT_WRITE, MAP_SHARED,
+				    stor, woff);
+				if (wwnd == MAP_FAILED)
+					goto eio;
+				wend = wwnd + wlen;
+				w = wwnd;
+			}
+			if (*r == '\\') {
+				quot = !quot;
+				if (quot)
+					continue;
+			}
+			*w++ = *r;
+			quot = 0;
+		}
+		if (munmap(rwnd, rlen) == -1)
+			goto eio;
+		roff += rlen;
+	}
+	if (wwnd && munmap(wwnd, wlen) == -1)
+		goto eio;
+	m->len = (woff - m->off) + (w - wwnd);
+ exit:
+	return e;
+ eio:
+	e = TE_CACHEIO;
+	goto exit;
+}
+
 int
 state2res(union parnode *respstate)
 {
diff --git a/imap.h b/imap.h
index 0681f2a..9aaefb5 100644
--- a/imap.h
+++ b/imap.h
@@ -17,9 +17,18 @@ struct imapctx {
 	} mb;
 };
 
+struct msgd {
+	off_t  off;
+	size_t len;
+};
+
 int	imap_init(struct imapctx *, int, int);
 int	imap_discocap(struct imapctx *);
 int	imap_login(struct imapctx *, const char *, const char *);
 int	imap_select(struct imapctx *, const char *);
+int	imap_fetch(struct imapctx *, uint32_t, uint32_t, int, struct msgd *);
+int	imap_delete(struct imapctx *, uint32_t, uint32_t);
+int	imap_expunge(struct imapctx *);
+int	imap_nop(struct imapctx *);
 int	imap_close(struct imapctx *);
 int	imap_logout(struct imapctx *);
diff --git a/parser.c b/parser.c
index 308571f..bb6248a 100644
--- a/parser.c
+++ b/parser.c
@@ -240,6 +240,7 @@ static int p_hdrlist(struct parctx *);
 static int p_lit(struct parctx *, int);
 static int p_lstr(struct parctx *);
 static int p_mailbox(struct parctx *);
+static int p_mat822body(struct parctx *);
 static int p_matbody(struct parctx *);
 static int p_matbsect(struct parctx *);
 static int p_matenv(struct parctx *);
@@ -490,7 +491,7 @@ par_movewnd(struct parctx *p, size_t off)
 		goto exit;
 
 	ci = mmap(0, wndcap, PROT_READ | PROT_WRITE,
-	    MAP_PRIVATE, p->cache, p->corig + off);
+	    MAP_SHARED, p->cache, p->corig + off);
 	if (ci == MAP_FAILED)
 		goto evm;
 
@@ -529,7 +530,7 @@ par_primecache(struct parctx *p)
 	if (p->buflen == 0)
 		goto exit;
 
-	ci = mmap(0, p->buflen, PROT_WRITE, MAP_PRIVATE, p->cache, p->corig);
+	ci = mmap(0, p->buflen, PROT_WRITE, MAP_SHARED, p->cache, p->corig);
 	if (ci == MAP_FAILED)
 		goto evm;
 
@@ -1449,6 +1450,15 @@ p_mailbox(struct parctx *p)
 	return p_lit(p, IL_INBOX) || p_astr(p);
 }
 
+int
+p_mat822body(struct parctx *p)
+{
+	struct parcur b;
+
+	return p_beg(p, &b, IP_822BODY) && p_lit(p, IL_RFC822) &&
+	    p_sp(p) && p_nstr(p) && p_end(p, &b) || p_rwd(p, &b);
+}
+
 int
 p_matbody(struct parctx *p)
 {
@@ -1478,12 +1488,11 @@ p_matenv(struct parctx *p)
 int
 p_mathdrotxt(struct parctx *p)
 {
-	struct parcur b, o;
+	struct parcur b;
 
 	return p_chk(p, &b) && p_lit(p, IL_RFC822) &&
-	    (p_chk(p, &o) && p_lit(p, IL_DHDR) || p_lit(p, IL_DTXT) ||
-	    p_rwd(p, &o) || p_opt(p)) && p_sp(p) && p_nstr(p) ||
-	    p_rwd(p, &b);
+	    (p_lit(p, IL_DHDR) || p_lit(p, IL_DTXT)) && p_sp(p) &&
+	    p_nstr(p) || p_rwd(p, &b);
 }
 
 int
@@ -1706,8 +1715,9 @@ p_msgattdyn(struct parctx *p)
 int
 p_msgattstat(struct parctx *p)
 {
-	return p_matenv(p) || p_matintdate(p) || p_mathdrotxt(p) ||
-	    p_matsize(p) || p_matbody(p) || p_matbsect(p) || p_matuid(p);
+	return p_matenv(p) || p_matintdate(p) || p_mat822body(p) ||
+	    p_mathdrotxt(p) || p_matsize(p) || p_matbody(p) ||
+	    p_matbsect(p) || p_matuid(p);
 }
 
 int
diff --git a/parser.h b/parser.h
index 26d03fc..061d427 100644
--- a/parser.h
+++ b/parser.h
@@ -16,6 +16,7 @@ enum {
 };
 
 enum {
+	IP_822BODY,
 	IP_ATOM,
 	IP_CAP,
 	IP_CAPDATA,
diff --git a/pshades.c b/pshades.c
index 21287a8..c821eb9 100644
--- a/pshades.c
+++ b/pshades.c
@@ -12,6 +12,7 @@
 #define LEN(a) (sizeof(a) / sizeof(a)[0])
 
 char *prod[] = {
+	"822BODY",
 	"ATOM",
 	"CAP",
 	"CAPDATA",
diff --git a/tttm.c b/tttm.c
index fc7684c..fdfcdec 100644
--- a/tttm.c
+++ b/tttm.c
@@ -1,4 +1,5 @@
 #include <err.h>
+#include <stdlib.h>
 #include <unistd.h>
 
 #include "errors.h"
@@ -6,7 +7,21 @@
 #include "parser.h"
 #include "imap.h"
 
-static void fin(int, const char *);
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+
+/*
+   Run fetch, pipe and delete cycle on this many messages a pop.
+*/
+#define BATCHSZ 10
+
+/*
+   Retry fetching mail at most this many times after getting a nil fetch.
+*/
+#define MAXREFC 2
+
+static int	pipemsg(struct msgd *, int);
+static void	fin(struct imapctx *, int, const char *);
+static void	terr(int, const char *);
 
 int
 main(int argc, char **argv)
@@ -14,12 +29,16 @@ main(int argc, char **argv)
 	int e;
 	struct imapctx con;
 	char *name, *pass;
+	int tmp;
+	int refetchc, batchsz, mpiped;
+	struct msgd bag[BATCHSZ], *m, *bend;
 
-	if ((e = imap_init(&con, STDIN_FILENO, STDOUT_FILENO)))
-		fin(e, "failed to initialize session");
-
-	if (con.state == IS_AUTH)
+	if ((e = imap_init(&con, STDIN_FILENO, STDOUT_FILENO))) {
+		terr(e, "failed to initialize session");
+	}
+	if (con.state == IS_AUTH) {
 		goto select;
+	}
 
 	name = argv[1];
 	pass = argv[2];
@@ -31,35 +50,85 @@ main(int argc, char **argv)
 		warnx("failed to log in: %s", errmsgs[e]);
 		goto logout;
 	} else if (e) {
-		fin(e, "failed to log in");
+		terr(e, "failed to log in");
 	}
 
  select:
-	e = imap_select(&con, "inbox");
-	if (e == TE_NO || e == TE_BAD) {
-		warnx("can't access mailbox: %s", errmsgs[e]);
-		goto logout;
-	} else if (e) {
-		fin(e, "failed to select mailbox");
+	if ((e = imap_select(&con, "inbox"))) {
+		fin(&con, e, "failed to select mailbox");
 	}
-
-	e = imap_close(&con);
-	if (e == TE_BAD) {
-		warnx("failed to close mailbox: %s", errmsgs[e]);
-		goto logout;
-	} else if (e) {
-		fin(e, "failed to close mailbox");
+	refetchc = 0;
+	while (con.mb.exists > 0) {
+		if (lseek(tmp, 0, SEEK_SET) == -1) {
+			warn("failed to rewind temporary file");
+			goto logout;
+		}
+		batchsz = MIN(BATCHSZ, con.mb.exists);
+		bend = bag + batchsz;
+		e = imap_fetch(&con, 1, batchsz, tmp, bag);
+		if (e == TE_NIL && refetchc < MAXREFC) {
+			if ((e = imap_nop(&con))) {
+				fin(&con, e, "failed to receive updates");
+			}
+			refetchc++;
+			continue;
+		} else if (e) {
+			fin(&con, e, "failed to fetch mail");
+		}
+		refetchc = 0;
+		for (m = bag; m < bend; m++) {
+			if ((e = pipemsg(m, tmp))) {
+				warn("failed to pipe message");
+				break;
+			}
+		}
+		mpiped = m - bag;
+		if (mpiped == 0) {
+			break;
+		}
+		if ((e = imap_delete(&con, 1, mpiped))) {
+			fin(&con, e, "failed to delete mail");
+		}
+		if ((e = imap_expunge(&con))) {
+			fin(&con, e, "failed to expunge mail");
+		}
+		if (mpiped < batchsz) {
+			break;
+		}
+	}
+	if ((e = imap_close(&con))) {
+		fin(&con, e, "failed to close mailbox");
 	}
 
  logout:
-	if ((e = imap_logout(&con)))
-		fin(e, "failed to log out");
+	if ((e = imap_logout(&con))) {
+		fin(&con, e, "failed to log out");
+	}
+	return 0;
+}
 
+int
+pipemsg(struct msgd *m, int stor)
+{
 	return 0;
 }
 
 void
-fin(int e, const char *msg)
+fin(struct imapctx *con, int e, const char *msg)
+{
+	if (e == TE_NO || e == TE_BAD || e == TE_NIL) {
+		warnx("%s: %s", msg, errmsgs[e]);
+		if ((e = imap_logout(con))) {
+			terr(e, "failed to log out");
+		}
+	} else {
+		terr(e, msg);
+	}
+	exit(1);
+}
+
+void
+terr(int e, const char *msg)
 {
 	if (e > TE_ERRNO) {
 		err(1, "%s: %s", msg, errmsgs[e]);