/*
**++
**  FACILITY:
**      CACHEM.C
**
**  ABSTRACT:
**      Cache Message Id Master utility for cacheing for News. ("Cache 'em")
**
**  AUTHOR:
**      Bill Teahan, University of Waikato, Hamilton, New Zealand
**
**  COPYRIGHT:
**      Copyright © 1991
**
**	6.1b7   3-Aug-1993  Charles Bailey  bailey@genetics.upenn.edu
**	  - added signal handler to avoid ugliness if an error occurrs
**	    when accessing cache
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
**--
**/

#ifdef __GNUC__
#define variant_union	volatile union
#define variant_struct	volatile struct
#endif

#ifdef __DECC
#pragma message save
#pragma message disable (MACROEXT)
#endif

#ifndef NAKED_INCLUDES
#define NAKED_INCLUDES 0
#endif

#if NAKED_INCLUDES

#include starlet
#include descrip
#include lckdef
#include prvdef
#include jpidef
#include lnmdef
#include psldef
#include chfdef
#include ssdef

#include types
#include stddef
#include stdio
#include stdlib
#include string
#include ctype

#else	/* !NAKED_INCLUDES */

#include <starlet.h>
#include <descrip.h>
#include <lckdef.h>
#include <prvdef.h>
#include <jpidef.h>
#include <lnmdef.h>
#include <psldef.h>
#include <chfdef.h>
#include <ssdef.h>

#if defined(__GNUC__)
  /*  problem is that both multinet and gnu_cc_include contain sys/types.h    *
   *  and the multinet version is missing some declarations (dev_t,off_t,...) *
   *  which are necessary when stat.h gets included, so we force GNU types.h  */
#  include <gnu_cc_include:[sys]types.h>
#elif defined(TWG) || defined(MULTINET)
#  include <sys/types.h>    /* take the Multinet (or other IP vendor) types.h */
#else
#  include <types.h>        /* take whatever we have */
#endif

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#endif

#ifdef __DECC
#pragma message restore
#endif

#include "cachedefine.h"

extern void lib$signal(int, ...);

extern int open_log_file(const char *);
extern int write_log_file(const char *);
extern int close_log_file();

extern void cache_free_lock(CACHE *);
extern void cache_get_lock(CACHE *);
extern int cache_check_lnm();
extern CACHE *cache_map_cache();
extern int cache_add_cache(CACHE_ID, CACHE *);
extern int cache_debug_cache(int,int);
extern int cache_find_cache(CACHE_ID, CACHE *);
extern void cache_dump_hash_list(int, int, CACHE *);
extern void cache_verify_cache(CACHE *);

void cache_dump_cache(CACHE *, int, int);
void cache_initialize_cache(CACHE *cache);


void cachem_exit_handler(s, cache )
	int s;
        CACHE *cache;
{
        write_log_file( "Exit handler in CACHEM" );
	cache_free_lock( cache );
	sys$exit(1);
}

void cachem_declare_exit_handler( cache )
	CACHE *cache;
{
	int status;

	static int exit_status_addr;
	static struct {
	  unsigned int exit_l_forw;
 	  unsigned int exit_a_addr;
 	  unsigned int exit_l_argc;
	  unsigned int exit_a_stat;
	  unsigned int exit_l_arg1;
	} exitblk;

	exitblk.exit_l_forw = 0;
 	exitblk.exit_a_addr = (unsigned int) cachem_exit_handler;
	exitblk.exit_l_argc = 2;
	exitblk.exit_a_stat = (unsigned int) &exit_status_addr;
	exitblk.exit_l_arg1 = (unsigned int) cache;
	status = sys$dclexh( &exitblk );
}

/* print out the date */
void cache_print_date( date )
int date[2];
{
	int status;
        unsigned short date_len;
	char date_str[25];
	$DESCRIPTOR( date_desc, date_str );
       
	status = sys$asctim( &date_len, &date_desc, date, 0 );
	cache_signal( status );
	date_str [date_len] = '\0';
	printf( "%s", date_str );
}

