/* dlist.c - list protocol for dump and sync
 *
 * Copyright (c) 1994-2008 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Carnegie Mellon University
 *      Center for Technology Transfer and Enterprise Creation
 *      4615 Forbes Avenue
 *      Suite 302
 *      Pittsburgh, PA  15213
 *      (412) 268-7393, fax: (412) 268-7395
 *      innovation@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * $Id: sync_support.c,v 1.25 2010/01/06 17:01:41 murch Exp $
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#include <utime.h>

#include "global.h"
#include "assert.h"
#include "mboxlist.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "quota.h"
#include "xmalloc.h"
#include "xstrlcat.h"
#include "xstrlcpy.h"
#include "acl.h"
#include "seen.h"
#include "mboxname.h"
#include "map.h"
#include "imapd.h"
#include "imparse.h"
#include "message.h"
#include "util.h"
#include "retry.h"
#include "cyr_lock.h"
#include "prot.h"

#include "dlist.h"

/* Parse routines */

const char *lastkey = NULL;

static void printfile(struct protstream *out, const struct dlist *dl)
{
    char buf[4096];
    struct stat sbuf;
    FILE *f;
    unsigned long size;

    f = fopen(dl->sval, "r");
    if (!f) {
	syslog(LOG_ERR, "IOERROR: Failed to read file %s", dl->sval);
	prot_printf(out, "NIL");
	return;
    }
    if (fstat(fileno(f), &sbuf) == -1) {
	syslog(LOG_ERR, "IOERROR: Failed to stat file %s", dl->sval);
	prot_printf(out, "NIL");
	fclose(f);
	return;
    }
    size = sbuf.st_size;
    if (size != dl->nval) {
	syslog(LOG_ERR, "IOERROR: Size mismatch %s (%lu != " MODSEQ_FMT ")",
	       dl->sval, size, dl->nval);
	prot_printf(out, "NIL");
	fclose(f);
	return;
    }

    prot_printf(out, "%%{");
    prot_printastring(out, dl->part);
    prot_printf(out, " ");
    prot_printastring(out, message_guid_encode(&dl->gval));
    prot_printf(out, " %lu}\r\n", size);
    while (size) {
	int n = fread(buf, 1, (size > 4096 ? 4096 : size), f);
	if (n <= 0) break;
	prot_write(out, buf, n);
	size -= n;
    }
    fclose(f);

    if (size) fatal("failed to finish reading file!", EC_IOERR);
}

/* XXX - these two functions should be out in append.c or reserve.c
 * or something more general */
const char *dlist_reserve_path(const char *part, struct message_guid *guid)
{
    static char buf[MAX_MAILBOX_PATH];
    snprintf(buf, MAX_MAILBOX_PATH, "%s/sync./%lu/%s", 
		  config_partitiondir(part), (unsigned long)getpid(),
		  message_guid_encode(guid));
    cyrus_mkdir(buf, 0755);
    return buf;
}

static int reservefile(struct protstream *in, const char *part,
		       struct message_guid *guid, unsigned long size,
		       const char **fname)
{
    FILE *file;
    char buf[8192+1];
    int r = 0, n;
    
    /* XXX - write to a temporary file then move in to place! */
    *fname = dlist_reserve_path(part, guid);

    file = fopen(*fname, "w+");
    if (!file) {
	syslog(LOG_ERR, "Failed to upload file %s", message_guid_encode(guid));
	r = IMAP_IOERROR;
	/* Note: we still read the file's data from the wire,
	 * to avoid losing protocol sync */
    }

    /* XXX - calculate sha1 on the fly? */
    while (size) {
	n = prot_read(in, buf, size > 8192 ? 8192 : size);
	if (!n) {
	    syslog(LOG_ERR,
		"IOERROR: reading message: unexpected end of file");
	    r = IMAP_IOERROR;
	    break;
	}
	size -= n;
	if (!r) fwrite(buf, 1, n, file);
    }

    if (r)
	goto error;

    /* Make sure that message flushed to disk just incase mmap has problems */
    fflush(file);
    if (ferror(file)) {
	r = IMAP_IOERROR;
	goto error;
    }

    if (fsync(fileno(file)) < 0) {
	r = IMAP_IOERROR;
	goto error;
    }

    fclose(file);

    return 0;

error:
    if (file) {
	fclose(file);
	unlink(*fname);
	*fname = NULL;
    }
    return r;
}

/* DLIST STUFF */

void dlist_stitch(struct dlist *dl, struct dlist *child)
{
    if (dl->tail)
	dl->tail = dl->tail->next = child;
    else
	dl->head = dl->tail = child;
}

static struct dlist *dlist_child(struct dlist *dl, const char *name)
{
    struct dlist *i = xzmalloc(sizeof(struct dlist));
    i->name = xstrdup(name);
    i->type = DL_NIL;
    if (dl)
	dlist_stitch(dl, i);
    return i;
}

