mblaze/mpick.c

1452 lines
26 KiB
C
Raw Normal View History

2016-09-05 12:03:46 +00:00
// FNM_CASEFOLD
2016-07-22 01:10:17 +00:00
#define _GNU_SOURCE
2016-09-05 12:03:46 +00:00
#include <fnmatch.h>
// strptime
2016-07-28 15:53:40 +00:00
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#ifdef __has_include
#if __has_include(<stdnoreturn.h>)
#include <stdnoreturn.h>
#else
#define noreturn /**/
#endif
#else
#define noreturn /**/
#endif
2016-07-28 15:53:40 +00:00
/* For Solaris. */
#if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE)
#define FNM_CASEFOLD FNM_IGNORECASE
#endif
2016-07-22 01:10:17 +00:00
#include <sys/ioctl.h>
#include <sys/mman.h>
2016-07-22 01:10:17 +00:00
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
2017-08-31 15:30:17 +00:00
#include <limits.h>
#include <locale.h>
#include <regex.h>
#include <stdarg.h>
2016-07-22 01:10:17 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2016-07-28 15:53:40 +00:00
#include <strings.h>
2016-07-22 01:10:17 +00:00
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include "blaze822.h"
enum op {
EXPR_OR = 1,
EXPR_AND,
EXPR_COND,
2016-07-22 01:10:17 +00:00
EXPR_NOT,
EXPR_LT,
EXPR_LE,
EXPR_EQ,
EXPR_NEQ,
EXPR_GE,
EXPR_GT,
EXPR_STREQ,
EXPR_STREQI,
EXPR_GLOB,
EXPR_GLOBI,
EXPR_REGEX,
EXPR_REGEXI,
EXPR_PRUNE,
2016-07-22 01:10:17 +00:00
EXPR_PRINT,
2019-01-28 09:53:05 +00:00
EXPR_REDIR_FILE,
EXPR_REDIR_PIPE,
2016-07-22 01:10:17 +00:00
EXPR_TYPE,
EXPR_ALLSET,
EXPR_ANYSET,
};
enum prop {
PROP_ATIME = 1,
PROP_CTIME,
PROP_DEPTH,
2016-07-31 15:05:20 +00:00
PROP_KEPT,
2016-07-22 01:10:17 +00:00
PROP_MTIME,
PROP_PATH,
2016-07-24 15:59:11 +00:00
PROP_REPLIES,
2016-07-22 01:10:17 +00:00
PROP_SIZE,
PROP_TOTAL,
PROP_FROM,
PROP_TO,
PROP_INDEX,
PROP_DATE,
PROP_FLAG,
};
enum flags {
FLAG_PASSED = 1,
FLAG_REPLIED = 2,
FLAG_SEEN = 4,
FLAG_TRASHED = 8,
FLAG_DRAFT = 16,
FLAG_FLAGGED = 32,
/* custom */
FLAG_NEW = 64,
FLAG_CUR = 128,
FLAG_PARENT = 256,
FLAG_CHILD = 512,
FLAG_INFO = 1024,
2016-07-22 01:10:17 +00:00
};
enum var {
VAR_CUR = 1,
};
2016-07-22 01:10:17 +00:00
struct expr {
enum op op;
union {
enum prop prop;
struct expr *expr;
char *string;
int64_t num;
regex_t *regex;
enum var var;
} a, b, c;
int extra;
2016-07-22 01:10:17 +00:00
};
struct mailinfo {
char *file;
2016-07-22 01:10:17 +00:00
char *fpath;
struct stat *sb;
struct message *msg;
time_t date;
int depth;
int index;
int replies;
int matched;
int prune;
2017-03-30 16:32:52 +00:00
int flags;
2016-07-22 01:10:17 +00:00
off_t total;
};
struct mlist {
struct mailinfo *m;
2016-07-24 15:59:11 +00:00
struct mlist *parent;
struct mlist *next;
};
struct thread {
int matched;
struct mlist childs[100];
struct mlist *cur;
};
2019-01-28 09:53:05 +00:00
struct file {
enum op op;
const char *name;
const char *mode;
FILE *fp;
struct file *next;
};
struct pos {
char *pos;
char *line;
size_t linenr;
};
static struct thread *thr;
2016-07-22 01:10:17 +00:00
static char *argv0;
static int Tflag;
2016-07-22 01:10:17 +00:00
2016-07-23 16:45:23 +00:00
static int need_thr;
2016-07-22 01:10:17 +00:00
static long kept;
2016-07-22 23:16:24 +00:00
static long num;
2016-07-22 01:10:17 +00:00
static struct expr *expr;
static long cur_idx;
2016-07-22 01:10:17 +00:00
static char *cur;
static time_t now;
static int prune;
2016-07-22 01:10:17 +00:00
static char *pos;
static const char *fname;
static char *line = NULL;
static int linenr = 0;
2019-01-28 09:53:05 +00:00
static struct file *files, *fileq = NULL;
2019-01-28 13:45:40 +00:00
static void *
xcalloc(size_t nmemb, size_t size)
{
void *r;
if ((r = calloc(nmemb, size)))
return r;
perror("calloc");
exit(2);
}
static char *
xstrdup(const char *s)
{
char *r;
if ((r = strdup(s)))
return r;
perror("strdup");
exit(2);
}
2016-07-22 01:10:17 +00:00
static void
ws()
{
for (; *pos;) {
while (isspace((unsigned char)*pos)) {
if (*pos == '\n') {
line = pos+1;
linenr++;
}
pos++;
}
if (*pos != '#')
break;
pos += strcspn(pos, "\n\0");
if (*pos != '\n')
break;
}
2016-07-22 01:10:17 +00:00
}
static int
2016-07-28 16:09:10 +00:00
token(char *token)
2016-07-22 01:10:17 +00:00
{
if (strncmp(pos, token, strlen(token)) == 0) {
pos += strlen(token);
ws();
return 1;
} else {
return 0;
}
}
2019-01-28 09:53:05 +00:00
static int
peek(char *token)
{
return strncmp(pos, token, strlen(token)) == 0;
}
noreturn static void
2016-07-28 16:09:10 +00:00
parse_error(char *msg, ...)
2016-07-22 01:10:17 +00:00
{
va_list ap;
va_start(ap, msg);
fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
2016-07-22 01:10:17 +00:00
vfprintf(stderr, msg, ap);
fprintf(stderr, "\n");
exit(2);
}
noreturn static void
parse_error_at(struct pos *savepos, char *msg, ...)
{
char *e;
if (savepos) {
pos = savepos->pos;
line = savepos->line;
linenr = savepos->linenr;
}
va_list ap;
va_start(ap, msg);
fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1);
vfprintf(stderr, msg, ap);
fprintf(stderr, " at '");
for (e = pos+15; *pos && *pos != '\n' && pos <= e; pos++)
putc(*pos, stderr);
fprintf(stderr, "'\n");
exit(2);
}
2016-07-22 01:10:17 +00:00
static struct expr *
mkexpr(enum op op)
{
struct expr *e = malloc(sizeof (struct expr));
if (!e)
parse_error("out of memory");
e->op = op;
2017-03-30 16:32:52 +00:00
e->extra = 0;
2016-07-22 01:10:17 +00:00
return e;
}
2019-01-28 13:38:05 +00:00
static void
freeexpr(struct expr *e)
{
if (!e) return;
switch (e->op) {
case EXPR_OR:
case EXPR_AND:
freeexpr(e->a.expr);
freeexpr(e->b.expr);
break;
case EXPR_COND:
freeexpr(e->a.expr);
freeexpr(e->b.expr);
freeexpr(e->c.expr);
break;
case EXPR_NOT:
freeexpr(e->a.expr);
break;
case EXPR_REDIR_FILE:
case EXPR_REDIR_PIPE:
free(e->a.string);
free(e->b.string);
break;
case EXPR_STREQ:
case EXPR_STREQI:
case EXPR_GLOB:
case EXPR_GLOBI:
case EXPR_REGEX:
case EXPR_REGEXI:
switch (e->a.prop) {
case PROP_PATH:
case PROP_FROM:
case PROP_TO:
break;
default: free(e->a.string);
}
if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI)
regfree(e->b.regex);
else
free(e->b.string);
}
free(e);
}
2016-07-22 01:10:17 +00:00
static struct expr *
chain(struct expr *e1, enum op op, struct expr *e2)
{
struct expr *i, *j, *e;
if (!e1)
return e2;
if (!e2)
return e1;
for (j = 0, i = e1; i->op == op; j = i, i = i->b.expr)
;
if (!j) {
e = mkexpr(op);
e->a.expr = e1;
e->b.expr = e2;
return e;
} else {
e = mkexpr(op);
e->a.expr = i;
e->b.expr = e2;
j->b.expr = e;
return e1;
}
}
static enum op
parse_op()
{
if (token("<="))
return EXPR_LE;
else if (token("<"))
return EXPR_LT;
else if (token(">="))
return EXPR_GE;
else if (token(">"))
return EXPR_GT;
else if (token("==") || token("="))
return EXPR_EQ;
else if (token("!="))
return EXPR_NEQ;
return 0;
}
static struct expr *parse_cmp();
static struct expr *parse_cond();
2016-07-22 01:10:17 +00:00
static struct expr *
parse_inner()
{
if (token("prune")) {
return mkexpr(EXPR_PRUNE);
} else if (token("print")) {
return mkexpr(EXPR_PRINT);
2019-01-28 09:58:27 +00:00
} else if (token("skip")) {
struct expr *e = mkexpr(EXPR_PRINT);
struct expr *not = mkexpr(EXPR_NOT);
not->a.expr = e;
return not;
2016-07-22 01:10:17 +00:00
} else if (token("!")) {
struct expr *e = parse_cmp();
struct expr *not = mkexpr(EXPR_NOT);
not->a.expr = e;
return not;
}
if (peek("(")) {
struct pos savepos = { pos, line, linenr };
(void) token("(");
struct expr *e = parse_cond();
2016-07-22 01:10:17 +00:00
if (token(")"))
return e;
parse_error_at(&savepos, "unterminated (");
2016-07-22 01:10:17 +00:00
return 0;
} else {
parse_error("unknown expression at '%.15s'", pos);
return 0;
}
}
static int
parse_string(char **s)
{
char *buf = 0;
size_t bufsiz = 0;
size_t len = 0;
if (*pos == '"') {
pos++;
while (*pos &&
(*pos != '"' || (*pos == '"' && *(pos+1) == '"'))) {
if (len+1 >= bufsiz) {
2016-07-22 01:10:17 +00:00
bufsiz = 2*bufsiz + 16;
buf = realloc(buf, bufsiz);
if (!buf)
parse_error("string too long");
}
if (*pos == '"')
pos++;
buf[len++] = *pos++;
}
if (!*pos)
parse_error("unterminated string");
if (buf)
buf[len] = 0;
pos++;
ws();
2016-07-28 16:09:10 +00:00
*s = buf ? buf : "";
2016-07-22 01:10:17 +00:00
return 1;
} else if (*pos == '$') {
char t;
char *e = ++pos;
2017-08-31 15:30:17 +00:00
while (isalnum((unsigned char)*pos) || *pos == '_')
2016-07-22 01:10:17 +00:00
pos++;
if (e == pos)
parse_error("invalid environment variable name");
t = *pos;
*pos = 0;
*s = getenv(e);
if (!*s)
2016-07-28 16:09:10 +00:00
*s = "";
2016-07-22 01:10:17 +00:00
*pos = t;
ws();
return 1;
}
return 0;
}
static struct expr *
parse_strcmp()
{
enum prop prop;
enum op op;
2017-10-26 19:50:29 +00:00
int negate;
char *h;
2016-07-22 01:10:17 +00:00
h = 0;
prop = 0;
2017-10-26 19:50:29 +00:00
negate = 0;
if (token("from"))
2016-07-22 01:10:17 +00:00
prop = PROP_FROM;
else if (token("to"))
prop = PROP_TO;
else if (token("subject"))
h = "subject";
else if (!parse_string(&h))
2016-07-22 01:10:17 +00:00
return parse_inner();
if (token("~~~"))
op = EXPR_GLOBI;
else if (token("~~"))
op = EXPR_GLOB;
else if (token("=~~"))
op = EXPR_REGEXI;
else if (token("=~"))
op = EXPR_REGEX;
else if (token("==="))
op = EXPR_STREQI;
else if (token("=="))
op = EXPR_STREQ;
else if (token("="))
op = EXPR_STREQ;
2017-10-26 19:50:29 +00:00
else if (token("!~~~"))
negate = 1, op = EXPR_GLOBI;
else if (token("!~~"))
negate = 1, op = EXPR_GLOB;
else if (token("!=~~"))
negate = 1, op = EXPR_REGEXI;
else if (token("!=~"))
negate = 1, op = EXPR_REGEX;
else if (token("!==="))
negate = 1, op = EXPR_STREQI;
else if (token("!==") || token("!="))
negate = 1, op = EXPR_STREQ;
2016-07-22 01:10:17 +00:00
else
parse_error_at(NULL, "invalid string operator");
2016-07-22 01:10:17 +00:00
char *s;
if (!parse_string(&s)) {
parse_error_at(NULL, "invalid string");
return 0;
}
2016-07-22 01:10:17 +00:00
int r = 0;
struct expr *e = mkexpr(op);
if (prop)
e->a.prop = prop;
else
e->a.string = h;
if (prop == PROP_FROM || prop == PROP_TO) {
char *disp, *addr;
blaze822_addr(s, &disp, &addr);
if (!disp && !addr)
2019-01-28 13:45:40 +00:00
parse_error_at(NULL, "invalid address");
free(s);
s = xstrdup((disp) ? disp : addr);
e->extra = (disp) ? 0 : 1;
}
2016-07-22 01:10:17 +00:00
2019-01-28 13:45:40 +00:00
if (op == EXPR_REGEX || op == EXPR_REGEXI) {
e->b.regex = malloc(sizeof (regex_t));
2019-01-28 13:45:40 +00:00
r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB |
(op == EXPR_REGEXI ? REG_ICASE : 0));
if (r != 0) {
char msg[256];
regerror(r, e->b.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", s, msg);
exit(2);
}
free(s);
} else {
e->b.string = s;
2016-07-22 01:10:17 +00:00
}
2017-10-26 19:50:29 +00:00
if (negate) {
struct expr *not = mkexpr(EXPR_NOT);
not->a.expr = e;
return not;
}
return e;
2016-07-22 01:10:17 +00:00
}
static int64_t
parse_num(int64_t *r)
{
char *s = pos;
2017-08-31 15:30:17 +00:00
if (isdigit((unsigned char)*pos)) {
2016-07-22 01:10:17 +00:00
int64_t n;
2017-08-31 15:30:17 +00:00
for (n = 0; isdigit((unsigned char)*pos) && n <= INT64_MAX / 10 - 10; pos++)
2016-07-22 01:10:17 +00:00
n = 10 * n + (*pos - '0');
2017-08-31 15:30:17 +00:00
if (isdigit((unsigned char)*pos))
2016-07-22 01:10:17 +00:00
parse_error("number too big: %s", s);
if (token("c")) ;
else if (token("b")) n *= 512LL;
else if (token("k")) n *= 1024LL;
else if (token("M")) n *= 1024LL * 1024;
else if (token("G")) n *= 1024LL * 1024 * 1024;
else if (token("T")) n *= 1024LL * 1024 * 1024 * 1024;
ws();
*r = n;
return 1;
} else {
return 0;
}
}
static struct expr *
parse_flag()
{
enum flags flag;
if (token("passed")) {
flag = FLAG_PASSED;
} else if (token("replied")) {
flag = FLAG_REPLIED;
} else if (token("seen")) {
flag = FLAG_SEEN;
} else if (token("trashed")) {
flag = FLAG_TRASHED;
} else if (token("draft")) {
flag = FLAG_DRAFT;
} else if (token("flagged")) {
flag = FLAG_FLAGGED;
} else if (token("new")) {
flag = FLAG_NEW;
} else if (token("cur")) {
flag = FLAG_CUR;
} else if (token("info")) {
flag = FLAG_INFO;
2016-07-23 16:45:23 +00:00
} else if (token("parent")) {
flag = FLAG_PARENT;
need_thr = 1;
} else if (token("child")) {
flag = FLAG_CHILD;
need_thr = 1;
2016-07-22 01:10:17 +00:00
} else
return parse_strcmp();
struct expr *e = mkexpr(EXPR_ANYSET);
e->a.prop = PROP_FLAG;
e->b.num = flag;
return e;
}
static struct expr *
parse_cmp()
{
enum prop prop;
enum op op;
if (token("depth"))
prop = PROP_DEPTH;
2016-07-31 15:05:20 +00:00
else if (token("kept"))
prop = PROP_KEPT;
2016-07-22 01:10:17 +00:00
else if (token("index"))
prop = PROP_INDEX;
2016-07-24 15:59:11 +00:00
else if (token("replies")) {
prop = PROP_REPLIES;
need_thr = 1;
} else if (token("size"))
2016-07-22 01:10:17 +00:00
prop = PROP_SIZE;
else if (token("total"))
prop = PROP_TOTAL;
else
return parse_flag();
if (!(op = parse_op()))
parse_error_at(NULL, "invalid comparison");
2016-07-22 01:10:17 +00:00
int64_t n;
if (parse_num(&n)) {
struct expr *e = mkexpr(op);
e->a.prop = prop;
e->b.num = n;
return e;
2017-08-31 15:30:17 +00:00
} else if (token("cur")) {
struct expr *e = mkexpr(op);
e->a.prop = prop;
e->b.var = VAR_CUR;
e->extra = 1;
return e;
2016-07-22 01:10:17 +00:00
}
return 0;
}
static int
parse_dur(int64_t *n)
{
char *s, *r;
if (!parse_string(&s))
return 0;
if (*s == '/' || *s == '.') {
struct stat st;
if (stat(s, &st) < 0)
parse_error("can't stat file '%s': %s",
s, strerror(errno));
*n = st.st_mtime;
return 1;
}
struct tm tm = { 0 };
r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm);
if (r && !*r) {
*n = mktime(&tm);
return 1;
}
r = strptime(s, "%Y-%m-%d", &tm);
if (r && !*r) {
tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
*n = mktime(&tm);
return 1;
}
r = strptime(s, "%H:%M:%S", &tm);
if (r && !*r) {
struct tm *tmnow = localtime(&now);
tm.tm_year = tmnow->tm_year;
tm.tm_mon = tmnow->tm_mon;
tm.tm_mday = tmnow->tm_mday;
*n = mktime(&tm);
return 1;
}
r = strptime(s, "%H:%M", &tm);
if (r && !*r) {
struct tm *tmnow = localtime(&now);
tm.tm_year = tmnow->tm_year;
tm.tm_mon = tmnow->tm_mon;
tm.tm_mday = tmnow->tm_mday;
tm.tm_sec = 0;
*n = mktime(&tm);
return 1;
}
if (*s == '-') {
s++;
errno = 0;
int64_t d;
d = strtol(s, &r, 10);
if (errno == 0 && r[0] == 'd' && !r[1]) {
struct tm *tmnow = localtime(&now);
tmnow->tm_mday -= d;
tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0;
*n = mktime(tmnow);
return 1;
}
if (errno == 0 && r[0] == 'h' && !r[1]) {
*n = now - (d*60*60);
return 1;
}
if (errno == 0 && r[0] == 'm' && !r[1]) {
*n = now - (d*60);
return 1;
}
if (errno == 0 && r[0] == 's' && !r[1]) {
*n = now - d;
return 1;
}
parse_error("invalid relative time format '%s'", s-1);
}
parse_error("invalid time format '%s'", s);
return 0;
}
static struct expr *
parse_timecmp()
{
enum prop prop;
enum op op;
if (token("atime"))
prop = PROP_ATIME;
else if (token("ctime"))
prop = PROP_CTIME;
else if (token("mtime"))
prop = PROP_MTIME;
else if (token("date"))
prop = PROP_DATE;
else
return parse_cmp();
op = parse_op();
if (!op)
parse_error_at(NULL, "invalid comparison");
2016-07-22 01:10:17 +00:00
int64_t n;
if (parse_num(&n) || parse_dur(&n)) {
struct expr *e = mkexpr(op);
e->a.prop = prop;
e->b.num = n;
return e;
}
return 0;
}
2019-01-28 09:53:05 +00:00
static struct expr *
parse_redir(struct expr *e)
{
char *s;
const char *m;
if (peek("||"))
return e;
if (token("|")) {
if (!parse_string(&s))
parse_error_at(NULL, "expected command");
2019-01-28 09:53:05 +00:00
struct expr *r = mkexpr(EXPR_REDIR_PIPE);
r->a.string = s;
2019-01-28 13:45:40 +00:00
r->b.string = xstrdup("w");
2019-01-28 09:53:05 +00:00
return chain(e, EXPR_AND, r);
}
else if (token(">>")) m = "a+";
else if (token(">")) m = "w+";
else return e;
if (!parse_string(&s))
parse_error_at(NULL, "expected file name");
2019-01-28 09:53:05 +00:00
struct expr *r = mkexpr(EXPR_REDIR_FILE);
r->a.string = s;
2019-01-28 13:45:40 +00:00
r->b.string = xstrdup(m);
2019-01-28 09:53:05 +00:00
return chain(e, EXPR_AND, r);
}
2016-07-22 01:10:17 +00:00
static struct expr *
parse_and()
{
2019-01-28 09:53:05 +00:00
struct expr *e1 = parse_redir(parse_timecmp());
2016-07-22 01:10:17 +00:00
struct expr *r = e1;
while (token("&&")) {
2019-01-28 09:53:05 +00:00
struct expr *e2 = parse_redir(parse_timecmp());
2016-07-22 01:10:17 +00:00
r = chain(r, EXPR_AND, e2);
}
return r;
}
static struct expr *
parse_or()
{
struct expr *e1 = parse_and();
struct expr *r = e1;
while (token("||")) {
struct expr *e2 = parse_and();
r = chain(r, EXPR_OR, e2);
}
return r;
}
static struct expr *
parse_cond()
{
struct expr *e1 = parse_or();
if (token("?")) {
struct expr *e2 = parse_or();
if (token(":")) {
struct expr *e3 = parse_cond();
struct expr *r = mkexpr(EXPR_COND);
r->a.expr = e1;
r->b.expr = e2;
r->c.expr = e3;
return r;
} else {
parse_error_at(NULL, "expected :", pos);
}
}
return e1;
}
static struct expr *
parse_expr()
2016-07-22 01:10:17 +00:00
{
ws();
return parse_cond();
}
static struct expr *
parse_buf(const char *f, char *s)
{
struct expr *e;
fname = f;
line = pos = s;
linenr = 1;
e = parse_expr();
2016-07-22 01:10:17 +00:00
if (*pos)
parse_error_at(NULL, "trailing garbage");
2016-07-22 01:10:17 +00:00
return e;
}
static struct expr *
2016-07-28 16:09:10 +00:00
parse_msglist(char *s)
2016-07-22 01:10:17 +00:00
{
int64_t n, m;
int r;
struct expr *e1, *e2;
char *d;
enum flags flag;
2016-07-22 01:10:17 +00:00
switch (*s) {
case '/':
s++;
e1 = mkexpr(EXPR_REGEXI);
e1->a.string = xstrdup("subject");
2016-07-22 01:10:17 +00:00
e1->b.regex = malloc(sizeof (regex_t));
r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE);
if (r != 0) {
char msg[256];
regerror(r, e1->b.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", s, msg);
}
return e1;
case ':':
n = 0;
switch (*++s) {
case '\0': parse_error("missing flag at '%s'", s-1);
2016-07-22 01:10:17 +00:00
case 'P': flag = FLAG_PASSED; break;
case 'F': flag = FLAG_FLAGGED; break;
case 'D': flag = FLAG_DRAFT; break;
2017-03-29 16:04:55 +00:00
case 'd': /* FALL THROUGH */
2016-07-22 01:10:17 +00:00
case 'T': flag = FLAG_TRASHED; break;
2017-03-29 16:04:55 +00:00
case 'u': n = 1; /* FALL THROUGH */
case 'r': /* FALL THROUGH */
2016-07-22 01:10:17 +00:00
case 'S': flag = FLAG_SEEN; break;
2017-03-29 16:04:55 +00:00
case 'o': n = 1; /* FALL THROUGH */
2016-07-22 01:10:17 +00:00
case 'n': flag = FLAG_NEW; break;
2017-03-29 18:21:31 +00:00
case 'R': flag = FLAG_REPLIED; break;
default: parse_error("unknown flag at '%s'", s);
2016-07-22 01:10:17 +00:00
}
e1 = mkexpr(EXPR_ANYSET);
e1->a.prop = PROP_FLAG;
e1->b.num = flag;
if (!n)
return e1;
e2 = mkexpr(EXPR_NOT);
e2->a.expr = e1;
return e2;
default:
2016-07-28 16:09:10 +00:00
pos = s;
2016-07-22 01:10:17 +00:00
if (((d = strchr(s, ':')) || (d = strchr(s, '-')))
2016-07-28 16:09:10 +00:00
&& parse_num(&n) && (pos = d + 1) && parse_num(&m)) {
2016-07-22 01:10:17 +00:00
/* index >= n */
e1 = mkexpr(EXPR_GE);
e1->a.prop = PROP_INDEX;
e1->b.num = n;
/* index <= m */
e2 = mkexpr(EXPR_LE);
e2->a.prop = PROP_INDEX;
e2->b.num = m;
/* e1 && e2 */
return chain(e1, EXPR_AND, e2);
} else if (parse_num(&n)) {
e1 = mkexpr(EXPR_EQ);
e1->a.prop = PROP_INDEX;
e1->b.num = n;
return e1;
} else {
char *disp, *addr;
blaze822_addr(s, &disp, &addr);
if (!disp && !addr)
parse_error("invalid address '%s'", s);
d = (disp) ? disp : addr;
e1 = mkexpr(EXPR_REGEXI);
e1->a.prop = PROP_FROM;
e1->b.regex = malloc(sizeof (regex_t));
e1->extra = (disp) ? 0 : 1;
r = regcomp(e1->b.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE);
if (r != 0) {
char msg[256];
regerror(r, e1->b.regex, msg, sizeof msg);
parse_error("invalid regex '%s': %s", d, msg);
}
return e1;
2016-07-22 01:10:17 +00:00
}
}
2016-07-28 15:53:51 +00:00
return 0;
2016-07-22 01:10:17 +00:00
}
time_t
msg_date(struct mailinfo *m)
{
if (m->date)
return m->date;
// XXX: date comparisation should handle zero dates
if (!m->msg && m->fpath) {
if (!(m->msg = blaze822(m->fpath))) {
m->fpath = NULL;
return -1;
}
}
2016-07-22 01:10:17 +00:00
char *b;
2016-07-24 15:59:11 +00:00
if (m->msg && (b = blaze822_hdr(m->msg, "date")))
2016-07-22 01:10:17 +00:00
return (m->date = blaze822_date(b));
return -1;
}
char *
msg_hdr(struct mailinfo *m, const char *h)
2016-07-22 01:10:17 +00:00
{
static char hdrbuf[4096];
if (!m->msg && m->fpath) {
if (!(m->msg = blaze822(m->fpath))) {
m->fpath = NULL;
*hdrbuf = 0;
return hdrbuf;
}
}
2017-03-30 17:36:56 +00:00
char *b;
if (!m->msg || !(b = blaze822_chdr(m->msg, h))) {
*hdrbuf = 0;
return hdrbuf;
}
2016-07-22 01:10:17 +00:00
blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8");
return hdrbuf;
2016-07-22 01:10:17 +00:00
}
char *
msg_addr(struct mailinfo *m, char *h, int t)
{
if (!m->msg && m->fpath) {
if (!(m->msg = blaze822(m->fpath))) {
m->fpath = NULL;
return "";
}
}
char *b;
if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0)
return "";
char *disp, *addr;
blaze822_addr(b, &disp, &addr);
if (t) {
if (!addr)
return "";
return addr;
} else {
if (!disp)
return "";
return disp;
}
}
2019-01-28 09:53:05 +00:00
FILE *
redir(struct expr *e)
{
struct file *file;
FILE *fp;
for (file = files; file; file = file->next) {
if (e->op == file->op &&
strcmp(e->a.string, file->name) == 0 &&
strcmp(e->b.string, file->mode) == 0)
return file->fp;
}
fflush(stdout);
fp = NULL;
switch (e->op) {
case EXPR_REDIR_FILE: fp = fopen(e->a.string, e->b.string); break;
case EXPR_REDIR_PIPE: fp = popen(e->a.string, e->b.string); break;
}
if (!fp) {
fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno));
exit(3);
}
2019-01-28 13:45:40 +00:00
file = xcalloc(1, sizeof (struct file));
2019-01-28 09:53:05 +00:00
file->op = e->op;
file->name = e->a.string;
file->mode = e->b.string;
file->fp = fp;
if (!files) files = file;
if (fileq) fileq->next = file;
fileq = file;
return fp;
}
2016-07-22 01:10:17 +00:00
int
eval(struct expr *e, struct mailinfo *m)
{
2019-01-28 09:53:05 +00:00
FILE *fp;
2016-07-22 01:10:17 +00:00
switch (e->op) {
case EXPR_OR:
return eval(e->a.expr, m) || eval(e->b.expr, m);
case EXPR_AND:
return eval(e->a.expr, m) && eval(e->b.expr, m);
case EXPR_COND:
return eval(e->a.expr, m)
? eval(e->b.expr, m)
: eval(e->c.expr, m);
2016-07-22 01:10:17 +00:00
case EXPR_NOT:
return !eval(e->a.expr, m);
return 1;
case EXPR_PRUNE:
prune = 1;
return 1;
2016-07-22 01:10:17 +00:00
case EXPR_PRINT:
return 1;
2019-01-28 09:53:05 +00:00
case EXPR_REDIR_FILE:
case EXPR_REDIR_PIPE:
fp = redir(e);
fputs(m->file, fp);
2019-01-28 09:53:05 +00:00
putc('\n', fp);
fflush(fp);
return 1;
2016-07-22 01:10:17 +00:00
case EXPR_LT:
case EXPR_LE:
case EXPR_EQ:
case EXPR_NEQ:
case EXPR_GE:
case EXPR_GT:
case EXPR_ALLSET:
case EXPR_ANYSET: {
2016-07-31 14:51:49 +00:00
long v = 0, n;
2016-07-22 01:10:17 +00:00
if (!m->sb && m->fpath && (
2016-07-22 01:10:17 +00:00
e->a.prop == PROP_ATIME ||
e->a.prop == PROP_CTIME ||
e->a.prop == PROP_MTIME ||
e->a.prop == PROP_SIZE)) {
m->sb = xcalloc(1, sizeof *m->sb);
if (stat(m->fpath, m->sb) == -1)
m->fpath = NULL;
// XXX: stat based expressions should handle 0
2016-07-22 01:10:17 +00:00
}
2017-03-30 16:32:52 +00:00
n = e->b.num;
if (e->extra)
switch (e->b.var) {
case VAR_CUR:
if (!cur_idx)
2016-07-31 14:51:49 +00:00
n = (e->op == EXPR_LT || e->op == EXPR_LE) ? LONG_MAX : -1;
else
2016-07-31 14:51:49 +00:00
n = cur_idx;
break;
}
2016-07-22 01:10:17 +00:00
switch (e->a.prop) {
case PROP_ATIME: if (m->sb) v = m->sb->st_atime; break;
case PROP_CTIME: if (m->sb) v = m->sb->st_ctime; break;
case PROP_MTIME: if (m->sb) v = m->sb->st_mtime; break;
2016-07-31 15:05:20 +00:00
case PROP_KEPT: v = kept; break;
2016-07-24 15:59:11 +00:00
case PROP_REPLIES: v = m->replies; break;
case PROP_SIZE: if (m->sb) v = m->sb->st_size; break;
2016-07-29 00:20:24 +00:00
case PROP_DATE: v = msg_date(m); break;
2016-07-22 01:10:17 +00:00
case PROP_FLAG: v = m->flags; break;
case PROP_INDEX: v = m->index; break;
case PROP_DEPTH: v = m->depth; break;
default: parse_error("unknown property");
2016-07-22 01:10:17 +00:00
}
switch (e->op) {
2016-07-31 14:51:49 +00:00
case EXPR_LT: return v < n;
case EXPR_LE: return v <= n;
case EXPR_EQ: return v == n;
case EXPR_NEQ: return v != n;
case EXPR_GE: return v >= n;
case EXPR_GT: return v > n;
case EXPR_ALLSET: return (v & n) == n;
case EXPR_ANYSET: return (v & n) > 0;
default: parse_error("invalid operator");
2016-07-22 01:10:17 +00:00
}
}
case EXPR_STREQ:
case EXPR_STREQI:
case EXPR_GLOB:
case EXPR_GLOBI:
case EXPR_REGEX:
case EXPR_REGEXI: {
const char *s;
2017-08-31 15:30:17 +00:00
switch (e->a.prop) {
case PROP_PATH: s = m->fpath ? m->fpath : ""; break;
case PROP_FROM: s = msg_addr(m, "from", e->extra); break;
case PROP_TO: s = msg_addr(m, "to", e->extra); break;
default: s = msg_hdr(m, e->a.string); break;
2016-07-22 01:10:17 +00:00
}
switch (e->op) {
case EXPR_STREQ: return strcmp(e->b.string, s) == 0;
case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0;
case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0;
case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0;
case EXPR_REGEX:
case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0;
}
}
}
return 0;
}
struct mailinfo *
mailfile(struct mailinfo *m, char *file)
2016-07-22 01:10:17 +00:00
{
static int init;
if (!init) {
// delay loading of the seqmap until we need to scan the first
// file, in case someone in the pipe updated the map before
char *seqmap = blaze822_seq_open(0);
blaze822_seq_load(seqmap);
cur = blaze822_seq_cur();
init = 1;
}
char *fpath = file;
2016-07-22 01:10:17 +00:00
m->index = num++;
m->file = file;
while (*fpath == ' ' || *fpath == '\t') {
m->depth++;
fpath++;
2016-07-22 01:10:17 +00:00
}
char *e = fpath + strlen(fpath) - 1;
while (fpath < e && (*e == ' ' || *e == '\t'))
2016-07-22 01:10:17 +00:00
*e-- = 0;
if (fpath[0] == '<') {
m->flags |= FLAG_SEEN | FLAG_INFO;
m->fpath = NULL;
return m;
}
if ((e = strrchr(fpath, '/') - 1) && (e - fpath) >= 2 &&
2016-07-22 19:34:54 +00:00
*e-- == 'w' && *e-- == 'e' && *e-- == 'n')
m->flags |= FLAG_NEW;
2016-07-22 01:10:17 +00:00
if (cur && strcmp(cur, fpath) == 0) {
m->flags |= FLAG_CUR;
cur_idx = m->index;
}
2016-07-22 01:10:17 +00:00
char *f = strstr(fpath, ":2,");
2016-07-22 01:10:17 +00:00
if (f) {
if (strchr(f, 'P'))
m->flags |= FLAG_PASSED;
2016-07-22 01:10:17 +00:00
if (strchr(f, 'R'))
m->flags |= FLAG_REPLIED;
2016-07-22 01:10:17 +00:00
if (strchr(f, 'S'))
m->flags |= FLAG_SEEN;
2016-07-22 01:10:17 +00:00
if (strchr(f, 'T'))
m->flags |= FLAG_TRASHED;
2016-07-22 01:10:17 +00:00
if (strchr(f, 'D'))
m->flags |= FLAG_DRAFT;
2016-07-22 01:10:17 +00:00
if (strchr(f, 'F'))
m->flags |= FLAG_FLAGGED;
}
m->fpath = fpath;
return m;
}
void
do_thr()
{
struct mlist *ml;
if (!thr)
return;
for (ml = thr->childs; ml; ml = ml->next) {
if (!ml->m)
continue;
if ((ml->m->prune = prune) || (Tflag && thr->matched))
continue;
if (expr && eval(expr, ml->m)) {
ml->m->matched = 1;
thr->matched++;
}
}
prune = 0;
for (ml = thr->childs; ml; ml = ml->next) {
if (!ml->m)
break;
if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) {
fputs(ml->m->file, stdout);
2019-01-28 09:53:05 +00:00
putc('\n', stdout);
kept++;
}
/* free collected mails */
if (ml->m->msg)
blaze822_free(ml->m->msg);
if (ml->m->sb)
free(ml->m->sb);
free(ml->m->file);
free(ml->m);
}
free(thr);
2016-07-24 15:59:11 +00:00
thr = 0;
}
void
collect(char *file)
{
struct mailinfo *m;
struct mlist *ml;
char *f;
f = xstrdup(file);
m = xcalloc(1, sizeof *m);
m = mailfile(m, f);
if (m->depth == 0) {
/* process previous thread */
if (thr)
do_thr();
/* new thread */
2019-01-28 13:45:40 +00:00
thr = xcalloc(1, sizeof *thr);
thr->matched = 0;
2016-07-23 16:45:23 +00:00
ml = thr->cur = thr->childs;
thr->cur->m = m;
} else {
2016-07-24 15:59:11 +00:00
ml = thr->cur + 1;
if (thr->cur->m->depth < m->depth) {
/* previous mail is a parent */
2016-07-23 16:45:23 +00:00
thr->cur->m->flags |= FLAG_PARENT;
2016-07-24 15:59:11 +00:00
ml->parent = thr->cur;
} else if (thr->cur->m->depth == m->depth) {
/* same depth == same parent */
ml->parent = thr->cur->parent;
} else if (thr->cur->m->depth > m->depth) {
/* find parent mail */
struct mlist *pl;
2017-08-31 15:30:17 +00:00
for (pl = thr->cur; pl->m->depth >= m->depth; pl--) ;
2016-07-24 15:59:11 +00:00
ml->parent = pl;
}
2016-07-23 16:45:23 +00:00
m->flags |= FLAG_CHILD;
thr->cur->next = ml;
thr->cur = ml;
ml->m = m;
2016-07-22 01:10:17 +00:00
}
2016-07-24 15:59:11 +00:00
for (ml = ml->parent; ml; ml = ml->parent)
ml->m->replies++;
}
void
oneline(char *file)
{
struct mailinfo m = { 0 };
m.index = num++;
(void) mailfile(&m, file);
if (expr && !eval(expr, &m))
2016-07-22 01:10:17 +00:00
goto out;
2016-07-24 15:59:11 +00:00
fputs(file, stdout);
2019-01-28 09:53:05 +00:00
putc('\n', stdout);
fflush(stdout);
2016-07-22 01:10:17 +00:00
kept++;
out:
if (m.msg)
blaze822_free(m.msg);
if (m.sb)
free(m.sb);
2016-07-22 01:10:17 +00:00
}
int
main(int argc, char *argv[])
{
long i;
int c;
int vflag;
2016-07-22 01:10:17 +00:00
argv0 = argv[0];
2016-07-29 00:20:24 +00:00
now = time(0);
num = 1;
vflag = 0;
2016-07-22 01:10:17 +00:00
while ((c = getopt(argc, argv, "Tt:v")) != -1)
2016-07-22 01:10:17 +00:00
switch (c) {
2016-07-23 16:45:23 +00:00
case 'T': Tflag = need_thr = 1; break;
case 't': expr = chain(expr, EXPR_AND, parse_buf("argv", optarg)); break;
case 'v': vflag = 1; break;
default:
2018-09-25 14:37:07 +00:00
fprintf(stderr, "Usage: %s [-Tv] [-t test] [msglist ...]\n", argv0);
exit(1);
2016-07-22 01:10:17 +00:00
}
if (optind != argc) {
for (c = optind; c < argc; c++) {
if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0)
break;
2016-07-22 01:10:17 +00:00
expr = chain(expr, EXPR_AND, parse_msglist(argv[c]));
}
struct stat st;
int fd;
size_t len;
for (; c < argc; c++) {
if ((fd = open(argv[c], O_RDONLY)) == -1)
exit(1);
if (fstat(fd, &st) == -1)
exit(1);
len = st.st_size;
char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if (s == MAP_FAILED) {
perror("mmap");
exit(1);
}
s[len+1] = '\0';
close(fd);
expr = chain(expr, EXPR_AND, parse_buf(argv[c], s));
munmap(s, len+1);
}
}
2016-07-22 01:10:17 +00:00
2016-07-28 15:53:51 +00:00
if (isatty(0))
2016-07-23 16:45:23 +00:00
i = blaze822_loop1(":", need_thr ? collect : oneline);
2016-07-28 15:53:51 +00:00
else
i = blaze822_loop(0, 0, need_thr ? collect : oneline);
/* print and free last thread */
if (Tflag && thr)
do_thr();
2016-07-22 01:10:17 +00:00
2019-01-28 13:38:05 +00:00
freeexpr(expr);
if (vflag)
fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept);
2019-01-28 09:53:05 +00:00
for (; files; files = fileq) {
fileq = files->next;
if (files->op == EXPR_REDIR_PIPE)
pclose(files->fp);
else
fclose(files->fp);
free(files);
}
2016-07-22 01:10:17 +00:00
return 0;
}