/*
** Copyright 1998 - 2011 Double Precision, Inc.
** See COPYING for distribution information.
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<fcntl.h>
#include	<time.h>
#include	<errno.h>
#include	"numlib/numlib.h"

#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif
#include	<sys/types.h>
#include	<sys/stat.h>
#if HAVE_SYS_WAIT_H
#include	<sys/wait.h>
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#ifndef WIFEXITED
#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
#if HAVE_DIRENT_H
#include <dirent.h>
#define NAMLEN(dirent) strlen((dirent)->d_name)
#else
#define dirent direct
#define NAMLEN(dirent) (dirent)->d_namlen
#if HAVE_SYS_NDIR_H
#include <sys/ndir.h>
#endif
#if HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#if HAVE_NDIR_H
#include <ndir.h>
#endif
#endif

#include	"liblock/config.h"
#include	"liblock/liblock.h"
#include	"maildir/config.h"
#include	"maildir/maildircreate.h"
#include	"maildir/maildirmisc.h"
#include	"maildir/maildirwatch.h"
#include	"liblock/mail.h"

#include	"imapscanclient.h"
#include	"imaptoken.h"
#include	"imapwrite.h"
#include	"imapd.h"

/*
** RFC 2060: "A good value to use for the unique identifier validity value is a
** 32-bit representation of the creation date/time of the mailbox."
**
** Well, Y2038k is on the horizon, time to push to reset button.
**
*/

#ifndef IMAP_EPOCH
#define IMAP_EPOCH	1000000000
#endif

static int do_imapscan_maildir2(struct imapscaninfo *, const char *,
				int, int, struct uidplus_info *);
void imapscanfail(const char *p);

#if SMAP
extern int smapflag;
#endif

extern int keywords();
extern void set_time(const char *tmpname, time_t timestamp);

static void imapscan_readKeywords(const char *maildir,
				  struct imapscaninfo *scaninfo);

void imapscan_init(struct imapscaninfo *i)
{
	memset(i, 0, sizeof(*i));

	if ((i->keywordList=malloc(sizeof(*i->keywordList))) == NULL)
		write_error_exit(0);

	libmail_kwhInit(i->keywordList);
}

void imapscan_copy(struct imapscaninfo *a,
		   struct imapscaninfo *b)
{
	imapscan_free(a);
	*a=*b;
	imapscan_init(b);
}

struct libmail_kwMessage *imapscan_createKeyword(struct imapscaninfo *a,
					      unsigned long n)
{
	if (n >= a->nmessages)
		return NULL;

	if (a->msgs[n].keywordMsg == NULL)
	{
		struct libmail_kwMessage *m=libmail_kwmCreate();

		if (!m)
			write_error_exit(0);

		m->u.userNum=n;
		a->msgs[n].keywordMsg=m;
	}

	return a->msgs[n].keywordMsg;
}

static int uselocks()
{
	const	char *p;

	if ((p=getenv("IMAP_USELOCKS")) != 0 && *p != '1')
		return 0;

	return 1;
}

int imapmaildirlock(struct imapscaninfo *scaninfo,
		    const char *maildir,
		    int (*func)(void *),
		    void *void_arg)
{
	char *newname;
	int tryAnyway;
	int rc;

	if (!uselocks())
		return (*func)(void_arg);

	if (scaninfo->watcher == NULL &&
	    (scaninfo->watcher=maildirwatch_alloc(maildir)) == NULL)
		imapscanfail("maildirwatch");

	if ((newname=maildir_lock(maildir, scaninfo->watcher, &tryAnyway))
	    == NULL)
	{
		if (!tryAnyway)
			return -1;

		imapscanfail("maildir_lock");
	}

	rc=(*func)(void_arg);

	if (newname)
	{
		unlink(newname);
		free(newname);
		newname=NULL;
	}
	return rc;
}


struct imapscan_info {
	struct imapscaninfo *scaninfo;
	const char *dir;
	int leavenew;
	int ro;
	struct uidplus_info *uidplus;
};