struct dlist *dlist_atom(struct dlist *dl, const char *name, const char *val)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_ATOM;
    i->sval = xstrdup(val);
    return i;
}

struct dlist *dlist_flag(struct dlist *dl, const char *name, const char *val)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_FLAG;
    i->sval = xstrdup(val);
    return i;
}

struct dlist *dlist_num(struct dlist *dl, const char *name, unsigned long val)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_NUM;
    i->nval = (modseq_t)val;
    return i;
}

struct dlist *dlist_date(struct dlist *dl, const char *name, time_t val)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_DATE;
    i->nval = (modseq_t)val;
    return i;
}

struct dlist *dlist_modseq(struct dlist *dl, const char *name, modseq_t val)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_MODSEQ;
    i->nval = val;
    return i;
}

struct dlist *dlist_guid(struct dlist *dl, const char *name,
			   struct message_guid *guid)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_GUID,
    message_guid_copy(&i->gval, guid);
    return i;
}

struct dlist *dlist_file(struct dlist *dl, const char *name,
			   const char *part,
			   struct message_guid *guid,
			   unsigned long size,
			   const char *fname)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_FILE;
    message_guid_copy(&i->gval, guid);
    i->sval = xstrdup(fname);
    i->nval = size;
    i->part = xstrdup(part);
    return i;
}

struct dlist *dlist_buf(struct dlist *dl, const char *name,
		        const char *val, size_t len)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_BUF;
    i->sval = xmalloc(len+1);
    memcpy(i->sval, val, len);
    i->sval[len] = '\0'; /* make it string safe too */
    i->nval = len;
    return i;
}

struct dlist *dlist_kvlist(struct dlist *dl, const char *name)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_KVLIST;
    return i;
}

struct dlist *dlist_list(struct dlist *dl, const char *name)
{
    struct dlist *i = dlist_child(dl, name);
    i->type = DL_ATOMLIST;
    return i;
}

struct dlist *dlist_new(const char *name)
{
    return dlist_kvlist(NULL, name);
}

static void dlist_print_helper(const struct dlist *dl, int printkeys,
			       struct protstream *out, int level)
{
    struct dlist *di;
    int i;

    if (printkeys)
	prot_printf(out, "%s ", dl->name);

    switch (dl->type) {
    case DL_ATOM:
	prot_printastring(out, dl->sval);
	break;
    case DL_FLAG:
	prot_printf(out, "%s", dl->sval);
	break;
    case DL_NUM:
    case DL_DATE: /* for now, we will format it later */
    case DL_MODSEQ:
	prot_printf(out, MODSEQ_FMT, dl->nval);
	break;
    case DL_FILE:
	printfile(out, dl);
	break;
    case DL_BUF:
	prot_printliteral(out, dl->sval, dl->nval);
	break;
    case DL_KVLIST:
	if (level) {
	    prot_printf(out, "\r\n");
	    for (i = 0; i <= level; i++)
		prot_printf(out, " ");
	}
	prot_printf(out, "%%(");
	for (di = dl->head; di; di = di->next) {
	    dlist_print_helper(di, 1, out, level);
	    if (di->next) {
		prot_printf(out, " ");
	    }
	}
	prot_printf(out, ")");
	break;
    case DL_ATOMLIST:
	prot_printf(out, "(");
	for (di = dl->head; di; di = di->next) {
	    dlist_print_helper(di, 0, out, di->type == DL_KVLIST ? level + 1 : level);
	    if (di->next)
		prot_printf(out, " ");
	}
	prot_printf(out, ")");
	break;
    }
}

void dlist_print(const struct dlist *dl, int printkeys, struct protstream *out)
{
    dlist_print_helper(dl, printkeys, out, 0);
}

void dlist_free(struct dlist **dlp)
{
    struct dlist *i, *next;
    if (!*dlp) return;
    i = (*dlp)->head;
    while (i) {
	free(i->name);
	next = i->next;
	switch (i->type) {
	case DL_KVLIST:
	case DL_ATOMLIST:
	    dlist_free(&i);
	    break;
	case DL_FILE:
	    free(i->part);
	    /* drop through */
	default:
	    free(i->sval);
	}
	free(i);
	i = next;
    }
    free(*dlp);
    *dlp = NULL;
}

static char next_nonspace(struct protstream *in, char c)
{
    while (Uisspace(c)) {
	c = prot_getc(in);
    }
    return c;
}

