#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <syslog.h>
#include "assert.h"
#include "map.h"
#include "bsearch.h"
#include "lock.h"
#include "retry.h"
#include "mailbox.h"
#include "imap_err.h"
#include "xmalloc.h"
#include "seen.h"
#define FNAME_SEEN "/cyrus.seen"
struct seen {
int fd;
const char *base;
unsigned long size;
ino_t ino;
long offset;
long length;
struct mailbox *mailbox;
char *user;
};
int seen_open(struct mailbox *mailbox, const char *user,
int flags __attribute__((unused)),
struct seen **seendbptr)
{
struct seen *seendb;
char fnamebuf[MAX_MAILBOX_PATH+1];
struct stat sbuf;
seendb = (struct seen *)xmalloc(sizeof(struct seen));
seendb->mailbox = mailbox;
seendb->user = xstrdup(user);
strlcpy(fnamebuf, mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
seendb->fd = open(fnamebuf, O_RDWR, 0666);
if (seendb->fd == -1) {
syslog(LOG_ERR, "IOERROR: opening %s: %m", fnamebuf);
free(seendb->user);
free((char *)seendb);
return IMAP_IOERROR;
}
if (fstat(seendb->fd, &sbuf) == -1) {
syslog(LOG_ERR, "IOERROR: fstat on %s: %m", fnamebuf);
close(seendb->fd);
free(seendb->user);
free((char *)seendb);
return IMAP_IOERROR;
}
seendb->ino = sbuf.st_ino;
seendb->base = 0;
seendb->size = 0;
map_refresh(seendb->fd, 1, &seendb->base, &seendb->size, sbuf.st_size,
fnamebuf, 0);
seendb->offset = 0;
seendb->mailbox->seen_lock_count = 0;
*seendbptr = seendb;
return 0;
}
int seen_lockread(struct seen *seendb, time_t *lastreadptr, unsigned int *lastuidptr,
time_t *lastchangeptr, char **seenuidsptr)
{
int r;
char fnamebuf[MAX_MAILBOX_PATH+1];
struct stat sbuf;
const char *lockfailaction;
const char *buf = 0, *p;
unsigned long left;
unsigned long length, namelen;
strlcpy(fnamebuf, seendb->mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
if (!seendb->mailbox->seen_lock_count) {
r = lock_reopen(seendb->fd, fnamebuf, &sbuf, &lockfailaction);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, fnamebuf);
return IMAP_IOERROR;
}
seendb->mailbox->seen_lock_count = 1;
if (seendb->ino != sbuf.st_ino) {
map_free(&seendb->base, &seendb->size);
}
map_refresh(seendb->fd, 1, &seendb->base, &seendb->size,
sbuf.st_size, fnamebuf, 0);
}
seendb->offset = bsearch_mem(seendb->user, 1, seendb->base, seendb->size,
seendb->offset, &length);
seendb->length = length;
*lastreadptr = 0;
*lastuidptr = 0;
*lastchangeptr = 0;
if (!length) {
*seenuidsptr = xstrdup("");
return 0;
}
namelen = strlen(seendb->user)+1;
buf = seendb->base + seendb->offset + namelen;
left = length - namelen;
while (left && isdigit((int) *buf)) {
*lastreadptr = *lastreadptr * 10 + *buf++ - '0';
left--;
}
if (left && *buf != '\n') {
left--;
buf++;
}
while (left && isdigit((int) *buf)) {
*lastuidptr = *lastuidptr * 10 + *buf++ - '0';
left--;
}
if (left && *buf != '\n') {
left--;
buf++;
}
p = buf;
while (left && !isspace((int) *p)) {
p++;
left--;
}
if (left > 1 && p[0] == ' ' && isdigit((int) p[1])) {
while (buf < p) {
*lastchangeptr = *lastchangeptr * 10 + *buf++ - '0';
}
buf++;
p++;
left--;
while (left && !isspace((int) *p)) {
p++;
left--;
}
}
*seenuidsptr = xmalloc(p - buf + 1);
strncpy(*seenuidsptr, buf, p - buf);
(*seenuidsptr)[p - buf] = '\0';
return 0;
}
#define PADSIZE 30
int seen_write(struct seen *seendb, time_t lastread, unsigned int lastuid,
time_t lastchange, char *seenuids)
{
char timeuidbuf[80];
int length;
int writefd = -1;
int replace;
char fnamebuf[MAX_MAILBOX_PATH+1];
char newfnamebuf[MAX_MAILBOX_PATH+1];
int n;
struct iovec iov[10];
int num_iov;
struct stat sbuf;
static const char padbuf[] = {
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
};
#define PRUNESIZE sizeof(padbuf)
assert(seendb->mailbox->seen_lock_count != 0);
snprintf(timeuidbuf, sizeof(timeuidbuf), "\t%u %u %u ", (unsigned int) lastread, lastuid, (unsigned int) lastchange);
length = strlen(seendb->user)+strlen(timeuidbuf)+strlen(seenuids)+1;
replace = (length >= seendb->length || length+PRUNESIZE < seendb->length);
num_iov = 0;
if (replace) {
strlcpy(fnamebuf, seendb->mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
strlcpy(newfnamebuf, fnamebuf, sizeof(newfnamebuf));
strlcat(newfnamebuf, ".NEW", sizeof(newfnamebuf));
writefd = open(newfnamebuf, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (writefd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", newfnamebuf);
return IMAP_IOERROR;
}
iov[num_iov].iov_base = (char *)seendb->base;
iov[num_iov++].iov_len = seendb->offset;
}
iov[num_iov].iov_base = seendb->user;
iov[num_iov++].iov_len = strlen(seendb->user);
iov[num_iov].iov_base = timeuidbuf;
iov[num_iov++].iov_len = strlen(timeuidbuf);
iov[num_iov].iov_base = seenuids;
iov[num_iov++].iov_len = strlen(seenuids);
iov[num_iov].iov_base = (char *)padbuf;
if (replace) {
iov[num_iov++].iov_len = PADSIZE;
length += PADSIZE;
}
else {
iov[num_iov++].iov_len = seendb->length - length;
}
iov[num_iov].iov_base = "\n";
iov[num_iov++].iov_len = 1;
if (replace) {
iov[num_iov].iov_base = (char *)seendb->base
+ seendb->offset + seendb->length;
iov[num_iov++].iov_len =
seendb->size - (seendb->offset + seendb->length);
}
if (replace) {
n = retry_writev(writefd, iov, num_iov);
if (n == -1 || fsync(writefd) ||
lock_blocking(writefd) == -1 ||
fstat(writefd, &sbuf) == -1 ||
rename(newfnamebuf, fnamebuf) == -1) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", newfnamebuf);
close(writefd);
unlink(newfnamebuf);
return IMAP_IOERROR;
}
close(seendb->fd);
seendb->fd = writefd;
seendb->ino = sbuf.st_ino;
seendb->length = length;
map_free(&seendb->base, &seendb->size);
map_refresh(seendb->fd, 1, &seendb->base, &seendb->size,
sbuf.st_size, fnamebuf, 0);
}
else {
lseek(seendb->fd, seendb->offset, 0);
n = retry_writev(seendb->fd, iov, num_iov);
if (n == -1 || fsync(seendb->fd)) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", fnamebuf);
return IMAP_IOERROR;
}
}
return 0;
}
int seen_unlock(struct seen *seendb)
{
int r;
if (seendb->mailbox->seen_lock_count == 0) return 0;
seendb->mailbox->seen_lock_count = 0;
r = lock_unlock(seendb->fd);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: unlocking seen db for %s: %m",
seendb->mailbox->name);
return IMAP_IOERROR;
}
return 0;
}
int seen_close(struct seen *seendb)
{
map_free(&seendb->base, &seendb->size);
close(seendb->fd);
free(seendb->user);
free((char *)seendb);
return 0;
}
int seen_create(struct mailbox *mailbox)
{
char fnamebuf[MAX_MAILBOX_PATH+1];
int fd;
strlcpy(fnamebuf, mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
fd = open(fnamebuf, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (fd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", fnamebuf);
return IMAP_IOERROR;
}
close(fd);
return 0;
}
int seen_delete(struct mailbox *mailbox)
{
char fnamebuf[MAX_MAILBOX_PATH+1];
int fd;
int r;
const char *lockfailaction;
strlcpy(fnamebuf, mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
fd = open(fnamebuf, O_RDWR, 0666);
if (fd == -1) {
syslog(LOG_ERR, "IOERROR: opening %s: %m", fnamebuf);
return IMAP_IOERROR;
}
r = lock_reopen(fd, fnamebuf, 0, &lockfailaction);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, fnamebuf);
close(fd);
return IMAP_IOERROR;
}
unlink(fnamebuf);
close(fd);
return 0;
}
int seen_copy(struct mailbox *oldmailbox,struct mailbox *newmailbox)
{
char oldfname[MAX_MAILBOX_PATH+1];
char newfname[MAX_MAILBOX_PATH+1];
strlcpy(oldfname, oldmailbox->path, sizeof(oldfname));
strlcat(oldfname, FNAME_SEEN, sizeof(oldfname));
strlcpy(newfname, newmailbox->path, sizeof(newfname));
strlcat(newfname, FNAME_SEEN, sizeof(newfname));
return mailbox_copyfile(oldfname, newfname, 0);
}
#define NEWIOV_GROW 3
struct iovec *newiov;
char *freenew;
int newiov_num;
int newiov_alloc = 0;
int newiov_dirty;
void
newiov_insert(line, len, freeit)
const char *line;
unsigned len;
int freeit;
{
int low=0;
int high=newiov_num-1;
int mid, cmp, i;
if (newiov_num == newiov_alloc) {
newiov_alloc += NEWIOV_GROW;
newiov = (struct iovec *)xrealloc((char *)newiov,
newiov_alloc * sizeof (struct iovec));
freenew = xrealloc(freenew, newiov_alloc);
}
if (newiov_num == 0 ||
bsearch_compare(line, newiov[newiov_num-1].iov_base) > 0) {
newiov[newiov_num].iov_base = (char *)line;
newiov[newiov_num].iov_len = len;
freenew[newiov_num] = freeit;
newiov_num++;
if (freeit) newiov_dirty = 1;
return;
}
newiov_dirty = 1;
while (low <= high) {
mid = (high - low)/2 + low;
cmp = bsearch_compare(line, newiov[mid].iov_base);
if (cmp == 0) return;
if (cmp < 0) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
for (i = newiov_num-1; i > high; i--) {
newiov[i+1].iov_base = newiov[i].iov_base;
newiov[i+1].iov_len = newiov[i].iov_len;
freenew[i+1] = freenew[i];
}
newiov_num++;
newiov[low].iov_base = (char *)line;
newiov[low].iov_len = len;
freenew[low] = freeit;
}
#define FIXING() \
if (!dst) { \
fixedline = xmalloc(endline - line + 2 + PADSIZE); \
strncpy(fixedline, line, p - line); \
dst = fixedline + (p - line); \
}
int seen_reconstruct(struct mailbox *mailbox,
time_t report_time,
time_t prune_time,
int (*report_proc)(),
void *report_rock)
{
char fnamebuf[MAX_MAILBOX_PATH+1];
char newfnamebuf[MAX_MAILBOX_PATH+1];
int fd;
struct stat sbuf;
const char *lockfailaction;
const char *base = 0;
unsigned long size = 0;
const char *line, *endline;
const char *tab, *p, *space;
time_t lastread;
unsigned lastuidread;
time_t lastchange;
int r, i, n;
unsigned lastuid, thisuid;
unsigned uidtoobig = mailbox->last_uid;
time_t now, nowplus1day;
int lastsep;
char *fixedline, *dst;
int writefd;
time(&now);
nowplus1day = now + 24*60*60;
strlcpy(fnamebuf, mailbox->path, sizeof(fnamebuf));
strlcat(fnamebuf, FNAME_SEEN, sizeof(fnamebuf));
fd = open(fnamebuf, O_RDWR, 0666);
if (fd == -1) {
return seen_create(mailbox);
}
r = lock_reopen(fd, fnamebuf, &sbuf, &lockfailaction);
if (r == -1) {
syslog(LOG_ERR, "IOERROR: %s %s: %m", lockfailaction, fnamebuf);
return IMAP_IOERROR;
}
map_refresh(fd, 1, &base, &size, sbuf.st_size, fnamebuf, 0);
newiov_dirty = 0;
newiov_num = 0;
endline = base;
while ((endline = memchr(line=endline, '\n', size - (endline - base)))) {
endline++;
p = tab = memchr(line, '\t', endline - line);
if (!tab ) {
newiov_dirty = 1;
continue;
}
p++;
lastread = 0;
while (p < endline && isdigit((int) *p)) {
lastread = lastread * 10 + *p++ - '0';
}
if (p >= endline || *p++ != ' ') {
newiov_dirty = 1;
continue;
}
if (lastread > nowplus1day) lastread = now;
if (report_proc && lastread > report_time) {
(*report_proc)(report_rock, line);
}
if (lastread < prune_time) {
newiov_dirty = 1;
continue;
}
lastuidread = 0;
while (p < endline && isdigit((int) *p)) {
lastuidread = lastuidread * 10 + *p++ - '0';
}
if (p >= endline || *p++ != ' ' || lastuidread > uidtoobig) {
newiov_dirty = 1;
continue;
}
lastchange = 0;
fixedline = dst = 0;
space = memchr(p, ' ', endline - p);
if (space && space+1 < endline &&
space[0] == ' ' && isdigit((int) space[1])) {
while (p < space && isdigit((int) *p)) {
lastchange = lastchange * 10 + *p++ - '0';
}
if (p != space) {
newiov_dirty = 1;
continue;
}
if (lastchange > nowplus1day) {
lastchange = now;
}
p++;
space = memchr(p, ' ', endline - p);
if (!space) space = endline - 1;
}
else {
FIXING();
*dst++ = '0';
*dst++ = ' ';
}
lastuid = 0;
lastsep = ',';
while (p < space) {
thisuid = 0;
while (p < space && isdigit((int) *p)) {
if (dst) *dst++ = *p;
thisuid = thisuid * 10 + *p++ - '0';
}
if (thisuid <= lastuid || thisuid > uidtoobig) {
FIXING();
while (isdigit((int) dst[-1])) dst--;
if (dst[-1] == ':') dst[-1] = ',';
}
else if (lastsep == ':' && *p == ':') {
FIXING();
*dst++ = lastsep = ',';
}
else if (*p == ':' || *p == ',') {
lastsep = *p;
if (dst) *dst++ = lastsep;
}
else break;
p++;
}
if (p[-1] == ':' || p[-1] == ',') {
FIXING();
}
if (dst && (dst[-1] == ':' || dst[-1] == ',')) {
dst[-1] = ' ';
}
while (p < endline) {
if (*p != ' ') {
FIXING();
}
if (dst) *dst++ = ' ';
p++;
}
if (dst) {
*dst++ = '\n';
newiov_insert(fixedline, dst - fixedline, 1);
}
else {
newiov_insert(line, endline - line, 0);
}
}
r = 0;
if (newiov_dirty) {
strlcpy(newfnamebuf, fnamebuf, sizeof(newfnamebuf));
strlcat(newfnamebuf, ".NEW", sizeof(newfnamebuf));
writefd = open(newfnamebuf, O_RDWR|O_TRUNC|O_CREAT, 0666);
if (writefd == -1) {
syslog(LOG_ERR, "IOERROR: creating %s: %m", newfnamebuf);
r = IMAP_IOERROR;
goto cleanup;
}
for (i = 0; i < newiov_num - 1; i++) {
if ((char *)newiov[i].iov_base + newiov[i].iov_len == newiov[i+1].iov_base &&
!freenew[i] && !freenew[i]) {
newiov[i+1].iov_base = newiov[i].iov_base;
newiov[i+1].iov_len += newiov[i].iov_len;
newiov[i].iov_len = 0;
}
}
n = retry_writev(writefd, newiov, newiov_num);
if (n == -1 || fsync(writefd) ||
fstat(writefd, &sbuf) == -1 ||
rename(newfnamebuf, fnamebuf) == -1) {
syslog(LOG_ERR, "IOERROR: writing %s: %m", newfnamebuf);
unlink(newfnamebuf);
r = IMAP_IOERROR;
}
close(writefd);
cleanup:
for (i = 0; i < newiov_num; i++) {
if (freenew[i]) free(newiov[i].iov_base);
}
}
map_free(&base, &size);
close(fd);
return r;
}
int seen_done(void)
{
return 0;
}
int seen_merge(const char *tmpfile, const char *tgtfile)
{
return -1;
}