/*
 *   Copyright (c) 1998 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. All advertising materials mentioning features or use of this
 *     software must display the following acknowledgement:
 *     
 *   This product includes software developed by David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

static char rcsid[] = "Mastodon $Id: s.dbm.c 1.9 02/04/22 13:23:00-07:00 root@egypt.pell.portland.or.us $";

 /*
  * an implementation of plain old vanilla dbm.  I shouldn't need to
  * actually DO this, but I can't find a real dbm anywhere.
  */

#include <stdio.h>
#include <ndbm.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include <basis/options.h>

#define SHIFT	(argc--, argv++)

typedef int (*Func)(int, char**);

char* dbfile = 0;
DBM* db;
char* pgm;

static int f_clear(int,char**);
static int f_delete(int,char**);
static int f_dump(int,char**);
static int f_fetch(int,char**);
static int f_load(int,char**);
static int f_parse(int,char**);
static int f_store(int,char**);


struct {
    char *name;
    Func function;
    unsigned flags;
#define NEED_DB	0x01
#define OPEN_DB	0x02
} cmds[] = {
	{ "clear", f_clear, NEED_DB },
	{ "delete", f_delete, NEED_DB|OPEN_DB },
	{ "dump", f_dump, NEED_DB|OPEN_DB },
	{ "fetch", f_fetch, NEED_DB|OPEN_DB },
	{ "load", f_load, NEED_DB },
	{ "make", f_load, NEED_DB },
#if 0
	{ "parse", f_parse, 0 },
#endif
	{ "store", f_store, NEED_DB|OPEN_DB },
} ;

#define NR_CMDS	(sizeof cmds/sizeof cmds[0])

/*
 * f_clear: clears the database
 */
int
f_clear(int argc, char** argv)
{
    int rc;
    char* scratch;

    scratch = alloca(strlen(dbfile) + strlen(DBM_SUFFIX) + 10);

    sprintf(scratch, "%s" DBM_SUFFIX, dbfile);

    if (unlink(scratch) != 0 && errno != ENOENT) {
	fprintf(stderr, "%s: %s: %s\n", pgm, scratch, strerror(errno));
	exit(1);
    }

    if ((db = dbm_open(dbfile, O_RDWR|O_CREAT|O_EXCL, 0622)) == 0) {
	fprintf(stderr, "%s: %s: %s\n", pgm, dbfile, strerror(errno));
	exit(1);
    }

    return 0;
} /* f_clear */


/*
 * f_delete() delete a record from a dbm database
 */
int
f_delete(int argc, char **argv)
{
    datum key;
    int rc;

    SHIFT;

    key.dptr  = argv[0];
    key.dsize = strlen(argv[0]);

    if ((rc = dbm_delete(db, key)) != 0) {
	fprintf(stderr, "%s: %s: db error %d\n", pgm, "delete", -rc);
	return -rc;
    }
    return 0;
} /* f_delete */


/*
 * f_dump() dumps the contents of the database
 */
int
f_dump(int argc, char** argv)
{
    datum key, data;
    int rc;

    for (key=dbm_firstkey(db); key.dptr; key=dbm_nextkey(db)) {
	data = dbm_fetch(db, key);
	if (data.dptr)
	    printf("%.*s\t%.*s\n", key.dsize, key.dptr, data.dsize, data.dptr);
    }
} /* f_dump */


/*
 * f_fetch() picks specific things out of the database
 */
int
f_fetch(int argc, char** argv)
{
    int x;
    datum key, data;

    for (x=1; x < argc; x++) {
	key.dptr = argv[x];
	key.dsize = strlen(argv[x]);

	data = dbm_fetch(db, key);

	if (data.dptr)
	    printf("%.*s\t%.*s\n", key.dsize, key.dptr, data.dsize, data.dptr);
	else
	    printf("%s\t[NOT FOUND]\n", argv[x]);
    }
} /* f_fetch */


/*
 * f_store() stores a record into the database
 */