char dlist_parse(struct dlist **dlp, int parsekey, struct protstream *in)
{
    struct dlist *dl = NULL;
    static struct buf kbuf;
    static struct buf vbuf;
    char c;

    /* handle the key if wanted */
    if (parsekey) {
	c = getword(in, &kbuf);
	c = next_nonspace(in, c);
    }
    else {
	buf_setcstr(&kbuf, "");
	c = prot_getc(in);
    }
    
    /* connection dropped? */
    if (c == EOF) goto fail;

    /* check what sort of value we have */
    if (c == '(') {
	dl = dlist_list(NULL, kbuf.s);
	c = next_nonspace(in, ' ');
	while (c != ')') {
	    struct dlist *di = NULL;
	    prot_ungetc(c, in);
	    c = dlist_parse(&di, 0, in);
	    if (di) dlist_stitch(dl, di);
	    c = next_nonspace(in, c);
	    if (c == EOF) goto fail;
	}
	c = prot_getc(in);
    }
    else if (c == '%') {
	/* no whitespace allowed here */
	c = prot_getc(in);
	if (c == '(') {
	    dl = dlist_list(NULL, kbuf.s);
	    c = next_nonspace(in, ' ');
	    while (c != ')') {
		struct dlist *di = NULL;
		prot_ungetc(c, in);
		c = dlist_parse(&di, 1, in);
		if (di) dlist_stitch(dl, di);
		c = next_nonspace(in, c);
		if (c == EOF) goto fail;
	    }
	}
	else if (c == '{') {
	    struct message_guid tmp_guid;
	    static struct buf pbuf, gbuf;
	    unsigned size = 0;
	    const char *fname;
	    c = getastring(in, NULL, &pbuf);
	    if (c != ' ') goto fail;
	    c = getastring(in, NULL, &gbuf);
	    if (c != ' ') goto fail;
	    c = getuint32(in, &size);
	    if (c != '}') goto fail;
	    c = prot_getc(in);
	    if (c == '\r') c = prot_getc(in);
	    if (c != '\n') goto fail;
	    if (!message_guid_decode(&tmp_guid, gbuf.s)) goto fail;
	    if (reservefile(in, pbuf.s, &tmp_guid, size, &fname)) goto fail;
	    dl = dlist_file(NULL, kbuf.s, pbuf.s, &tmp_guid, size, fname);
	    /* file literal */
	}
	else {
	    /* unknown percent type */
	    goto fail;
	}
	c = prot_getc(in);
    }
    else if (c == '{') {
	prot_ungetc(c, in);
	/* could be binary in a literal */
	c = getbastring(in, NULL, &vbuf);
	dl = dlist_buf(NULL, kbuf.s, vbuf.s, vbuf.len);
    }
    else {
	prot_ungetc(c, in);
	c = getastring(in, NULL, &vbuf);
	dl = dlist_atom(NULL, kbuf.s, vbuf.s);
	if (imparse_isnumber(vbuf.s))
	    dl->nval = atomodseq_t(vbuf.s);
    }

    /* success */
    *dlp = dl;
    return c;

fail:
    dlist_free(&dl);
    return EOF;
}

static struct dlist *dlist_getchild(struct dlist *dl, const char *name)
{
    struct dlist *i;

    if (!dl) return NULL;

    for (i = dl->head; i; i = i->next) {
	if (!strcmp(name, i->name))
	    return i;
    }
    lastkey = name;
    return NULL;
}

/* XXX - type coercion logic here */

int dlist_getatom(struct dlist *dl, const char *name, const char **val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    *val = i->sval;
    return 1;
}

int dlist_getbuf(struct dlist *dl, const char *name, const char **val, size_t *len)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    *val = i->sval;
    *len = (size_t)i->nval;
    return 1;
}

int dlist_getnum(struct dlist *dl, const char *name, uint32_t *val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    *val = (uint32_t)i->nval;
    return 1;
}

int dlist_getdate(struct dlist *dl, const char *name, time_t *val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    /* XXX: string parse when the date format changes */
    *val = (time_t)i->nval;
    return 1;
}

int dlist_getmodseq(struct dlist *dl, const char *name, modseq_t *val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    *val = (modseq_t)i->nval;
    return 1;
}

int dlist_getguid(struct dlist *dl, const char *name, struct message_guid **val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    /* XXX - maybe malloc like strings? would save some in the general case */
    if (!message_guid_decode(&i->gval, i->sval)) return 0;
    *val = &i->gval;
    return 1;
}

int dlist_getfile(struct dlist *dl, const char *name,
		   const char **part,
		   struct message_guid **guid,
		   unsigned long *size,
		   const char **fname)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    if (!message_guid_decode(&i->gval, i->sval)) return 0;
    *guid = &i->gval;
    *size = i->nval;
    *fname = i->sval;
    *part = i->part;
    return 1;
}

int dlist_getlist(struct dlist *dl, const char *name, struct dlist **val)
{
    struct dlist *i = dlist_getchild(dl, name);
    if (!i) return 0;
    *val = i;
    return 1;
}

const char *dlist_lastkey()
{
    return lastkey;
}
