/* testgdbm.c - Driver program to test the database routines and to
   help debug gdbm.  Uses inside information to show "system" information */

/*  This file is part of GDBM, the GNU data base manager, by Philip A. Nelson.
    Copyright (C) 1990  Free Software Foundation, Inc.

    GDBM 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 1, or (at your option)
    any later version.

    GDBM 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 GDBM; see the file COPYING.  If not, write to
    the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

    You may contact the author by:
       e-mail:  phil@wwu.edu
      us-mail:  Philip A. Nelson
                Computer Science Department
                Western Washington University
                Bellingham, WA 98226
        phone:  (206) 676-3035
       
*************************************************************************/

/*
 * MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
 *
 * To this port, the same copying conditions apply as to the
 * original release.
 *
 * IMPORTANT:
 * This file is not identical to the original GNU release!
 * You should have received this code as patch to the official
 * GNU release.
 *
 * MORE IMPORTANT:
 * This port comes with ABSOLUTELY NO WARRANTY.
 *
 * $Header: e:/gnu/gdbm/RCS/testgdbm.c'v 1.4.0.2 90/08/16 09:55:48 tho Exp $
 */

#include <stdio.h>
#include <sys/types.h>
#ifndef MSDOS
#include <sys/file.h>
#endif /* not MSDOS */
#include <sys/stat.h>
#include "gdbmdefs.h"
#include "systems.h"
#include "gdbmerrno.h"
#include "extern.h"

extern gdbm_error gdbm_errno;

extern char * gdbm_version;

gdbm_file_info *gdbm_file;

#ifdef __STDC__
static void print_bucket (hash_bucket *bucket, char *mesg);
static void _gdbm_print_avail_list (gdbm_file_info *dbf);
extern void main (int argc, char **argv);
void gdbm_perror (char *msg);
#endif /* __STDC__ */


/* access GDBM_ERRNO.  [tho]  */

void
gdbm_perror (char *msg)
{
  char *err_msg;

  switch (gdbm_errno)
    {
    case GDBM_NO_ERROR:
      err_msg = "no error";
      break;
    case GDBM_MALLOC_ERROR:
      err_msg = "can't malloc";
      break;
    case GDBM_BLOCK_SIZE_ERROR:
      err_msg = "bad block size";
      break;
    case GDBM_FILE_OPEN_ERROR:
      err_msg = "can't open file";
      break;
    case GDBM_FILE_WRITE_ERROR:
      err_msg = "can't write file";
      break;
    case GDBM_FILE_SEEK_ERROR:
      err_msg = "can't seek file";
      break;
    case GDBM_FILE_READ_ERROR:
      err_msg = "can't read file";
      break;
    case GDBM_BAD_MAGIC_NUMBER:
      err_msg = "bad magic number";
      break;
    case GDBM_EMPTY_DATABASE:
      err_msg = "empty database";
      break;
    case GDBM_CANT_BE_READER:
      err_msg = "can't be reader";
      break;
    case GDBM_CANT_BE_WRITER:
      err_msg = "can't be writer";
      break;
    case GDBM_READER_CANT_DELETE:
      err_msg = "can't delete";
      break;
    case GDBM_READER_CANT_STORE:
      err_msg = "can't store";
      break;
    case GDBM_READER_CANT_REORGANIZE:
      err_msg = "can't reorganize";
      break;
    case GDBM_UNKNOWN_UPDATE:
      err_msg = "unkown update";
      break;
    case GDBM_ITEM_NOT_FOUND:
      err_msg = "item not found";
      break;
    case GDBM_REORGANIZE_FAILED:
      err_msg = "reorganization failed";
      break;
    case GDBM_CANNOT_REPLACE:
      err_msg = "can't replace";
      break;
    default:
      err_msg = "unknown error";
    }

  fprintf (stderr, "%s (GDBM error: %s)\n", msg, err_msg);
}


