/* Copyright 1993 - Matti Aarnio, Turku University, Turku, Finland
   This will be free software, but only when it is finished.

   The way the Zmailer uses DBM entries is by using strings with
   their terminating NULL as keys, and as data..  Thus the length
   is strlen(string)+1, not strlen(string) !
*/

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#ifdef HAVE_NDBM_H
#define datum Ndatum
#include <ndbm.h>
#undef datum
#endif
#ifdef HAVE_GDBM_H
#define datum Gdatum
#include <gdbm.h>
#undef datum
#endif
#ifdef HAVE_DB_H
#include <db.h>
#endif

#define PROG "makendbm"

int aliasinput = 0;

#include <errno.h>
extern int errno;	/* Not all systems declare it in <errno.h> ... */

/* extern char *strchr(); */

extern void create_dbase __((FILE *, void *, int));

extern char *skip821address __((char *));

void
usage(prog,err,errno)
char *prog, *err;
int errno;
{
  fprintf(stderr,"Usage: %s [-a] dbtype database.name [infilename|-]\n",prog);
  fprintf(stderr,"  where supported dbtypes are:");
#ifdef HAVE_NDBM_H
  fprintf(stderr," ndbm");
#endif
#ifdef HAVE_GDBM_H
  fprintf(stderr," gdbm");
#endif
#ifdef HAVE_DB_H
  fprintf(stderr," btree");
#endif
  fprintf(stderr,"\n");
  fprintf(stderr,"  If no infilename is defined, database.name is assumed.\n");
#ifdef HAVE_NDBM_H
  fprintf(stderr,"  (NDBM appends  .pag, and .dir  into actual db file names..)\n");
#endif
#ifdef HAVE_GDBM_H
  fprintf(stderr,"  (GDBM appends .pag, to the actual db file name..)\n");
#endif
#ifdef HAVE_DB_H
  fprintf(stderr,"  (BTREE appends .db, to the actual db file name..)\n");
#endif
  fprintf(stderr,"\n");
  fprintf(stderr,"  The '-a' option is for parsing input that comes in\n");
  fprintf(stderr,"  'aliases' format:  'key: data,in,single,long,line'\n");
  fprintf(stderr," Error now: %s",err);
  fprintf(stderr,", errno=%d (%s)",errno,strerror(errno));
  fprintf(stderr,"\n");
  exit (1);
}


char inputline[64*1024]; /* 64 kB for SINGLE line is rather much :-) */

void
create_dbase(infile,dbf,typ)
     FILE *infile;
     void *dbf;
     int typ;
{
	char *s, *t;
#ifdef HAVE_NDBM_H
	DBM *ndbmfile = dbf;
#endif
#ifdef HAVE_GDBM_H
	GDBM_FILE gdbmfile = dbf;
#endif
#ifdef HAVE_DB_H
	DB *dbfile = dbf;
#endif
	int rc;


	while (!feof(infile) && !ferror(infile)) {
	  *inputline = 0;
	  fgets(inputline,sizeof(inputline)-1,infile);
	  if (*inputline == 0) break; /* Last! */
	  s = (char *)strchr(inputline,'\n');
	  if (s) *s = 0; /* Zap \n from the end -- if it exists.. */
	  if (*inputline == '#') continue; /* Comment! */

	  /* Scan first white-space separated token, point its start with t! */
	  t = inputline;
	  while (*t == '\t' || *t == ' ') ++t;

	  if (*t == 0) continue; /* Blank line! */
	  if (*t == '#') continue; /* Comment.. */

	  if (aliasinput) {
	    s = skip821address(t);
	    /* We are now at the white-space -- possibly the last
	       char of the line prefix is a double-colon (:) */
	    if (s > t) {
	      if (s[-1] == ':')
		s[-1] = 0;
	      else if (s[0] != 0)
		*s++ = 0; /* Zero-terminate the key, scan for input */
	    }
	  } else {
	    s = t;
	    /* Scan over the non-LWSP text */
	    while (*s && *s != '\t' && *s != ' ') ++s;
	    /* Stopped without line-end NIL ?  Trunc, and advance! */
	    if (*s) *s++ = 0;
	  }
	  /* Scan forward, if we have some LWSP here, and then more data ? */
	  while (*s && (*s == '\t' || *s == ' ')) ++s;

	  rc = -2;
#ifdef HAVE_NDBM_H
	  if (typ == 1) {
	    Ndatum Ndat, Nkey;
	    Nkey.dptr  = t;
	    Nkey.dsize = strlen(t)+1;
	    Ndat.dptr  = s;
	    Ndat.dsize = strlen(s)+1;
	    rc = dbm_store(ndbmfile, Nkey, Ndat, DBM_INSERT);
	  }
#endif
#ifdef HAVE_GDBM_H
	  if (typ == 2) {
	    Gdatum Gdat, Gkey;
	    Gkey.dptr  = t;
	    Gkey.dsize = strlen(t)+1;
	    Gdat.dptr  = s;
	    Gdat.dsize = strlen(s)+1;
	    rc = gdbm_store(gdbmfile, Gkey, Gdat, GDBM_INSERT);
	  }
#endif
#ifdef HAVE_DB_H
	  if (typ == 3) {
	    DBT Bkey, Bdat;
	    Bkey.data = t;
	    Bkey.size = strlen(t)+1;
	    Bdat.data = s;
	    Bdat.size = strlen(s)+1;
	    rc = (dbfile->put)(dbfile,&Bkey,&Bdat,R_NOOVERWRITE);
	  }
#endif
	  if (rc > 0) {
	    fprintf(stderr,"Key: `%s' is duplicate!\n",t);
	  } else if (rc < 0) {
	    break; /* Some error -- any, just quit */
	  }
	}
}


