/* $Id: tst.C,v 1.8 2003/07/12 19:32:12 dm Exp $ */

/*
 *
 * Copyright (C) 2003 David Mazieres (dm@uun.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include "db.h"

#define bzero(p, s) memset (p, 0, s)

#define ENV_DIR "/var/tmp/dbenv"
#define DBPATH ENV_DIR "/tst.db"

static DB_ENV *dbenv;
static DB *db1, *db2;

void
_ckerr (char *line, int err)
{
  if (err) {
    fprintf (stderr, "ERROR:\n%s\n%s\n", line, db_strerror (err));
    exit (1);
  }
}
#define ckerr(x)				\
  do {						\
    int _ckerr_err_ = (x);			\
    if (_ckerr_err_)				\
      _ckerr (#x, _ckerr_err_);			\
  } while (0)

static int xtruncate (DB *db, DB_TXN *tid, u_int32_t *x, u_int32_t y)
  __attribute__ ((unused));
static int
xtruncate (DB *db, DB_TXN *tid, u_int32_t *x, u_int32_t y)
{
  int err;
  DBT k, v;
  DBC *c = NULL;

  if ((err = db->cursor (db, tid, &c, 0)))
    return err;

  bzero (&k, sizeof (k));
  bzero (&v, sizeof (v));
  v.flags |= DB_DBT_PARTIAL;
  while (!(err = c->c_get (c, &k, &v, DB_NEXT)))
    c->c_del (c, 0);

  c->c_close (c);
  if (err == DB_NOTFOUND)
    return 0;
  return err;
}

static void
mkdbs ()
{
  DB_TXN *tid;
  ckerr (db_create (&db1, dbenv, 0));
  ckerr (db_create (&db2, dbenv, 0));

#if DB_VERSION_MAJOR < 4
  ckerr (txn_begin (dbenv, NULL, &tid, 0));
#else /* DB_VERSION_MAJOR >= 4 */
  ckerr (dbenv->txn_begin (dbenv, NULL, &tid, 0));
#endif /* DB_VERSION_MAJOR >= 4 */

#if DB_VERSION_MAJOR < 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR == 0)
  ckerr (db1->open (db1, DBPATH, "db1", DB_BTREE, DB_CREATE, 0644));
  ckerr (db2->open (db2, DBPATH, "db2", DB_BTREE, DB_CREATE, 0644));
#else /* DB version >= 4.1 */
  ckerr (db1->open (db1, tid, DBPATH, "db1", DB_BTREE, DB_CREATE, 0644));
  ckerr (db2->open (db2, tid, DBPATH, "db2", DB_BTREE, DB_CREATE, 0644));
#endif /* DB version >= 4.1 */

#if DB_VERSION_MAJOR < 4
  ckerr (txn_commit (tid, 0));
#else /* DB_VERSION_MAJOR >= 4 */
  ckerr (tid->commit (tid, 0));
#endif /* DB_VERSION_MAJOR >= 4 */
}

static void
scandb (DB *db)
{
  DBC *c;
  DBT k, v;
  int i = 0;

  ckerr (db->cursor (db, NULL, &c, 0));
  bzero (&k, sizeof (k));
  bzero (&v, sizeof (v));
  for (;; i++) {
    int err = c->c_get (c, &k, &v, DB_NEXT);
    if (err == DB_NOTFOUND)
      break;
    else if (err)
      _ckerr ("while iterating through database", err);
  }
  printf ("scanned %d items\n", i);
  c->c_close (c);
}

static void
storecrap (DB_TXN *tid)
{
  int n = rand () % 1000;
  while (n--) {
    DBT k, v;
    int ki = rand () % 10000;
    int vi = rand ();
    DB *db = (ki & 1) ? db1 : db2;
    bzero (&k, sizeof (k));
    bzero (&v, sizeof (v));
    k.data = &ki;
    k.size = sizeof (ki);
    v.data = &vi;
    v.size = sizeof (vi);
    ckerr (db->put (db, tid, &k, &v, 0));
  }
}

int
main (int argc, char **argv)
{
  struct timeval tv;
  DB_TXN *tid = NULL;
  unsigned int seed;

  setvbuf (stdout, NULL, _IONBF, 0);
  gettimeofday (&tv, NULL);
  seed = tv.tv_usec;
  if (argc > 1)
    seed = atoi (argv[1]);
  printf ("seed is %u\n", seed);
  srand (seed);

  ckerr (db_env_create (&dbenv, 0));
  if (access (ENV_DIR, 0) < 0 && errno == ENOENT)
    mkdir (ENV_DIR, 0777);

  printf ("opening environment...");
  ckerr (dbenv->open (dbenv, ENV_DIR,
		      (DB_CREATE | DB_INIT_LOG | DB_INIT_LOCK | DB_INIT_MPOOL
		      | DB_INIT_TXN | DB_RECOVER), 0644));
  printf ("done\n");

  mkdbs ();
  scandb (db1);
  scandb (db2);

  for (;;) {
    u_int32_t garbage;

    if (!tid)
      ckerr (dbenv->txn_begin (dbenv, NULL, &tid, 0));

    write (1, ".", 1);
    if (rand () % 2)
      ckerr (db1->truncate (db1, tid, &garbage, 0));
    else
      ckerr (db2->truncate (db2, tid, &garbage, 0));

    /* simulate a crash */
    if (!(rand () % 50)) {
      write (1, "XT\n", 3);
      _exit (0);
    }

    storecrap (tid);

    /* simulate a crash */
    if (!(rand () % 50)) {
      write (1, "XS\n", 3);
      _exit (0);
    }

    switch (rand () % 3) {
    case 1:
      write (1, "C", 1);
      ckerr (tid->commit (tid, 0));
      tid = NULL;
      break;
    case 2:
      write (1, "A", 1);
      ckerr (tid->abort (tid));
      tid = NULL;
      break;
    }
  }

  write (1, "\n", 1);

  return 0;
}