static int imapscan_maildir_cb(void *);

int imapscan_maildir(struct imapscaninfo *scaninfo,
		     const char *dir, int leavenew, int ro,
		     struct uidplus_info *uidplus)
{
	struct imapscan_info ii;

	ii.scaninfo=scaninfo;
	ii.dir=dir;
	ii.leavenew=leavenew;
	ii.ro=ro;
	ii.uidplus=uidplus;

	return imapmaildirlock(scaninfo, dir, imapscan_maildir_cb, &ii);
}

int imapscan_maildir_cb(void *void_arg)
{
	struct imapscan_info *ii=(struct imapscan_info *)void_arg;
	int rc=do_imapscan_maildir2(ii->scaninfo,
				   ii->dir,
				   ii->leavenew,
				   ii->ro,
				   ii->uidplus);

	if (rc)
		rc= -1;
	return rc;
}

/* This structure is a temporary home for the filenames */

struct tempinfo {
	struct tempinfo *next;
	char *filename;
	unsigned long uid;
	int	found;
	int	isrecent;
	} ;

static char *imapscan_namedir(const char *dir, const char *new_or_cur)
{
char	*p=malloc(strlen(dir)+strlen(new_or_cur)+2);

	if (!p)	write_error_exit(0);
	strcat(strcat(strcpy(p, dir), "/"), new_or_cur);
	return (p);
}

static int fnamcmp(const char *a, const char *b)
{
	long ai, bi;
	char ca, cb;

	ai = atol(a);
	bi = atol(b);
	if(ai - bi)
		return ai - bi;

	do
	{
		ca= *a++;
		cb= *b++;

		if (ca == ':') ca=0;
		if (cb == ':') cb=0;
	} while (ca && cb && ca == cb);


	return ( (int)(unsigned char)ca - (int)(unsigned char)cb);
}

static int sort_by_filename(struct tempinfo **a, struct tempinfo **b)
{
	return (fnamcmp( (*a)->filename, (*b)->filename));
}

static int sort_by_filename_status(struct tempinfo **a, struct tempinfo **b)
{
	if ( (*a)->found && (*b)->found )
	{
		if ( (*a)->uid < (*b)->uid )
			return (-1);
		if ( (*a)->uid > (*b)->uid )
			return (1);
		return (0);	/* What the fuck??? */
	}
	if ( (*a)->found )	return (-1);
	if ( (*b)->found )	return (1);

	return (fnamcmp( (*a)->filename, (*b)->filename));
}

/* Binary search on an array of tempinfos which is sorted by filenames */

static int search_by_filename(struct tempinfo **a, unsigned s, unsigned *i,
	const char *filename)
{
unsigned lo=0, hi=s, mid;
int	rc;

	while (lo < hi)
	{
		mid=(hi+lo)/2;
		rc=fnamcmp( a[mid]->filename, filename);
		if (rc < 0)
		{
			lo=mid+1;
			continue;
		}
		if (rc > 0)
		{
			hi=mid;
			continue;
		}
		*i=mid;
		return (0);
	}
	return (-1);
}

void imapscanfail(const char *p)
{
	fprintf(stderr, "ERR: Failed to create cache file: %s (%s)\n", p,
		getenv("AUTHENTICATED"));
#if	HAVE_STRERROR
	fprintf(stderr, "ERR: Error: %s\n", strerror(errno));
#endif

#if	HAVE_FAM
	if (errno == EIO)
	{
		fprintf(stderr,
			"ERR: Check for proper operation and configuration\n"
			"ERR: of the File Access Monitor daemon (famd).\n");
	}
#endif
}

static char *readbuf;
static unsigned readbufsize=0;

