/*
 *	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 <fcntl.h>
#include <sys/file.h>
#include "search.h"
#include "io.h"
#ifdef	USE_MMAP
#include <sys/mman.h>
#endif

extern int cistrcmp();
extern int deferit;
extern void v_set();

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

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

struct conscell *
search_seq(sip)
	struct search_info *sip;
{
	FILE *fp;
	register unsigned char *cp, *s;
	struct conscell *tmp;
	struct spblk *spl;
	int retry, symid;
	char buf[BUFSIZ];
	struct file_map *fm;
#ifdef	USE_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_seq: cannot open %s!\n",
					sip->file);
			return NULL;
		}
		fm = (struct file_map *)emalloc(sizeof(struct file_map));
		fm->fp = fp;
#ifdef	USE_MMAP
		fstat(fileno(fp),&fst);
		fm->size = fst.st_size;
		fm->mtime = fst.st_mtime;
		fm->lines = 0;
		fm->offsets = NULL;
		fm->membuf = (void*)mmap(NULL, fst.st_size,
					 PROT_READ, MAP_SHARED, fileno(fp), 0);
#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, O_RDONLY, spt_files);
		else
			spl->data = (u_char *)fm;
	}
	fp = fm->fp;
#ifdef	USE_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;
	while ((s = (u_char *)mfgets(buf, sizeof buf, fm)) != NULL)
#else
	fseek(fp, 0L, 0);
	while ((s = (u_char *)fgets(buf, sizeof buf, fp)) != NULL)
#endif
	{
		buf[sizeof buf - 1] = '\0';
		for (cp = (u_char *)buf; *cp; ++cp)
			if (!isascii(*cp) || isspace(*cp))
				break;
		if (*cp == '\0')
			*(cp+1) = '\0';
		else
			*cp = '\0';
		if (cistrcmp((char *)(sip->key), buf) == 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 (!retry && ferror(fp)) {
		fclose(fp);
#ifdef	USE_MMAP
		if (fm->membuf != NULL)
			munmap(fm->membuf,fm->size);
#endif
		if (fm->offsets != NULL)
		  free(fm->offsets);
		free(fm);
		++retry;
		goto reopen;
	}

	return NULL;
}

/*
 * Flush buffered information from this database, close any file descriptors.
 */

void
close_seq(sip)
	struct search_info *sip;
{
	struct file_map *fm;
	struct spblk *spl;
	int symid;

	if (sip->file == NULL)
		return;
	symid = symbol((u_char *)sip->file);
	spl = sp_lookup(symid, spt_modcheck);
	if (spl != NULL)
		sp_delete(spl, spt_modcheck);
	spl = sp_lookup(symid, spt_files);
	if (spl == NULL || (fm = (struct file_map *)spl->data) == NULL)
		return;	/* nothing to flush */
	fclose(fm->fp);
	sp_install(symid, (u_char *)NULL, 0, spt_files);
#ifdef	USE_MMAP
	if (fm->membuf != NULL)
		munmap(fm->membuf,fm->size);
#endif
	if (fm->offsets != NULL)
	  free(fm->offsets);
	free(fm);
}

FILE *
open_seq(sip, mode)
	struct search_info *sip;
	char *mode;
{
	struct file_map *fm;
	struct spblk *spl;
	int symid, imode;
#ifdef	USE_MMAP
	struct stat fst;
#endif

	if (sip->file == NULL)
		return NULL;
	symid = symbol((u_char *)sip->file);
	spl = sp_lookup(symid, spt_files);
	if (spl != NULL
	    && (*mode == 'w' || *mode == 'a'
		|| (*mode == 'r' && *(mode+1) == '+'))
	    && spl->mark != O_RDWR) {
		close_seq(sip);
		imode = O_RDWR;
	} else
		imode = O_RDONLY;
	if (spl == NULL || (fm = (struct file_map *)spl->data) == NULL) {
		FILE *fp = fopen(sip->file, mode);
		if (fp == NULL) {
			++deferit;
			v_set(DEFER, DEFER_IO_ERROR);
			fprintf(stderr,
				"add_seq: cannot open %s mode \"%s\"!\n",
				sip->file, mode);
			return NULL;
		}
		fm = (struct file_map *)emalloc(sizeof(struct file_map));
		fm->fp = fp;
#ifdef	USE_MMAP
		fstat(fileno(fm->fp),&fst);
		fm->size = fst.st_size;
		fm->mtime = fst.st_mtime;
		fm->lines = 0;
		fm->offsets = NULL;
		fm->membuf = (void*)mmap(NULL, fst.st_size,
					 PROT_READ, MAP_SHARED, fileno(fm->fp), 0);
#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, imode, spt_files);
		else
			spl->data = (u_char *)fm;
	}
	return fm->fp;
}

