/* Copyright 2000-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* This file came from the SDBM package (written by oz@nexus.yorku.ca).
 * That package was under public domain. This file has been ported to
 * APR, updated to ANSI C and other, newer idioms, and added to the Apache
 * codebase under the above copyright and license.
 */

/*
 * testdbm: Simple APR dbm tester.
 * Automatic test case: ./testdbm auto foo
 *  - Attempts to store and fetch values from the DBM.
 *
 * Run the program for more help.
 */

#include "apr.h"
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_errno.h"
#include "apr_getopt.h"
#include "apr_time.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"

#if APR_HAVE_STDIO_H
#include <stdio.h>
#endif
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>     /* for atexit(), malloc() */
#include <string.h>

#include "apr_dbm.h"

static const char *progname;
static int rflag;

#define DERROR      0
#define DLOOK       1

#define DDELETE     3
#define DCAT        4
#define DBUILD      5
#define DPRESS      6
#define DCREAT      7
#define DNAME       8
#define DTRUNC      9
#define DAUTO      10

#define LINEMAX     8192

typedef struct {
    const char *sname;
    int scode;
    int flags;
} cmd;

static const cmd cmds[] = {

    { "fetch",   DLOOK,   APR_DBM_READONLY },
    { "get",     DLOOK,   APR_DBM_READONLY },
    { "look",    DLOOK,   APR_DBM_READONLY },
    { "add",     DBUILD,  APR_DBM_READWRITE },
    { "insert",  DBUILD,  APR_DBM_READWRITE },
    { "store",   DBUILD,  APR_DBM_READWRITE },
    { "delete",  DDELETE, APR_DBM_READWRITE },
    { "remove",  DDELETE, APR_DBM_READWRITE },
    { "dump",    DCAT,    APR_DBM_READONLY },
    { "list",    DCAT,    APR_DBM_READONLY },
    { "cat",     DCAT,    APR_DBM_READONLY },
    { "build",   DBUILD,  APR_DBM_RWCREATE },    /** this one creates the DB */
    { "creat",   DCREAT,  APR_DBM_RWCREATE },
    { "trunc",   DTRUNC,  APR_DBM_RWTRUNC },
    { "new",     DCREAT,  APR_DBM_RWCREATE },
    { "names",   DNAME,   APR_DBM_READONLY },
#if 0
    {"squash",   DPRESS,  APR_DBM_READWRITE, },
    {"compact",  DPRESS,  APR_DBM_READWRITE, },
    {"compress", DPRESS,  APR_DBM_READWRITE, },
#endif
    { "auto",    DAUTO,   APR_DBM_RWCREATE },
};

#define CMD_SIZE (sizeof(cmds)/sizeof(cmd))

static void doit(const cmd *act, const char*type, const char *file, apr_pool_t *pool);
static const cmd *parse_command(const char *str);
static void prdatum(FILE *stream, apr_datum_t d);
static void oops(apr_dbm_t *dbm, apr_status_t rv, const char *s1,
                 const char *s2);
static void show_usage(void);

int main(int argc, const char * const * argv)
{
    apr_pool_t *pool;
    const cmd *act;
    apr_getopt_t *os;
    char optch;
    const char *optarg;
    const char*dbtype;

    (void) apr_initialize();
    apr_pool_create(&pool, NULL);
    atexit(apr_terminate);

    (void) apr_getopt_init(&os, pool, argc, argv);

    progname = argv[0];
    dbtype = "default";

    while (apr_getopt(os, "Rt:", &optch, &optarg) == APR_SUCCESS) {
        switch (optch) {
        case 'R':       /* raw processing  */
            rflag++;
            break;
        case 't':
            dbtype = optarg;
            break;
        default:
            show_usage();
            fputs("unknown option.",stderr);
            exit(-1);
            break;
        }
    }

    if (argc <= os->ind) {
        show_usage();
        fputs("Note: If you have no clue what this program is, start with:\n", stderr);
        fputs("      ./testdbm auto foo\n", stderr);
        fputs("      where foo is the DBM prefix.\n", stderr);
        exit(-2);
    }

    if ((act = parse_command(argv[os->ind])) == NULL) {
        show_usage();
        fprintf(stderr, "unrecognized command: %s\n", argv[os->ind]);
        exit(-3);
    }

    if (++os->ind >= argc) {
        show_usage();
        fputs("please supply a DB file to use (may be created)\n", stderr);
        exit(-4);
    }

    doit(act, dbtype, argv[os->ind], pool);

    apr_pool_destroy(pool);

    return 0;
}