char *readline(unsigned i, FILE *fp)
{
int	c;

	for (;;)
	{
		if (i >= 10000)
			--i;	/* DOS check */

		if (i >= readbufsize)
		{
		char	*p= readbuf ? realloc(readbuf, readbufsize+256):
					malloc(readbufsize+256);

			if (!p)	write_error_exit(0);
			readbuf=p;
			readbufsize += 256;
		}

		c=getc(fp);
		if (c == EOF || c == '\n')
		{
			readbuf[i]=0;
			return (c == EOF ? 0:readbuf);
		}
		readbuf[i++]=c;
	}
}

static int do_imapscan_maildir2(struct imapscaninfo *scaninfo,
				const char *dir, int leavenew, int ro,
				struct uidplus_info *uidplus)
{
char	*dbfilepath, *newdbfilepath;
struct	tempinfo *tempinfo_list=0, **tempinfo_array=0, *tempinfoptr;
struct	tempinfo *newtempinfo_list=0;
unsigned	tempinfo_cnt=0, i;
FILE	*fp;
char	*p, *q;
unsigned long uidv, nextuid;
int	version;
struct	stat	stat_buf;
DIR	*dirp;
struct	dirent *de;
unsigned long left_unseen=0;
int	dowritecache=0;

	if (is_sharedsubdir(dir))
		maildir_shared_sync(dir);


	/* Step 0 - purge the tmp directory */

	maildir_purgetmp(dir);

	dbfilepath=malloc(strlen(dir)+sizeof("/" IMAPDB));
	if (!dbfilepath)	write_error_exit(0);
	strcat(strcpy(dbfilepath, dir), "/" IMAPDB);

	/*
	** We may need to rebuild the UID cache file.  Create the new cache
	** file in the tmp subdirectory.
	*/

	{
		char	uniqbuf[80];
		static  unsigned tmpuniqcnt=0;
		struct maildir_tmpcreate_info createInfo;
		int fd;

		maildir_tmpcreate_init(&createInfo);

		createInfo.maildir=dir;
		createInfo.hostname=getenv("HOSTNAME");
		sprintf(uniqbuf, "imapuid_%u", tmpuniqcnt++);
		createInfo.uniq=uniqbuf;
		createInfo.doordie=1;

		if ((fd=maildir_tmpcreate_fd(&createInfo)) < 0)
		{
			write_error_exit(0);
		}
		close(fd);

		newdbfilepath=createInfo.tmpname;
		createInfo.tmpname=NULL;
		maildir_tmpcreate_free(&createInfo);
	}

	/* Step 1 - read the cache file */

	if ((fp=fopen(dbfilepath, "r")) != 0 &&
		(p=readline(0, fp)) != 0 &&
		sscanf(p, "%d %lu %lu", &version, &uidv, &nextuid) == 3 &&
		version == IMAPDBVERSION)
	{
		while ((p=readline(0, fp)) != 0)
		{
		char	*q=strchr(p, ' ');
		unsigned long uid;
		struct	tempinfo	*newtmpl;

			if (!q)	continue;
			*q++=0;
			if (sscanf(p, "%lu", &uid) != 1)	continue;
			if ((newtmpl=(struct tempinfo *)
				malloc(sizeof(struct tempinfo))) == 0
				|| (newtmpl->filename=strdup(q)) == 0)
			{
				unlink(newdbfilepath);
				write_error_exit(0);
			}
			newtmpl->next=tempinfo_list;
			tempinfo_list=newtmpl;
			newtmpl->uid=uid;
			newtmpl->found=0;
			newtmpl->isrecent=0;
			++tempinfo_cnt;
		}
		fclose(fp);
		fp=0;
	}
	else if(!ro)
	{

	/* First time - create the cache file */

		if (fp)	fclose(fp);
		nextuid=1;
		if ((fp=fopen(newdbfilepath, "w")) == 0 ||
			fstat(fileno(fp), &stat_buf) != 0)
		{
			if (fp)	fclose(fp);
			imapscanfail(newdbfilepath);

			/* bk: ignore error */
			unlink(newdbfilepath);
			unlink(dbfilepath);
			fp = 0;
			/*
			free(dbfilepath);
			unlink(newdbfilepath);
			free(newdbfilepath);
			return (-1);
			*/
		}
		uidv=stat_buf.st_mtime - IMAP_EPOCH;
		dowritecache=1;
	}
	else
	{
		nextuid=1;
		uidv=time(0) - IMAP_EPOCH;
	}

