git clone https://orangeshoelaces.net/git/tttm.git
Author: Vasily Kolobkov on 05/26/2016
Committer: Vasily Kolobkov on 05/26/2016
Fetch, delete, rinse and repeat
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(-)
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]);