/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 *
 *	Port of NDBM version to GDBM by Matti Aarnio 1989-1997
 *
 *	Currently uses GDBM version 1.7.3 !
 *	Supports also GDBM version 1.8.0!
 */
/*
 *	Lots of modifications (new guts, more or less..) by
 *	Matti Aarnio <mea@nic.funet.fi>  (copyright) 1992-2004
 */

/* LINTLIBRARY */

#include "mailer.h"
#ifdef	HAVE_GDBM
#include <gdbm.h>
#ifndef	HAVE_GDBM_FDESC
	/* Because GDBM 1.7.3 does not have gdbm_fdesc() or some such,
	   we must do "Horrible Kludges"(TM) to find the fileno..	*/
	/* THIS MUST TRACK THE GDBM INTERNAL DATA STRUCTURE ! Auurgh...! */
struct __gdbmfoo {
	char *name;     /* Space alloc for things that are there */
	int read_write;
#ifdef GDBM_FASTMODE	/* This appeared somewhen in between 0.9 and 1.7.3 */
        int fast_write;
#endif
	void (*fatal_err) __((void));
	int desc;       /* this is what we do need! */
	/* the rest is irrelevant for us.. */
};
#define gdbm_fdesc(db) (((struct __gdbmfoo *) (db))->desc)
#endif

#include <fcntl.h>
#include <sys/file.h>
#include <time.h>

#include "libsh.h"
#include "search.h"
#include "io.h"
#include "libz.h"
#include "libc.h"
#include "libsh.h"

/* extern int gdbm_errno; */
extern int deferit;
extern int nobody;	/* UID of NOBODY */

typedef struct ZGdbmPrivate {
  GDBM_FILE db;
  int    roflag;
  time_t stmtime;
  long   stino;
  int    stnlink;
} ZGdbmPrivate;


static ZGdbmPrivate * open_gdbm __((search_info *, int, const char *));

static ZGdbmPrivate *
open_gdbm(sip, roflag, comment)
	search_info *sip;
	int roflag;
	const char *comment;
{
	int i, flag;
	ZGdbmPrivate *prv;

	if (sip->file == NULL)
		return NULL;

	prv = /*(ZGdbmPrivate*)*/ *sip->dbprivate;

	if (prv && roflag == O_RDWR && roflag != prv->roflag)
	  close_gdbm(sip,"open_gdbm");

	if (roflag == O_RDWR)	flag = GDBM_WRITER;
	else			flag = GDBM_READER;

	prv = /*(ZGdbmPrivate*)*/ *sip->dbprivate;


	if (!prv || !prv->db) {

		GDBM_FILE db = NULL;

		if (!prv) {
		  prv = malloc(sizeof(*prv));
		  if (!prv) return NULL;
		  memset(prv, 0, sizeof(*prv));
		  *sip->dbprivate = (void*)prv;
		}

		for (i = 0; i < 3; ++i) {
		  /* sigh.. name parameter is 'char *', where our
		     variable is 'const char *' ... */
		  db = gdbm_open((char*)sip->file, 0, flag, 0, NULL);
		  if (db != NULL)
		    break;
		  sleep(1);
		}

		if (db) {
		  prv->db     = db;
		  prv->roflag = roflag;
		}

		if (db == NULL) {
		  ++deferit;
		  v_set(DEFER, DEFER_IO_ERROR);
		  fprintf(stderr, "%s: cannot open %s! (%s)\n",
			  comment, sip->file, gdbm_strerror(gdbm_errno));
		  return NULL;
		}
	}
	return prv;
}

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

void
close_gdbm(sip,comment)
	search_info *sip;
	const char *comment;
{
	ZGdbmPrivate *prv;

	prv = /*(ZGdbmPrivate*)*/ *sip->dbprivate;

	if (!sip->file || !prv)
		return;

	if (prv->db)
	  gdbm_close(prv->db);

	prv->db = NULL;
}


/*
 * Search an GDBM format database for a key pair.
 */

conscell *
search_gdbm(sip)
	search_info *sip;
{
	GDBM_FILE db;
	datum val, key;
	conscell *tmp;
	int retry;
	ZGdbmPrivate *prv;

	retry = 0;

reopen:
	prv = open_gdbm(sip, O_RDONLY, "search_gdbm");

	if (!prv || !prv->db)
	  return NULL;
	db = prv->db;

	memset(&key, 0, sizeof(key));
	memset(&val, 0, sizeof(val));

	key.dptr  = (void*)sip->key;
	key.dsize = strlen(sip->key) + 1;

	val = gdbm_fetch(db, key);

	if (val.dptr == NULL) {
	  if (!retry && gdbm_errno != GDBM_NO_ERROR &&
	      gdbm_errno != GDBM_EMPTY_DATABASE) {
	    close_gdbm(sip,"search_gdbm");
	    ++retry;
	    goto reopen;
	  }
	  return NULL;
	}
	tmp = newstring(dupnstr(val.dptr, val.dsize), val.dsize);
	free(val.dptr);
	return tmp;
}

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