int
main(argc,argv)
int argc;
char *argv[];
{
	char *dbasename = NULL;
	FILE *infile = NULL;
	int typ = 0;
#ifdef HAVE_NDBM_H
	DBM *ndbmfile = NULL;
#endif
#ifdef HAVE_GDBM_H
	GDBM_FILE gdbmfile = NULL;
#endif
#ifdef HAVE_DB_H
	DB *dbfile = NULL;
#endif
	char *dbtype = NULL;
	void *dbf = NULL;
	char *argv0 = argv[0];

	aliasinput = 0;

	if (argc > 3) {
	  if (strcmp(argv[1],"-a") == 0) {
	    aliasinput = 1;
	    ++argv;
	    --argc;
	  }
	}

	/* Usage: */
	/*  makendbm dbtype database.name [infilename|-] */
	/* argv[] 0    1          2           3		 */

	if (argc < 3) usage(argv0,"too few arguments",0);
	if (argc > 4) usage(argv0,"too many arguments",0);
	dbasename = argv[2];

	if (argc == 4) {
	  if (strcmp(argv[3],"-")==0)
	    infile = stdin;
	  else
	    infile = (FILE*)fopen(argv[3],"r");
	} else
	  infile = (FILE*)fopen(argv[2],"r");
	dbtype = argv[1];

	if (infile == NULL) usage(argv0,"bad infile",errno);

	typ = 0;
#ifdef HAVE_NDBM_H
	if (strcasecmp(dbtype,"ndbm")==0)
	  typ = 1;
	else
#endif
#ifdef HAVE_GDBM_H
	  if (strcasecmp(dbtype,"gdbm")==0)
	    typ = 2;
	  else
#endif
#ifdef HAVE_DB_H
	    if (strcasecmp(dbtype,"btree")==0)
	      typ = 3;
#endif
	if (typ == 0)
	  usage(argv0,"unknown dbtype",0);

#ifdef HAVE_NDBM_H
	if (typ == 1) {
	  ndbmfile = dbm_open(dbasename, O_RDWR|O_CREAT|O_TRUNC, 0644);
	  dbf = ndbmfile;
	}
#endif
#ifdef HAVE_GDBM_H
	if (typ == 2) {
	  /* Play loose .. don't do syncs while writing */
	  dbasename = strcpy(malloc(strlen(dbasename)+8),dbasename);
	  strcat(dbasename,".pag"); /* ALWAYS append this */
	  gdbmfile = gdbm_open(dbasename, 0, GDBM_NEWDB|GDBM_FAST, 0644, NULL);
	  dbf = gdbmfile;
	}
#endif
#ifdef HAVE_DB_H
	if (typ == 3) {
	  dbasename = strcpy(malloc(strlen(dbasename)+8),dbasename);
	  strcat(dbasename,".db"); /* ALWAYS append this */
	  dbfile = dbopen(dbasename, O_RDWR|O_CREAT|O_TRUNC, 0644,
			  DB_BTREE, NULL);
	  dbf = dbfile;
	}
#endif
	if (dbf == NULL)
	  usage(argv0,"Can't open dbase file",errno);

	create_dbase(infile,dbf,typ);

#ifdef HAVE_NDBM_H
	if (typ == 1)
	  dbm_close(ndbmfile);	
#endif
#ifdef HAVE_GDBM_H
	if (typ == 2)
	  gdbm_close(gdbmfile);
#endif
#ifdef HAVE_DB_H
	if (typ == 3) {
	  (dbfile->sync)(dbfile,0);
	  (dbfile->close)(dbfile);
	}
#endif

	return 0;
}