	while (uidplus)
	{
		struct	tempinfo	*newtmpl;

		if (uidplus->tmpkeywords)
			if (rename(uidplus->tmpkeywords,
				   uidplus->newkeywords) < 0)
			{
				struct libmail_kwGeneric g;

				/*
				** Maybe courierimapkeywords needs to be
				** created.
				*/

				libmail_kwgInit(&g);
				libmail_kwgReadMaildir(&g, dir);
				libmail_kwgDestroy(&g);

				rename(uidplus->tmpkeywords,
				       uidplus->newkeywords);
			}

		maildir_movetmpnew(uidplus->tmpfilename,
				   uidplus->curfilename);

		if (uidplus->mtime)
			set_time (uidplus->curfilename, uidplus->mtime);

		if ((newtmpl=(struct tempinfo *)
		     malloc(sizeof(struct tempinfo))) == 0
		    || (newtmpl->filename=strdup(strrchr(uidplus->curfilename,
							 '/')+1)) == 0)
		{
			unlink(newdbfilepath);
			write_error_exit(0);
		}

		if ((p=strrchr(newtmpl->filename, MDIRSEP[0])) != 0)
			*p=0;

		newtmpl->next=tempinfo_list;
		tempinfo_list=newtmpl;
		newtmpl->uid=nextuid;
		uidplus->uid=nextuid;
		nextuid++;
		newtmpl->found=0;
		newtmpl->isrecent=0;
		++tempinfo_cnt;

		uidplus=uidplus->next;
		dowritecache=1;
	}

	if (!fp && (fp=fopen(newdbfilepath, "w")) == 0)
	{
		imapscanfail(newdbfilepath);

		/* bk: ignore error */
		unlink(newdbfilepath);
		unlink(dbfilepath);
		/*
		free(dbfilepath);
		unlink(newdbfilepath);
		free(newdbfilepath);
		while (tempinfo_list)
		{
			tempinfoptr=tempinfo_list;
			tempinfo_list=tempinfoptr->next;
			free(tempinfoptr->filename);
			free(tempinfoptr);
		}
		return (-1);
		*/
	}

	/*
	** Convert the link list of cached files to an array, then
	** sort it by filename.
	*/

	if ((tempinfo_array=(struct tempinfo **)malloc(
		(tempinfo_cnt+1)*sizeof(struct tempinfo *))) == 0)
	{
		unlink(newdbfilepath);
		write_error_exit(0);
	}

	for (i=0, tempinfoptr=tempinfo_list; tempinfoptr;
		tempinfoptr=tempinfoptr->next, i++)
		tempinfo_array[i]=tempinfoptr;

	if (tempinfo_cnt)
		qsort(tempinfo_array, tempinfo_cnt,
			sizeof(tempinfo_array[0]),
			( int (*)(const void *, const void *))
				&sort_by_filename);

	/* Step 2 - read maildir/cur.  Search the array.  Mark found files. */

	p=imapscan_namedir(dir, "cur");
	dirp=opendir(p);
	free(p);
	while (dirp && (de=readdir(dirp)) != 0)
	{
	int	rc;
	struct	tempinfo	*newtmpl;

		if (de->d_name[0] == '.')	continue;

		p=my_strdup(de->d_name);

		/* IMAPDB doesn't store the filename flags, so strip them */
		q=strrchr(p, MDIRSEP[0]);
		if (q)	*q=0;
		rc=search_by_filename(tempinfo_array, tempinfo_cnt, &i, p);
		if (q)	*q=MDIRSEP[0];
		if (rc == 0)
		{
			tempinfo_array[i]->found=1;
			free(tempinfo_array[i]->filename);
			tempinfo_array[i]->filename=p;
				/* Keep the full filename */
			continue;
		}

		if ((newtmpl=(struct tempinfo *)
			malloc(sizeof(struct tempinfo))) == 0)
		{
			unlink(newdbfilepath);
			write_error_exit(0);
		}
		newtmpl->filename=p;
		newtmpl->next=newtempinfo_list;
		newtmpl->found=0;
		newtmpl->isrecent=1;
		newtempinfo_list=newtmpl;
		dowritecache=1;
	}
	if (dirp)	closedir(dirp);