/* list ids in the cache */
void cache_list_ids( cache, filename, here, there, debug )
	CACHE *cache;
	char *filename;
	int here, there, debug;
{
	int i;
	FILE *fp;

 	fp = fopen( filename, "w" );
	if (fp == NULL)
	    printf( "Unable to open file %s\n", filename );
	else {
	    cache_get_lock( cache );
	    if (here < 1) here = 1;
	    if (there > CACHE_IDS_SIZE-1) there = CACHE_IDS_SIZE-1;
	    for (i = here; i <= there; i++) {
	      if ((i / 1000) * 1000 == i) { /* free lock temporarily */
		  if (debug) printf( "%d...\015", i );
		  cache_free_lock( cache );
		  cache_get_lock( cache );
	      }
	      if (cache->cache_ids [i].cache_hval != 0)
		  fprintf( fp, "%s\n", cache->cache_ids [i].cache_id );
	    }
	    cache_free_lock( cache );
	    fclose( fp );
	    if (debug) printf( "\n" );
	}
}

/* dump out the contents of the cache */
void cache_dump_cache( cache, here, there )
	CACHE *cache;
	int here, there;
{
	int i;

	cache_get_lock( cache );
	printf( "Current Cache Index = %d\n", cache->cache_cid );
	printf( "Ids found in cache (that were rejected) = %d\n",
		cache->cache_found );
	printf( "Ids not found in cache (added to cache) = %d\n",
		cache->cache_lost );
	printf( "Date cache last zeroed = " );
	cache_print_date( cache->cache_zero_date );
	printf( "\n" );
	printf( "Date counts last reset = " );
	cache_print_date( cache->cache_reset_date );
	printf( "\n\n" );

	if (here < 1) here = 1;
	if (there > CACHE_IDS_SIZE-1) there = CACHE_IDS_SIZE-1;
	for (i = here; i <= there; i++) {
	  if ((i / 1000) * 1000 == i) { /* free lock temporarily */
	      cache_free_lock( cache );
	      cache_get_lock( cache );
	  }
	  if (cache->cache_ids [i].cache_hval != 0)
 	    printf( "Index %d (Hash %d = %X) Id %s\n", i, 
		cache->cache_ids [i].cache_hval,
    		cache->cache_ids [i].cache_hvalue,
		cache->cache_ids [i].cache_id );
	}
	cache_free_lock( cache );
}

/* signal handler - output the message, and then exit gracefully
      3-Aug-1993  Charles Bailey  bailey@genetics.upenn.edu  */
int cachem_signal_handler(struct chf$signal_array *sigarr, 
                          struct chf$mech_array *mecharr) {

	sigarr->chf$l_sig_args -= 2;
	if (!(sys$putmsg(sigarr)&1)) return(SS$_RESIGNAL);

	/* since we don't know if we have a lock on the cache,
	    just $Deq all user-mode locks */
	if (!(sys$deq(0,0,0,LCK$M_DEQALL)&1)) return(SS$_RESIGNAL);
	/* we've aready dequeued any locks, and we're not sure we can get at
	   the log file, so just cancel the exit handler */
	if (!(sys$canexh(0)&1)) return(SS$_RESIGNAL);

	/* exit with a severe error, but one which doesn't require $FAO params.
	   If we exit with the signalled condition, e.g. SS$_ACCVIO, junk is
	   used as $FAO params, generating a potentially misleading message */
	sys$exit(SS$_ABORT);
	return(SS$_RESIGNAL);  /* should not happen */
}