int
add_gdbm(sip, value)
	search_info *sip;
	const char *value;
{
	GDBM_FILE db;
	datum val, key;
	ZGdbmPrivate *prv;

	prv = open_gdbm(sip, O_RDWR, "add_gdbm");
	if (!prv || !prv->db)
		return EOF;
	db = prv->db;

	memset(&key, 0, sizeof(key));
	memset(&val, 0, sizeof(val));

	key.dptr  = (void*)sip->key;
	key.dsize = strlen(sip->key) + 1;

	val.dptr  = (void*)value;
	val.dsize = strlen(value) + 1;

	if (gdbm_store(db, key, val, GDBM_REPLACE) < 0) {
		++deferit;
		v_set(DEFER, DEFER_IO_ERROR);
		fprintf(stderr, "add_gdbm: cannot store (\"%s\",\"%s\")\n",
				sip->key, value);
		return EOF;
	}
	return 0;
}

/*
 * Remove the indicated key from the database.
 */

int
remove_gdbm(sip)
	search_info *sip;
{
	GDBM_FILE db;
	datum key;
	ZGdbmPrivate *prv;

	prv = open_gdbm(sip, O_RDWR, "remove_gdbm");
	if (!prv || !prv->db)
		return EOF;
	db = prv->db;

	memset(&key, 0, sizeof(key));

	key.dptr  = (void*)sip->key;
	key.dsize = strlen(sip->key) + 1;

	if (gdbm_delete(db, key) < 0) {
		++deferit;
		v_set(DEFER, DEFER_IO_ERROR);
		fprintf(stderr,
			"remove_gdbm: cannot remove \"%s\" from \"%s\"\n",
			sip->key, sip->file);
		return EOF;
	}
	return 0;
}

/*
 * Print the database.
 */

void
print_gdbm(sip, outfp)
	search_info *sip;
	FILE *outfp;
{
	GDBM_FILE db;
	datum key, nextkey, val;
	ZGdbmPrivate *prv;

	prv = open_gdbm(sip, O_RDONLY, "print_gdbm");
	if (!prv || !prv->db)
		return;
	db = prv->db;

	memset(&key, 0, sizeof(key));
	memset(&nextkey, 0, sizeof(nextkey));
	memset(&val, 0, sizeof(val));

	key = gdbm_firstkey(db);
	while (key.dptr) {
		val = gdbm_fetch(db, key);
		if (gdbm_errno)
			break;
		if (val.dptr == NULL || *val.dptr == '\0')
			fprintf(outfp, "%s\n", key.dptr);
		else
			fprintf(outfp, "%s\t%s\n", key.dptr, val.dptr);
		if (val.dptr)
			free(val.dptr);
		nextkey = gdbm_nextkey(db, key);
		free(key.dptr);
		key = nextkey;
	}
	fflush(outfp);
}

/*
 * Count the database.
 */

void
count_gdbm(sip, outfp)
	search_info *sip;
	FILE *outfp;
{
	ZGdbmPrivate *prv;
	int count = 0;

	prv = open_gdbm(sip, O_RDONLY, "count_gdbm");
	if (prv && prv->db) {

	  GDBM_FILE db = prv->db;
	  datum key, nextkey;
	  
	  key = gdbm_firstkey(db);
	  while (key.dptr) {
	    ++count;
	    nextkey = gdbm_nextkey(db, key);
	    if (key.dptr) free(key.dptr);
	    if (gdbm_errno) break;
	    key = nextkey;
	  }
	}
	fprintf(outfp,"%d\n",count);
	fflush(outfp);
}

/*
 * Print the uid of the owner of the database file.
 * (For the security purposes.)  Unlike  DBM and NDBM,
 * for GDBM there is only one file, and this makes
 * more sense..
 */

void
owner_gdbm(sip, outfp)
	search_info *sip;
	FILE *outfp;
{
	GDBM_FILE db;
	int	fno;
	struct stat stbuf;
	ZGdbmPrivate *prv;

	prv = open_gdbm(sip, O_RDONLY, "owner_gdbm");
	if (!prv || !prv->db) {
	  fprintf(outfp, "%d\n", nobody);
	  return;
	}
	db = prv->db;

	fno = gdbm_fdesc(db);
	if (fstat(fno, &stbuf) < 0) {
		fprintf(stderr, "owner_gdbm: cannot fstat(\"%s\")!\n",
				sip->file);
		fprintf(outfp, "%d\n", nobody);
		return;
	}
	fprintf(outfp, "%d\n", stbuf.st_uid);
	fflush(outfp);
}

int
modp_gdbm(sip)
	search_info *sip;
{
	GDBM_FILE db;
	int	fno;
	struct stat stbuf;
	int rval;
	ZGdbmPrivate *prv;

	prv = open_gdbm(sip, O_RDONLY, "modp_gdbm");
	if (!prv || !prv->db)
		return 0;
	db = prv->db;

	fno = gdbm_fdesc(db);
	if (fstat(fno, &stbuf) < 0) {
		fprintf(stderr, "modp_gdbm: cannot fstat(\"%s\")!\n",
				sip->file);
		return 0;
	}
	if (stbuf.st_nlink == 0)
		return 1;	/* Unlinked underneath of us! */

	rval = ( (stbuf.st_mtime != prv->stmtime) ||
		 (stbuf.st_nlink != prv->stnlink) ||
		 (stbuf.st_ino   != prv->stino)   );

	prv->stmtime = stbuf.st_mtime;
	prv->stnlink = stbuf.st_nlink;
	prv->stino   = stbuf.st_ino;

	return rval;
}
#endif	/* HAVE_GDBM */