	/* Step 3 - purge messages that no longer exist in the maildir */

	free(tempinfo_array);

	for (tempinfo_array= &tempinfo_list; *tempinfo_array; )
	{
		if ( (*tempinfo_array)->found )
		{
			tempinfo_array= & (*tempinfo_array)->next;
			continue;
		}
		tempinfoptr= *tempinfo_array;
		*tempinfo_array=tempinfoptr->next;
		free(tempinfoptr->filename);
		free(tempinfoptr);
		--tempinfo_cnt;
		dowritecache=1;
	}

	/* Step 4 - add messages in cur that are not in the cache file */

	while (newtempinfo_list)
	{
		tempinfoptr=newtempinfo_list;
		newtempinfo_list=tempinfoptr->next;

		tempinfoptr->next=tempinfo_list;
		tempinfo_list=tempinfoptr;
		++tempinfo_cnt;
	}

	/* Step 5 - read maildir/new.  */

	p=imapscan_namedir(dir, "new");

	if (leavenew)
	{
		dirp=opendir(p);
		while (dirp && (de=readdir(dirp)) != 0)
		{
			if (de->d_name[0] == '.')	continue;
			++left_unseen;
		}
		if (dirp)	closedir(dirp);
	}
	else
		/*
		** Some filesystems keel over if we delete files while
		** reading the directory where the files are.
		** Accomodate them by processing 20 files at a time.
		*/
	{
		char *new_buf[20];
		char *cur_buf[20];
		int keepgoing;
		int n;

		do
		{
			n=0;
			keepgoing=0;

			dirp=opendir(p);
			while (dirp && (de=readdir(dirp)) != 0)
			{
				struct	tempinfo	*newtmpl;
				char	*newname, *curname;
				char	*z;

				if (de->d_name[0] == '.')	continue;

				z=de->d_name;

				newname=imapscan_namedir(p, z);
				curname=malloc(strlen(newname)
					       +sizeof(MDIRSEP "2,"));
				if (!curname)
				{
					unlink(newdbfilepath);
					write_error_exit(0);
				}
				strcpy(curname, newname);
				z=strrchr(curname, '/');

				memcpy(z-3, "cur", 3);
				/* Mother of all hacks */
				if (strchr(z, MDIRSEP[0]) == 0)
					strcat(z, MDIRSEP "2,");

				new_buf[n]=newname;
				cur_buf[n]=curname;

				if ((newtmpl=(struct tempinfo *)
				     malloc(sizeof(struct tempinfo))) == 0)
				{
					unlink(newdbfilepath);
					write_error_exit(0);
				}
				newtmpl->filename=my_strdup(z+1);
				newtmpl->next=tempinfo_list;
				tempinfo_list=newtmpl;
				++tempinfo_cnt;
				newtmpl->found=0;
				newtmpl->isrecent=1;
				dowritecache=1;

				if (++n >= sizeof(cur_buf)/
				    sizeof(cur_buf[0]))
				{
					keepgoing=1;
					break;
				}
			}

			if (dirp)	closedir(dirp);

			while (n)
			{
				char *newname, *curname;

				--n;

				newname=new_buf[n];
				curname=cur_buf[n];

				if (rename(newname, curname))
				{
					fprintf(stderr,
						"ERR: rename(%s,%s) failed:"
						" %s\n",
						newname, curname,
						strerror(errno));
					keepgoing=0;
					/* otherwise we could have infinite loop */
				}

				free(newname);
				free(curname);
			}
		} while (keepgoing);
	}
	free(p);