int main(void)
{
 	int finished = 0;
	int found, debug, here, there, total;
	char reply[2];
	char filename[64];

   	CACHE *cache;
	CACHE_ID id;

	VAXC$ESTABLISH(cachem_signal_handler);

	if (!cache_check_lnm()) {
	    printf( "Logical NEWS_CACHE is not defined on this node.\n" );
	    return(0);
	}

	cache = (CACHE *) cache_map_cache();
	cachem_declare_exit_handler( cache );

        open_log_file( CACHE_LOG_FILENAME );
        write_log_file( "Entering CACHEM" );
	printf("Option (h for Help)? " );
	while (!finished) {
	  finished = (gets( reply ) == NULL);
	  if (!finished) {  
	    switch (reply [0]) {
		case 'a' :
		case 'A' :
		    printf( "Id? " );
		    gets( id );
		    cache_add_cache( id, cache );
		    break;
		/*case 'b' :
		case 'B' :
		    cache_debug_cache(1,1);
		    break;*/
		case 'c' :
		case 'C' :
		    cache_get_lock( cache );
		    printf( "Number of insertions     = %d\n",
			cache->cache_lost );
		    printf( "Number of rejections     = %d\n",
			cache->cache_found );
		    total = (cache->cache_found + cache->cache_lost);
		    printf( "Percentage of rejects    = %5.1f\n",
			100.0 * ((float) cache->cache_found /
			(float) (total ? total : 1)) );
		    printf( "Current index in cache   = %d\n",
			cache->cache_cid );
		    printf( "Date counts last reset   = " );
		    cache_print_date( cache->cache_reset_date );
		    printf( "\n" );
		    cache_free_lock( cache );
		    break;
		case 'd' :
		case 'D' :
		    printf( "From index? " );
		    scanf( "%d", &here );
		    printf( "To index? " );
		    scanf( "%d", &there );
		    cache_dump_cache( cache, here, there );
		    break;
		case 'e' :
		case 'E' :
		    finished = 1;
		    break;
		case 'f' :
		case 'F' :
		    printf( "Id? " );
		    gets( id );
 		    debug = cache_debug_cache(0,0);
		    found = (cache_find_cache( id, cache ));
		    if ((!debug) && (found))
			printf( "Found in cache list\n" );
		    else
			printf( "Not found in cache list\n" );
		    break;
		case 'h' :
		case 'H' :
		    printf( "Valid options are : \n" );
		    printf( "A(dd)       Add an id to the cache\n" );
		    printf( "C(ounts)    List number of insertions/rejections\n" );
		    printf( "D(ump)      Dump out the contents for a range of indices\n" );
		    printf( "E(xit)      Quit\n" );
		    printf( "F(ind)      Find if an id is in the cache\n" );
		    printf( "L(ist)      List ids in the cache to a file\n" );
		    printf( "R(eset)     Reset the insertion/rejection counters\n" );
		    printf( "S(earch)    Search for hash list > depth\n" );
		    printf( "V(erify)    Verify cache/print statistics\n" );
		    printf( "Z(ero)      Zap the cache\n" );
		    break;
		case 'l' :
		case 'L' :
		    printf( "Filename? " );
		    gets( filename );
		    printf( "From index? " );
		    scanf( "%d", &here );
		    printf( "To index? " );
		    scanf( "%d", &there );
		    cache_list_ids( cache, filename, here, there, 1 );
		    break;
		case 'r' :
		case 'R' :
		    printf( "Are you sure you want to reset the counts? " );
		    gets( reply );
		    if ((reply [0] == 'Y') || (reply [0] == 'y')) {
			printf( "Re-setting counts...\n" );
			cache_get_lock( cache );
			cache->cache_lost = 0;
			cache->cache_found = 0;
			sys$gettim( cache->cache_reset_date );
			cache_free_lock( cache );
		    }
		    else
			printf( "Don't worry. I haven't touched them.\n" );
		    break;
		case 's' :
		case 'S' :
		    printf( "From index? " );
		    scanf( "%d", &here );
		    printf( "Depth? " );
		    scanf( "%d", &there );
		    cache_dump_hash_list( here, there, cache );
		    break;
		case 'v' :
		case 'V' :
		    cache_verify_cache(cache);
		    break;
		case 'z' :
		case 'Z' :
		    printf( "Are you sure you want to zap it? " );
		    gets( reply );
		    if ((reply [0] == 'Y') || (reply [0] == 'y')) {
			printf( "Re-initializing cache...\n" );
			cache_get_lock( cache );
			cache_initialize_cache( cache );
			cache_free_lock( cache );
		    }
		    else
			printf( "Don't worry. I haven't touched the cache.\n" );
		    break;
	    }
	  }
	  if (!finished && (reply [0] != '\0'))
	      printf("Option (h for Help)? " );
	}
	write_log_file( "Exiting CACHEM" );
        close_log_file();
        return(1);
}