/*
 * Add the indicated key/value pair to the list.
 */

int
add_seq(sip, value)
	struct search_info *sip;
	char *value;
{
	FILE *fp;

	if ((fp = open_seq(sip, "r+")) == NULL)
		return EOF;
	fseek(fp, 0L, 2);
	if (value == NULL || *value == '\0')
		fprintf(fp, "%s\n", sip->key);
	else
		fprintf(fp, "%s\t%s\n", sip->key, value);
	return fflush(fp);
}

/*
 * Print the database.  This is equivalent to listing the file and so it
 * can be used by the other text-reading database types, e.g. search_bin().
 */

void
print_seq(sip, outfp)
	struct search_info *sip;
	FILE *outfp;
{
	FILE *fp;
	int n;
	char buf[BUFSIZ];

	if ((fp = open_seq(sip, "r")) == NULL)
		return;

	fseek(fp, 0L, 0);
	while ((n = fread(buf, 1, sizeof buf, fp)) > 0)
		fwrite(buf, 1, n, outfp);
	fflush(outfp);
}

void
owner_seq(sip, outfp)
	struct search_info *sip;
	FILE *outfp;
{
	FILE *fp;
	struct stat stbuf;

	if (sip->file == NULL || (fp = open_seq(sip, "r")) == NULL)
		return;

	if (fstat(fileno(fp), &stbuf) < 0) {
		fprintf(stderr, "owner_seq: cannot fstat(\"%s\")!\n",
			sip->file);
		return;
	}
	fprintf(outfp, "%d\n", stbuf.st_uid);
	fflush(outfp);
}

int
modp_seq(sip)
	struct search_info *sip;
{
	FILE *fp;
	struct stat stbuf;
	struct spblk *spl;
	int symid, rval;

	if (sip->file == NULL || (fp = open_seq(sip, "r")) == NULL)
		return 0;

	if (fstat(fileno(fp), &stbuf) < 0) {
		fprintf(stderr, "modp_seq: cannot fstat(\"%s\")!\n",
				sip->file);
		return 0;
	}

	symid = symbol((u_char *)sip->file);
	spl = sp_lookup(symid, spt_modcheck);
	if (spl != NULL) {
		rval = (stbuf.st_mtime != (time_t)spl->data
			|| stbuf.st_nlink != (int)spl->mark);
	} else
		rval = 0;
	sp_install(symid, (u_char *)stbuf.st_mtime,
		   stbuf.st_nlink, spt_modcheck);
	return rval;
}

#ifdef USE_MMAP
#ifndef NO_SEQREMAP
void
seq_remap(fm,newsize)
struct file_map *fm;
long newsize;
{
	if (fm->membuf != NULL)
		munmap(fm->membuf, fm->size);
	fm->membuf = (void*)mmap(NULL, newsize,
				 PROT_READ, MAP_SHARED, fileno(fm->fp), 0);
	fm->size = newsize;
	if (fm->offsets != NULL)
	  free(fm->offsets);
	fm->offsets = NULL;
	fm->lines = 0;
}
#endif

char *
mfgets(buf,bufsize,fm)
char *buf;
int bufsize;
struct file_map *fm;
{
	char *eof = fm->membuf + fm->size;
	char *s   = fm->membuf + fm->pos;
	char *b   = buf;
	int i;

	if (s >= eof) return NULL; /* EOF.. */

	for (i = 0; *s != '\n' && s < eof && i < bufsize; ++i)
		*b++ = *s++;
	if (*s == '\n' && s < eof && i < bufsize)
		*b++ = *s, ++i;
	if (b < eof)
		*b = 0; /* We may have space for this, if not.. */
	fm->pos += i;
	return buf;
}
#endif