	/*
	** Step 6: sort existing messages by UIDs, new messages will
	** sort after all messages with UIDs, and new messages are
	** sorted by filename, so that they end up roughly in the order
	** they were received.
	*/

	if ((tempinfo_array=(struct tempinfo **)malloc(
		(tempinfo_cnt+1)*sizeof(struct tempinfo *))) == 0)
	{
		unlink(newdbfilepath);
		write_error_exit(0);
	}

	for (i=0, tempinfoptr=tempinfo_list; tempinfoptr;
		tempinfoptr=tempinfoptr->next, i++)
		tempinfo_array[i]=tempinfoptr;

	if (tempinfo_cnt)
		qsort(tempinfo_array, tempinfo_cnt,
			sizeof(tempinfo_array[0]),
			( int (*)(const void *, const void *))
				&sort_by_filename_status);

	/* Assign new UIDs */

	for (i=0; i<tempinfo_cnt; i++)
		if ( !tempinfo_array[i]->found )
		{
			tempinfo_array[i]->uid= nextuid++;
			dowritecache=1;
		}

	/* bk: ignore if failed to open file */
	if (!ro && dowritecache && fp)
	{
		int need_fclose;

	/* Step 7 - write out the new cache file */

		version=IMAPDBVERSION;
		fprintf(fp, "%d %lu %lu\n", version, uidv, nextuid);

		for (i=0; i<tempinfo_cnt; i++)
		{
			q=strrchr(tempinfo_array[i]->filename, MDIRSEP[0]);
			if (q)  *q=0;
			fprintf(fp, "%lu %s\n", tempinfo_array[i]->uid,
				tempinfo_array[i]->filename);
			if (q)	*q=MDIRSEP[0];
		}

		need_fclose=1;
		if (fflush(fp) || ferror(fp) || ((need_fclose=0), fclose(fp)))
		{
			imapscanfail(dir);
			if (need_fclose)
				fclose(fp);
			/* bk: ignore if failed */
			unlink(newdbfilepath);
			unlink(dbfilepath);
			/*
			free(tempinfo_array);
			free(dbfilepath);
			unlink(newdbfilepath);
			free(newdbfilepath);
			while (tempinfo_list)
			{
				tempinfoptr=tempinfo_list;
				tempinfo_list=tempinfoptr->next;
				free(tempinfoptr->filename);
				free(tempinfoptr);
			}
			return (-1);
			*/
		}
		/* bk */
		else

		rename(newdbfilepath, dbfilepath);
	}
	else
	{
		if (fp)
			fclose(fp);
		unlink(newdbfilepath);
	}
	free(dbfilepath);
	free(newdbfilepath);

	/* Step 8 - create the final scaninfo array */

	scaninfo->msgs=0;
	if (tempinfo_cnt && (scaninfo->msgs=(struct imapscanmessageinfo *)
		malloc(tempinfo_cnt * sizeof(*scaninfo->msgs))) == 0)
		write_error_exit(0);
	scaninfo->nmessages=tempinfo_cnt;
	scaninfo->uidv=uidv;
	scaninfo->left_unseen=left_unseen;
	scaninfo->nextuid=nextuid+left_unseen;

	for (i=0; i<tempinfo_cnt; i++)
	{
		scaninfo->msgs[i].uid=tempinfo_array[i]->uid;
		scaninfo->msgs[i].filename=tempinfo_array[i]->filename;
		scaninfo->msgs[i].keywordMsg=NULL;
		scaninfo->msgs[i].copiedflag=0;

#if SMAP
		if (smapflag)
			scaninfo->msgs[i].recentflag=0;
		else
#endif
			scaninfo->msgs[i].recentflag=
				tempinfo_array[i]->isrecent;
		scaninfo->msgs[i].changedflags=0;

		free(tempinfo_array[i]);
	}
	free(tempinfo_array);