static void doit(const cmd *act, const char*type, const char *file, 
                 apr_pool_t *pool)
{
    apr_status_t rv;
    apr_datum_t key;
    apr_datum_t val;
    apr_dbm_t *db;
    char *op;
    int n;
    char *line;
    const char *use1;
    const char *use2;
#ifdef TIME
    long start;
    extern long time();
#endif

    rv = apr_dbm_open_ex(&db, type, file, act->flags, APR_OS_DEFAULT, pool);
    if (rv != APR_SUCCESS)
        oops(db, rv, "cannot open: %s", file);

    line = (char *) apr_palloc(pool,LINEMAX);

    switch (act->scode) {

    case DLOOK:
        while (fgets(line, LINEMAX, stdin) != NULL) {
            n = strlen(line) - 1;
            line[n] = 0;
            if (n == 0)
                break;

            key.dptr = line;
            key.dsize = n;
            rv = apr_dbm_fetch(db, key, &val);
            if (rv == APR_SUCCESS) {
                prdatum(stdout, val);
                putchar('\n');
                continue;
            }
            prdatum(stderr, key);
            fprintf(stderr, ": not found.\n");
        }
        break;

    case DDELETE:
        while (fgets(line, LINEMAX, stdin) != NULL) {
            n = strlen(line) - 1;
            line[n] = 0;
            if (n == 0)
                break;

            key.dptr = line;
            key.dsize = n;
            if (apr_dbm_delete(db, key) != APR_SUCCESS) {
                prdatum(stderr, key);
                fprintf(stderr, ": not found.\n");
            }
        }
        break;
    case DCAT:
        rv = apr_dbm_firstkey(db, &key);
        if (rv != APR_SUCCESS)
            oops(db, rv, "could not fetch first key: %s", file);

        while (key.dptr != NULL) {
            prdatum(stdout, key);
            putchar('\t');
            rv = apr_dbm_fetch(db, key, &val);
            if (rv != APR_SUCCESS)
                oops(db, rv, "apr_dbm_fetch", "failure");
            prdatum(stdout, val);
            putchar('\n');
            rv = apr_dbm_nextkey(db, &key);
            if (rv != APR_SUCCESS)
                oops(db, rv, "NextKey", "failure");
        }
        break;
    case DBUILD:
#ifdef TIME
        start = time(0);
#endif
        while (fgets(line, LINEMAX, stdin) != NULL) {
            n = strlen(line) - 1;
            line[n] = 0;
            if (n == 0)
                break;

            key.dptr = line;
            if ((op = strchr(line, '\t')) != 0) {
                key.dsize = op - line;
                *op++ = 0;
                val.dptr = op;
                val.dsize = line + n - op;
            }
            else
                oops(NULL, APR_EGENERAL, "bad input: %s", line);

            rv = apr_dbm_store(db, key, val);
            if (rv != APR_SUCCESS) {
                prdatum(stderr, key);
                fprintf(stderr, ": ");
                oops(db, rv, "store: %s", "failed");
            }
        }
#ifdef TIME
        printf("done: %d seconds.\n", time(0) - start);
#endif
        break;
    case DPRESS:
        break;
    case DCREAT:
        break;
    case DTRUNC:
        break;
    case DNAME:
        apr_dbm_get_usednames(pool, file, &use1, &use2);
        fprintf(stderr, "%s %s\n", use1, use2);
        break;
    case DAUTO:
        {
            int i;
            char *valdata = "0123456789";
            fprintf(stderr, "Generating data: ");
            for (i = 0; i < 10; i++) {
                int j;
                char c, keydata[10];
                for (j = 0, c = 'A' + (i % 16); j < 10; j++, c++) {
                    keydata[j] = c;
                }
                key.dptr = keydata;
                key.dsize = 10;
                val.dptr = valdata;
                val.dsize = 10;
                rv = apr_dbm_store(db, key, val);
                if (rv != APR_SUCCESS) {
                    prdatum(stderr, key);
                    fprintf(stderr, ": ");
                    oops(db, rv, "store: %s", "failed");
                }
            }
            fputs("OK\n", stderr);
            fputs("Testing existence/retrieval: ", stderr);
            for (i = 0; i < 10; i++) {
                int j;
                char c, keydata[10];
                for (j = 0, c = 'A' + (i % 16); j < 10; j++, c++) {
                    keydata[j] = c;
                }
                key.dptr = keydata;
                key.dsize = 10;
                if (!apr_dbm_exists(db, key)) {
                    prdatum(stderr, key);
                    oops(db, 0, "exists: %s", "failed");
                }
                rv = apr_dbm_fetch(db, key, &val);
                if (rv != APR_SUCCESS || val.dsize != 10 ||
                    (strncmp(val.dptr, valdata, 10) != 0) ) { 
                    prdatum(stderr, key);
                    fprintf(stderr, ": ");
                    oops(db, rv, "fetch: %s", "failed");
                }
            }
            fputs("OK\n", stderr);
        }
        break;
    }

    apr_dbm_close(db);
}