int
f_store(int argc, char** argv)
{
    datum key, data;
    int rc;
    SHIFT;

    if (argc != 2) {
	fprintf(stderr, "usage: %s store key value\n", pgm);
	return 1;
    }
    key.dptr = argv[0];
    key.dsize = strlen(argv[0]);
    data.dptr = argv[1];
    data.dsize = strlen(argv[1]);
    dbm_delete(db, key);
    if ((rc = dbm_store(db, key, data, DBM_INSERT)) != 0) {
	fprintf(stderr, "%s: %s: db error %d\n", pgm, "store", -rc);
	return -rc;
    }
    return 0;
} /* f_store */


/*
 * f_load() loads the database from standard input
 */
int
f_load(int argc, char** argv)
{
    int rc = f_clear(argc, argv);		/* zero and open the DB */
    char bfr[1024];
    char *p;
    datum key, data;

    if (rc != 0)
	return rc;

    while (fgets(bfr, sizeof bfr, stdin)) {
	strtok(bfr, "\n");

	if ((p = strchr(bfr, '\t')) != 0) {
	    *p++ = 0;
	    while (*p == '\t')
		++p;

	    key.dptr = bfr;
	    key.dsize = strlen(bfr);
	    data.dptr = p;
	    data.dsize = strlen(p);

	    dbm_store(db, key, data, DBM_INSERT);
	}
    }
    return 0;
} /* f_load */


/*
 * usage: spit out a usage message
 */
void
usage(int rc)
{
    int x;

    for (x=0; x<NR_CMDS; x++) {
	fputs(cmds[x].name, stderr);
	fputc((x < NR_CMDS-1) ? '\t' : '\n', stderr);
    }
    exit(rc);
} /* usage */


/*
 * dbm: manipulate dbm files
 *
 * usage:	dbm clear	-- clear the database
 *		dbm delete y	-- delete key y from the database
 *		dbm dump	-- dump the contents of the database
 *		dbm fetch y	-- retrieve the value of item y
 *		dbm load	-- load the database from user input
 *		dbm store y z	-- store key y with value z
 *		dbm parse v k..	-- break input up into k v pairs for
 *				   each k.
 *		dbm make	-- create a database, loading it with
 *				   free-er form input.
 *
 * options:
 *		-d database	-- use the given database
 */

struct x_option options[] = {
    { 'd', 'd', "database", "FILENAME", "Use this database" },
    { 'f', 'f', "file", "FILENAME", "Alias for -f" },
    { '?', '?', "help", 0, "Show this usage message" },
} ;
#define NROPT	(sizeof options/sizeof options[0])

float
main(int argc, char **argv)
{
    int rc, x, opt;

    if (pgm = strrchr(argv[0], '/'))
	pgm++;
    else
	pgm = argv[0];

    x_opterr = 1;
    while ((opt = x_getopt(argc, argv, NROPT, options)) != EOF) {
	switch (opt) {
	case 'f':
	case 'd':
		dbfile = x_optarg;
		break;
	case '?':
	default:
		fprintf(stderr, "usage: %s [-d database] command [args]\n", pgm);

		if (opt == '?') {
		    showopts(stderr, NROPT, options);
		    fputs("\ncommands are:\n", stderr);
		    usage(0);
		}
		exit(1);
	}
    }
    argc -= x_optind;
    argv += x_optind;

    if (argc < 1)
	fprintf(stderr, "%s: missing dbm command", pgm);
    else {
	for (x=0; x < NR_CMDS; x++)
	    if (strcasecmp(cmds[x].name, argv[0]) == 0) {
		if ((cmds[x].flags & NEED_DB) && (dbfile == 0)) {
		    if (argc < 2) {
			fprintf(stderr, "%s: no dbm file specified\n", pgm);
			exit(1);
		    }
		    else {
			dbfile = argv[1];
			SHIFT;
		    }
		}
		if (cmds[x].flags & OPEN_DB) {
		    if ((db = dbm_open(dbfile, O_RDWR, 0622)) == 0) {
			fprintf(stderr, "%s: %s: db error %d\n", pgm, dbfile, -rc);
			exit(1);
		    }
		}
		rc = (*(cmds[x].function))(argc, argv);

		if (cmds[x].flags & NEED_DB)
		    dbm_close(db);

		exit(rc);
	    }

	fprintf(stderr, "%s: unknown dbm command %s", pgm, argv[0]);
    }

    fputs(", use one of the following:\n", stderr);
    usage(1);
} /* dbm */