/* Debug procedure to print the contents of the current hash bucket. */
VOID
print_bucket (bucket, mesg)
     hash_bucket *bucket;
     char *mesg;
{
  int  index;

  printf ("******* %s **********\n\nbits = %d\ncount= %d\nHash Table:\n",
	 mesg, bucket->bucket_bits, bucket->count);
  printf ("     #    hash value     key size    data size     data adr  home\n");
  for (index = 0; index < gdbm_file->header->bucket_elems; index++)
#ifdef MSDOS
    printf ("  %4d  %12lx  %11d  %11d  %11ld %5d\n", index,
	   bucket->h_table[index].hash_value,
	   bucket->h_table[index].key_size,
	   bucket->h_table[index].data_size,
	   bucket->h_table[index].data_pointer,
	   (int) (bucket->h_table[index].hash_value % gdbm_file->header->bucket_elems));
#else /* not MSDOS */
    printf ("  %4d  %12x  %11d  %11d  %11d %5d\n", index,
	   bucket->h_table[index].hash_value,
	   bucket->h_table[index].key_size,
	   bucket->h_table[index].data_size,
	   bucket->h_table[index].data_pointer,
	   bucket->h_table[index].hash_value % gdbm_file->header->bucket_elems);
#endif /* not MSDOS */

  printf ("\nAvail count = %1d\n", bucket->av_count);
  printf ("Avail  adr     size\n");
  for (index = 0; index < bucket->av_count; index++)
#ifdef MSDOS
    printf ("%9ld%9d\n", bucket->bucket_avail[index].av_adr,
#else /* not MSDOS */
    printf ("%9d%9d\n", bucket->bucket_avail[index].av_adr,
#endif /* not MSDOS */
	                bucket->bucket_avail[index].av_size);
}


VOID
_gdbm_print_avail_list (dbf)
     gdbm_file_info *dbf;
{
  LONG temp;
  int size;
  avail_block *av_stk;
 
  /* Print the the header avail block.  */
  printf ("\nheader block\nsize  = %d\ncount = %d\n",
	  dbf->header->avail.size, dbf->header->avail.count);
  for (temp = 0; temp < dbf->header->avail.count; temp++)
    {
#ifdef MSDOS
      printf ("  %15d   %10ld \n", dbf->header->avail.av_table[temp].av_size,
#else /* not MSDOS */
      printf ("  %15d   %10d \n", dbf->header->avail.av_table[temp].av_size,
#endif /* not MSDOS */
	      dbf->header->avail.av_table[temp].av_adr);
    }

  /* Initialize the variables for a pass throught the avail stack. */
  temp = dbf->header->avail.next_block;
  size = ( ( (dbf->header->avail.size * sizeof (avail_elem)) >> 1)
	  + sizeof (avail_block));
  av_stk = (avail_block *) alloca (size);
#ifdef MSDOS
  if (av_stk == (avail_block *) 0)
    {
      fprintf (stderr, "alloca failed.\n");
      exit (-2);
    }
#endif /* MSDOS */

  /* Print the stack. */
  while (FALSE)
    {
      lseek (dbf->desc, temp, L_SET);
#ifdef MSDOS
      read  (dbf->desc, (char *) av_stk, size);
#else /* not MSDOS */
      read  (dbf->desc, av_stk, size);
#endif /* not MSDOS */

      /* Print the block! */
      printf ("\nblock = %d\nsize  = %d\ncount = %d\n", temp,
	      av_stk->size, av_stk->count);
      for (temp = 0; temp < av_stk->count; temp++)
	{
#ifdef MSDOS
	  printf ("  %15d   %10ld \n", av_stk->av_table[temp].av_size,
#else /* not MSDOS */
	  printf ("  %15d   %10d \n", av_stk->av_table[temp].av_size,
#endif /* not MSDOS */
	    av_stk->av_table[temp].av_adr);
	}
      temp = av_stk->next_block;
    }
}

_gdbm_print_bucket_cache (dbf)
     gdbm_file_info *dbf;
{
  int index;
  char changed;
 
  printf ("Bucket Cache:\n  Index:  Address  Changed  Data_Hash \n");
  for (index=0; index < CACHE_SIZE; index++)
    {
      changed = dbf->bucket_cache[index].ca_changed;
#ifdef MSDOS
      printf ("  %5d:  %7ld  %7s  %lx\n",
#else /* not MSDOS */
      printf ("  %5d:  %7d  %7s  %x\n",
#endif /* not MSDOS */
	      index,
	      dbf->bucket_cache[index].ca_adr,
	      (changed ? "True" : "False"),
	      dbf->bucket_cache[index].ca_data.hash_val);
    }
}


/* The test program allows one to call all the routines plus the hash function.
   The commands are single letter commands.  The user is prompted for all other
   information.  See the help command (?) for a list of all commands. */

VOID
main (argc, argv)
     int argc;
     char *argv[];

{

  char  cmd_ch;

  datum key_data;
  datum data_data;
  datum return_data;

  char key_line[500];
  char data_line[1000];

  char done = FALSE;

  char *file_name;


  /* Argument checking. */
  if (argc > 2)
    {
      printf ("Usage: %s [gdbm-file] \n",argv[0]);
      exit (2);
    }

  if (argc > 1)
    {
      file_name = argv[1];
    }
  else
    {
      file_name = "junk.gdbm";
    }

  /* Initialize variables. */
  key_data.dptr = NULL;
  data_data.dptr = data_line;

  gdbm_file = gdbm_open (file_name, 512, GDBM_WRCREAT, 00664, NULL);
  if (gdbm_file == NULL)
    {
      gdbm_perror ("gdbm_open failed");
      exit (2);
    }

  /* Welcome message. */
  printf ("\nWelcome to the gdbm test program.  Type ? for help.\n\n");
  
  while (!done)
    {
      printf ("com -> ");
#ifdef MSDOS			/* shut up the compiler */
      cmd_ch = (char) getchar ();
#else /* not MSDOS */
      cmd_ch = getchar ();
#endif /* not MSDOS */
      if (cmd_ch != '\n')
	{
	  char temp;
	  do
#ifdef MSDOS			/* shut up the compiler */
	      temp = (char) getchar ();
#else /* not MSDOS */
	      temp = getchar ();
#endif /* not MSDOS */
	  while (temp != '\n' && temp != EOF);
	}
      if (cmd_ch == EOF) cmd_ch = 'q';
      switch (cmd_ch)
	{
	
	/* Standard cases found in all test{dbm,ndbm,gdbm} programs. */
	case '\n':
	  printf ("\n");
	  break;

	case 'c':
	  {
	    int temp;
	    temp = 0;
	    if (key_data.dptr != NULL) free (key_data.dptr);
	    return_data = gdbm_firstkey (gdbm_file);
	    while (return_data.dptr != NULL)
	      {
		temp++;
		key_data = return_data;
		return_data = gdbm_nextkey (gdbm_file, key_data);
		free (key_data.dptr);
	      }
	    printf ("There are %d items in the database.\n\n", temp);
	  }
	  break;

	case 'd':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  printf ("key -> ");
	  gets (key_line);
	  key_data.dptr = key_line;
	  key_data.dsize = strlen (key_line)+1;
	  if (gdbm_delete (gdbm_file, key_data) != 0)
	    gdbm_perror ("Item not found or deleted");
	  printf ("\n");
	  key_data.dptr = NULL;
	  break;

	case 'f':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  printf ("key -> ");
	  gets (key_line);
	  key_data.dptr = key_line;
	  key_data.dsize = strlen (key_line)+1;
	  return_data = gdbm_fetch (gdbm_file, key_data);
	  if (return_data.dptr != NULL)
	    {
	      printf ("data is ->%s\n\n", return_data.dptr);
	      free (return_data.dptr);
	    }
	  else
	    printf ("No such item found.\n\n");
	  key_data.dptr = NULL;
	  break;

	case 'n':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  printf ("key -> ");
	  gets (key_line);
	  key_data.dptr = key_line;
	  key_data.dsize = strlen (key_line)+1;
	  return_data = gdbm_nextkey (gdbm_file, key_data);
	  if (return_data.dptr != NULL)
	    {
	      key_data = return_data;
	      printf ("key is  ->%s\n", key_data.dptr);
	      return_data = gdbm_fetch (gdbm_file, key_data);
	      printf ("data is ->%s\n\n", return_data.dptr);
	      free (return_data.dptr);
	    }
	  else
	    {
	      gdbm_perror ("No such item found");
	      key_data.dptr = NULL;
	    }
	  break;

	case 'q':
	  done = TRUE;
	  break;

	case 's':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  printf ("key -> ");
	  gets (key_line);
	  key_data.dptr = key_line;
	  key_data.dsize = strlen (key_line)+1;
	  printf ("data -> ");
	  gets (data_line);
	  data_data.dsize = strlen (data_line)+1;
	  if (gdbm_store (gdbm_file, key_data, data_data, GDBM_REPLACE) != 0)
	    gdbm_perror ("Item not inserted");
	  printf ("\n");
	  key_data.dptr = NULL;
	  break;

	case '1':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  key_data = gdbm_firstkey (gdbm_file);
	  if (key_data.dptr != NULL)
	    {
	      printf ("key is  ->%s\n", key_data.dptr);
	      return_data = gdbm_fetch (gdbm_file, key_data);
	      printf ("data is ->%s\n\n", return_data.dptr);
	      free (return_data.dptr);
	    }
	  else
	    gdbm_perror ("No such item found");
	  break;

	case '2':
	  return_data = gdbm_nextkey (gdbm_file, key_data);
	  if (return_data.dptr != NULL)
	    {
	      free (key_data.dptr);
	      key_data = return_data;
	      printf ("key is  ->%s\n", key_data.dptr);
	      return_data = gdbm_fetch (gdbm_file, key_data);
	      printf ("data is ->%s\n\n", return_data.dptr);
	      free (return_data.dptr);
	    }
	  else
	    gdbm_perror ("No such item found");
	  break;


	/* Special cases for the testgdbm program. */
	case 'r':
	  {
	    if (gdbm_reorganize (gdbm_file))
	      gdbm_perror ("Reorganization failed");
	    else
	      printf ("Reorganization succeeded. \n\n");
	  }
	  break;

	case 'A':
	  _gdbm_print_avail_list (gdbm_file);
	  printf ("\n");
	  break;

	case 'B':
	  {
	    int temp;
	    char number[80];

	    printf ("bucket? ");
	    gets (number);
	    sscanf (number,"%d",&temp);

	    if (temp >= gdbm_file->header->dir_size /4)
	      {
		gdbm_perror ("Not a bucket");
		break;
	      }
	    _gdbm_get_bucket (gdbm_file, temp);
	  }
	  printf ("Your bucket is now ");

	case 'C':
	  print_bucket (gdbm_file->bucket, "Current bucket");
	  printf ("\n current directory entry = %d.\n", gdbm_file->bucket_dir);
#ifdef MSDOS
	  printf (" current bucket address  = %ld.\n\n",
#else /* not MSDOS */
	  printf (" current bucket address  = %d.\n\n",
#endif /* not MSDOS */
		  gdbm_file->cache_entry->ca_adr);
	  break;

	case 'D':
	  printf ("Hash table directory.\n");
	  printf ("  Size =  %d.  Bits = %d. \n\n",gdbm_file->header->dir_size,
		  gdbm_file->header->dir_bits);
	  {
	    int temp;

	    for (temp = 0; temp < gdbm_file->header->dir_size / 4; temp++)
	      {
#ifdef MSDOS
		printf ("  %10d:  %12ld\n", temp, gdbm_file->dir[temp]);
#else /* not MSDOS */
		printf ("  %10d:  %12d\n", temp, gdbm_file->dir[temp]);
#endif /* not MSDOS */
		if ( (temp+1) % 20 == 0 && isatty (0))
		  {
		    printf ("*** CR to continue: ");
		    while (getchar () != '\n') /* Do nothing. */;
		  }
	      }
	  }
	  printf ("\n");
	  break;

	case 'F':
	  {
#ifndef MSDOS			/* shut up the compiler */
  	    int temp;
#endif /* not MSDOS */

	    printf ("\nFile Header: \n\n");
#ifdef MSDOS
	    printf ("  table        = %ld\n", gdbm_file->header->dir);
#else /* not MSDOS */
	    printf ("  table        = %d\n", gdbm_file->header->dir);
#endif /* not MSDOS */
	    printf ("  table size   = %d\n", gdbm_file->header->dir_size);
	    printf ("  table bits   = %d\n", gdbm_file->header->dir_bits);
	    printf ("  block size   = %d\n", gdbm_file->header->block_size);
	    printf ("  bucket elems = %d\n", gdbm_file->header->bucket_elems);
	    printf ("  bucket size  = %d\n", gdbm_file->header->bucket_size);
#ifdef MSDOS
	    printf ("  header magic = %lx\n", gdbm_file->header->header_magic);
#else /* not MSDOS */
	    printf ("  header magic = %x\n", gdbm_file->header->header_magic);
#endif /* not MSDOS */
	    printf ("  next block   = %d\n", gdbm_file->header->next_block);
	    printf ("  avail size   = %d\n", gdbm_file->header->avail.size);
	    printf ("  avail count  = %d\n", gdbm_file->header->avail.count);
	    printf ("  avail nx blk = %d\n", gdbm_file->header->avail.next_block);
	    printf ("\n");
	  }
	  break;

        case 'H':
	  if (key_data.dptr != NULL) free (key_data.dptr);
	  printf ("key -> ");
	  gets (key_line);
	  key_data.dptr = key_line;
	  key_data.dsize = strlen (key_line)+1;
#ifdef MSDOS
	  printf ("hash value = %lx. \n\n", _gdbm_hash (key_data));
#else /* not MSDOS */
	  printf ("hash value = %x. \n\n", _gdbm_hash (key_data));
#endif /* not MSDOS */
	  key_data.dptr = NULL;
	  break;

	case 'K':
	  _gdbm_print_bucket_cache (gdbm_file);
	  break;

	case 'V':
	  printf ("%s\n\n", gdbm_version);
	  break;

	case '?':
	  printf ("c - count (number of entries)\n");
	  printf ("d - delete\n");
	  printf ("f - fetch\n");
	  printf ("n - nextkey\n");
	  printf ("q - quit\n");
	  printf ("s - store\n");
	  printf ("1 - firstkey\n");
	  printf ("2 - nextkey on last key (from n, 1 or 2)\n\n");

	  printf ("r - reorganize\n");
	  printf ("A - print avail list\n");
	  printf ("B - get and print current bucket n\n");
	  printf ("C - print current bucket\n");
	  printf ("D - print hash directory\n");
	  printf ("F - print file header\n");
	  printf ("H - hash value of key\n");
	  printf ("K - print the bucket cache\n");
	  printf ("V - print version of gdbm\n");
	  break;

	default:
	  printf ("What? \n\n");
	  break;

	}
    }

  /* Quit normally. */
  exit (0);

}
