/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/* LINTLIBRARY */

#include "mailer.h"
#include <ctype.h>
#include <sys/file.h>
#include "search.h"
#ifdef	HAVE_MMAP
#include <sys/mman.h>
#endif
#include <errno.h>

extern FILE *fpmap();
extern int deferit, cistrcmp();
extern void v_set();

#ifdef HAVE_MMAP
extern void seq_remap __((struct file_map *, long newsize));
extern char *mfgets __((char *, int, struct file_map *));
#endif

/*
 * Binary search of a file for keyword-value pairs.
 */

struct conscell *
search_bin(sip)
	struct search_info *sip;
{
	FILE *fp;
	register unsigned char *cp, *s;
	register int c;
	long int top, bot, mid;
	int i, retry;
	spkey_t symid;
	struct conscell *tmp;
	struct spblk *spl;
	char buf[BUFSIZ];
	struct file_map *fm;
#ifdef	HAVE_MMAP
	struct stat fst;
#endif

	if (sip->file == NULL)
		return NULL;
	retry = 0;
	symid = symbol((u_char *)sip->file);
	spl = sp_lookup(symid, spt_files);
	if (spl == NULL || (fm = (struct file_map *)spl->data) == NULL) {
reopen:
		fp = fopen(sip->file, "r");
		if (fp == NULL) {
			++deferit;
			v_set(DEFER, DEFER_IO_ERROR);
			fprintf(stderr, "search_bin: cannot open %s!\n",
				sip->file);
			return NULL;
		}
		fm = (struct file_map *)emalloc(sizeof(struct file_map));
		fm->fp = fp;
#ifdef	HAVE_MMAP
		fstat(FILENO(fp),&fst);
		fm->size = fst.st_size;
		fm->mtime = fst.st_mtime;
		fm->lines = 0;
		fm->offsets = NULL;
		if (fm->size)
		  fm->membuf = (void*)mmap(NULL, fst.st_size,
					   PROT_READ, MAP_SHARED,
					   FILENO(fp), 0);
		else
		  fm->membuf = NULL;
		if ((long)fm->membuf == -1) {
			fprintf(stderr,
				"search_bin: cannot mmap() (r/o) %d bytes of file \"%s\" into memory, errno=%d",
				fst.st_size, sip->file, errno);
			free(fm);
			++deferit;
			v_set(DEFER, DEFER_IO_ERROR);
			return NULL;
		}
#else
		fm->size   = 0;
		fm->mtime  = 0;
		fm->lines = 0;
		fm->offsets = NULL;
		fm->membuf = NULL;
#endif
		if (spl == NULL)
			sp_install(symid, (u_char *)fm, DESC_FILEP, spt_files);
		else
			spl->data = (u_char *)fm;
	}
	fp = fm->fp;
#ifdef	HAVE_MMAP
	/* This  fstat()  for possible seq_remap() trigger causes a bit
	   more syscalls, than is really necessary.   Therefore it is
	   likely best to have "-m" option on the relation definitions
	   and live with that -- relation information does not pass to
	   the low-level drivers, thus these drivers don't know about
	   possible upper-level "-m"..					*/
#define NO_SEQREMAP
#ifndef NO_SEQREMAP
	if (fstat(FILENO(fp),&fst) < 0) abort(); /* Will succeed, or crash.. */
	if (fst.st_mtime != fm->mtime ||
	    fst.st_size  != fm->size) {
		/* Changes at the original file, remap.. */
		seq_remap(fm,fst.st_size);
	}
#endif
	fm->pos = 0;	/* We have it mmap()ed incore, collect line
			   start offsets into an array for latter use
			   on searches..				*/
	if (fm->size > 0 && fm->lines == 0 && fm->offsets == NULL) {
	  int linecnt = 0;
	  char *buf = fm->membuf;
	  char *eof = fm->membuf + fm->size;
	  while (buf < eof)
	    if (*++buf == '\n') ++linecnt;
	  buf = fm->membuf;
	  fm->offsets = (int*)emalloc(sizeof(int)*(linecnt+1));
	  linecnt = 0;
	  while (buf < eof) {
	    fm->offsets[linecnt] = (buf - (char*)fm->membuf);
	    while (buf < eof && *buf != '\n') ++buf;
	    ++buf; /* Skip over the newline */
	    ++linecnt;
	  }
	  fm->lines = linecnt;
	}

	top = fm->lines-1;
	bot = 0;
	while (bot <= top) {
		mid = (top + bot) / 2;
		fm->pos = fm->offsets[mid];
		if (mfgets(buf, sizeof (buf), fm) == NULL) return NULL;
		
		for (cp = (unsigned char *)buf; *cp; ++cp)
			if (!isascii(*cp) || isspace(*cp))
				break;
		if (*cp == '\0')
			*(cp+1) = '\0';
		else
			*cp = '\0';
		i = cistrcmp((char *)(sip->key), buf);
		if (i == 0) {
			for (++cp; *cp; ++cp)
				if (isascii(*cp) && !isspace(*cp))
					break;
			for (s = cp; *s != '\0'; ++s)
				if (!isascii(*s) || isspace(*s))
					break;
			return newstring((u_char *)strnsave((char *)cp, s-cp));
		}
		if (i < 0)
			top = mid - 1;
		else
			bot = mid + 1;
	}
#endif

#ifndef	HAVE_MMAP
	bot = 0;
	fseek(fp, 0L, 2);	/* EOF */
	top = ftell(fp);
	for(;;) {
		mid = (top + bot)/2;
		fseek(fp, mid, 0);
		do {
			c = getc(fp);
			mid++;
		} while (!ferror(fp) && c != EOF && c != '\n');
		if (fgets(buf, sizeof buf, fp) == NULL) {
			if (!retry && ferror(fp)) {
				fclose(fp);
				++retry;
				goto reopen;
			}
			break;
		}
		for (cp = (unsigned char *)buf; *cp; ++cp)
			if (!isascii(*cp) || isspace(*cp))
				break;
		if (*cp == '\0')
			*(cp+1) = '\0';
		else
			*cp = '\0';
		i = cistrcmp((char *)(sip->key), buf);
		if (i < 0) {
			if(top <= mid)
				break;
			top = mid;
		} else if (i == 0) {
			for (++cp; *cp; ++cp)
				if (isascii(*cp) && !isspace(*cp))
					break;
			for (s = cp; *s != '\0'; ++s)
				if (!isascii(*s) || isspace(*s))
					break;
			return newstring((u_char *)strnsave((char *)cp, s - cp));
		} else
			bot = mid;
	}
	fseek(fp, bot, 0);
	while (ftell(fp) < top) {
		if (fgets(buf, sizeof buf, fp) == NULL)
			return NULL;
		for (cp = (unsigned char *)buf; *cp; ++cp)
			if (!isascii(*cp) || isspace(*cp))
				break;
		if (*cp == '\0')
			*(cp+1) = '\0';
		else
			*cp = '\0';
		i = cistrcmp((char *)(sip->key), buf);
		if (i < 0)
			return NULL;
		else if (i == 0) {
			for (++cp; *cp; ++cp)
				if (isascii(*cp) && !isspace(*cp))
					break;
			for (s = cp; *s != '\0'; ++s)
				if (!isascii(*s) || isspace(*s))
					break;
			return newstring((u_char *)strnsave((char *)cp, s - cp));
		}
	}
#endif
	return NULL;
}