	imapscan_readKeywords(dir, scaninfo);


	return (0);
}

static int try_maildir_open(const char *dir, struct imapscanmessageinfo *n)
{
int	fd;
char	*filename=maildir_filename(dir, 0, n->filename);
char	*p;

	if (!filename)
	{
		return (0);
	}

	p=strrchr(filename, '/')+1;

	if (strcmp(p, n->filename))
	{
		n->changedflags=1;
		free(n->filename);
		n->filename=malloc(strlen(p)+1);
		if (!n->filename)	write_error_exit(0);
		strcpy(n->filename, p);
	}

	fd=maildir_semisafeopen(filename, O_RDONLY, 0);
	free(filename);
	return (fd);
}

int imapscan_openfile(const char *dir, struct imapscaninfo *i, unsigned j)
{
struct imapscanmessageinfo *n;

	if (j >= i->nmessages)
	{
		errno=EINVAL;
		return (-1);
	}

	n=i->msgs+j;

	return (try_maildir_open(dir, n));
}

void imapscan_free(struct imapscaninfo *i)
{
	unsigned	n;

	if (i->watcher)
	{
		maildirwatch_free(i->watcher);
		i->watcher=NULL;
	}

	if (i->msgs)
	{
		for (n=0; n<i->nmessages; n++)
		{
			if (i->msgs[n].filename)
				free(i->msgs[n].filename);

			if (i->msgs[n].keywordMsg)
				libmail_kwmDestroy(i->msgs[n].keywordMsg);

		}
		free(i->msgs);
		i->msgs=0;
	}

	if (i->keywordList)
	{
		if (libmail_kwhCheck(i->keywordList) < 0)
			write_error_exit("INTERNAL ERROR: Keyword hashtable "
					 "memory corruption.");

		free(i->keywordList);
		i->keywordList=NULL;
	}
}

/*
** Keyword-related stuff  See README.imapkeywords.html for more information.
*/

extern char *current_mailbox;

int imapscan_updateKeywords(const char *filename,
			    struct libmail_kwMessage *newKeyword)
{
	char *tmpname, *newname;
	int rc;

	if (maildir_kwSave(current_mailbox, filename, newKeyword,
			   &tmpname, &newname, 0))
	{
		perror("maildir_kwSave");
		return -1;
	}

	rc=rename(tmpname, newname);

	if (rc)
	{
		perror(tmpname);
		unlink(tmpname);
	}
	free(tmpname);
	free(newname);
	return rc;
}

static unsigned long hashFilename(const char *fn, struct imapscaninfo *info)
{
	unsigned long hashBucket=0;

	while (*fn && *fn != MDIRSEP[0])
	{
		hashBucket=(hashBucket << 1) ^ (hashBucket & 0x8000 ? 0x1301:0)
			^ (unsigned char)*fn++;
	}
	hashBucket=hashBucket & 0xFFFF;

	return hashBucket % info->nmessages; /* Cannot get here if its zero */
}

struct imapscanReadKeywordInfo {
	struct maildir_kwReadInfo ri;

	struct imapscaninfo *messages;
	int hashedFilenames;
};