static const cmd *parse_command(const char *str)
{
    int i;

    for (i = 0; i < CMD_SIZE; i++)
        if (strcasecmp(cmds[i].sname, str) == 0)
            return &cmds[i];

    return NULL;
}

static void prdatum(FILE *stream, apr_datum_t d)
{
    int c;
    const char *p = d.dptr;
    int n = d.dsize;

    while (n--) {
        c = *p++ & 0377;
        if (c & 0200) {
            fprintf(stream, "M-");
            c &= 0177;
        }
        if (c == 0177 || c < ' ') 
            fprintf(stream, "^%c", (c == 0177) ? '?' : c + '@');
        else
            putc(c, stream);
    }
}

static void oops(apr_dbm_t * dbm, apr_status_t rv, const char *s1,
                 const char *s2)
{
    char errbuf[200];

    if (progname) {
        fprintf(stderr, "%s: ", progname);
    }
    fprintf(stderr, s1, s2);
    fprintf(stderr, "\n");

    if (rv != APR_SUCCESS) {
        apr_strerror(rv, errbuf, sizeof(errbuf));
        fprintf(stderr, "APR Error %d - %s\n", rv, errbuf);

        if (dbm) {
            apr_dbm_geterror(dbm, &rv, errbuf, sizeof(errbuf));
            fprintf(stderr, "APR_DB Error %d - %s\n", rv, errbuf);
        }
    }
    exit(1);
}

static void show_usage(void)
{
    int i;

    if (!progname) {
        progname = "testdbm";
    }

    fprintf(stderr, "%s [-t DBM-type] [-R] [commands] dbm-file-path\n", 
            progname);

    fputs("Available DBM-types:", stderr);
#if APU_HAVE_GDBM
    fputs(" GDBM", stderr);
#endif
#if APU_HAVE_NDBM
    fputs(" NDBM", stderr);
#endif
#if APU_HAVE_SDBM
    fputs(" SDBM", stderr);
#endif
#if APU_HAVE_DB
    fputs(" DB", stderr);
#endif
    fputs(" default\n", stderr);

    fputs("Available commands:\n", stderr);
    for (i = 0; i < CMD_SIZE; i++) {
        fprintf(stderr, "%-8s%c", cmds[i].sname,
                ((i + 1) % 6 == 0) ? '\n' : ' ');
    }
    fputs("\n", stderr);
}