static struct libmail_kwMessage **findMessageByFilename(const char *filename,
						     int autocreate,
						     size_t *indexNum,
						     void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;

	size_t l;
	struct imapscanmessageinfo *i;

	struct imapscaninfo *scaninfo=info->messages;

	if (!info->hashedFilenames)
	{
		unsigned long n;

		for (n=0; n<scaninfo->nmessages; n++)
			scaninfo->msgs[n].firstBucket=NULL;

		for (n=0; n<scaninfo->nmessages; n++)
		{
			unsigned long bucket=hashFilename(scaninfo->msgs[n]
							  .filename,
							  scaninfo);

			scaninfo->msgs[n].nextBucket=
				scaninfo->msgs[bucket].firstBucket;

			scaninfo->msgs[bucket].firstBucket=scaninfo->msgs+n;
		}
		info->hashedFilenames=1;
	}

	l=strlen(filename);

	for (i= scaninfo->nmessages ?
		     scaninfo->msgs[hashFilename(filename, scaninfo)]
		     .firstBucket:NULL; i; i=i->nextBucket)
	{
		if (strncmp(i->filename, filename, l))
			continue;

		if (i->filename[l] == 0 ||
		    i->filename[l] == MDIRSEP[0])
			break;
	}

	if (!i)
		return NULL;

	if (indexNum)
		*indexNum= i-scaninfo->msgs;

	if (!i->keywordMsg && autocreate)
		imapscan_createKeyword(info->messages, i-scaninfo->msgs);

	return &i->keywordMsg;
}

static size_t getMessageCount(void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;

	return info->messages->nmessages;
}

static const char *getMessageFilename(size_t n, void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;

	if (n >= info->messages->nmessages)
		return NULL;

	return info->messages->msgs[n].filename;
}

static void updateKeywords(size_t n, struct libmail_kwMessage *kw,
			   void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;

	if (n >= info->messages->nmessages)
		return;

	if (info->messages->msgs[n].keywordMsg)
		libmail_kwmDestroy(info->messages->msgs[n].keywordMsg);

	kw->u.userNum=n;
	info->messages->msgs[n].keywordMsg=kw;
}

static struct libmail_kwHashtable * getKeywordHashtable(void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;

	return info->messages->keywordList;
}

static struct libmail_kwMessage **findMessageByIndex(size_t indexNum,
						  int autocreate,
						  void *voidarg)
{
	struct imapscanReadKeywordInfo *info=
		(struct imapscanReadKeywordInfo *)voidarg;
	struct imapscanmessageinfo *i;

	if (indexNum >= info->messages->nmessages)
		return NULL;

	i= &info->messages->msgs[indexNum];

	if (!i->keywordMsg && autocreate)
		imapscan_createKeyword(info->messages, indexNum);

	return &i->keywordMsg;
}

static void initri(struct imapscanReadKeywordInfo *rki)
{
	memset(rki, 0, sizeof(*rki));

	rki->ri.findMessageByFilename= &findMessageByFilename;
	rki->ri.getMessageCount= &getMessageCount;
	rki->ri.findMessageByIndex= &findMessageByIndex;
	rki->ri.getKeywordHashtable= &getKeywordHashtable;
	rki->ri.getMessageFilename= &getMessageFilename;
	rki->ri.updateKeywords= &updateKeywords;
	rki->ri.voidarg= rki;
}

void imapscan_readKeywords(const char *maildir,
			   struct imapscaninfo *scaninfo)
{
	struct imapscanReadKeywordInfo rki;

	initri(&rki);

	do
	{
		unsigned long i;

		for (i=0; i<scaninfo->nmessages; i++)
			if (scaninfo->msgs[i].keywordMsg)
			{
				libmail_kwmDestroy(scaninfo->msgs[i]
						      .keywordMsg);
				scaninfo->msgs[i].keywordMsg=NULL;
			}

		rki.messages=scaninfo;

		if (maildir_kwRead(maildir, &rki.ri) < 0)
			write_error_exit(0);

	} while (rki.ri.tryagain);
}

int imapscan_restoreKeywordSnapshot(FILE *fp, struct imapscaninfo *scaninfo)
{
	struct imapscanReadKeywordInfo rki;

	initri(&rki);

	rki.messages=scaninfo;
	return maildir_kwImport(fp, &rki.ri);
}

int imapscan_saveKeywordSnapshot(FILE *fp, struct imapscaninfo *scaninfo)
{
	struct imapscanReadKeywordInfo rki;

	initri(&rki);

	rki.messages=scaninfo;
	return maildir_kwExport(fp, &rki.ri);
}
