/*
**++
**  FACILITY:
**      CACHE.C
**
**  ABSTRACT:
**      Message Id caching for News.
**      Link with MAP.OBJ and HASH.OBJ.
**      (In News, call the routine CACHE_CHECK before HIST_CHECK and
**      ITM_CHECK in NNTP_SERVER.C).
**
**  AUTHOR:
**      Bill Teahan, University of Waikato, Hamilton, New Zealand
**
**  COPYRIGHT:
**      Copyright (c) 1991
**
**  MODIFICATION HISTORY:
**      Created		Mar-1991     WJT
**	Modified	Mar-1992     SLOANE@kuhub.cc.ukans.edu
**	- Fixed a bug in the exit routine, changed the calling sequence to
**	- include a flag indicating whether or not to actually add the article
**	- to the cache, and changed the hit/miss counting code.
**      Modified	Mar-1992     WJT
**	- Map cache, declare exit handler only once first time CACHE_CHECK1
**	- is called
**	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)
**	V6.1b10 31-Jul-1995	Mark Pizzolato	mark@infocomm.com
**	  - fixed an original bug which has received numerous mention on the
**	    mailing list regarding the errors that it logs. "Lock check
**	    variable already set to 1".  The code originally checked to see
**	    if the lock check variable was set prior to actually aquiring the
**	    lock manager lock for the global section.
**	  - Code has been modified to properly create and initialize the
**	    CACHE_FILENAME file if it doesn't exist.  This removes any external
**	    steps from being necessary to create and initialize the cache file.
**
**--
**/

#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 secdef
/* #include rmsdef   already included from rms.h */
#include rms

#if defined(TWG) || defined(MULTINET)
#include <types.h>
#ifdef __DECC
#include <sys/types.h>
#endif
#else
#include types
#endif

#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 <secdef.h>
/* #include <rmsdef.h>   already included from rms.h */
#include <rms.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"

struct	dsc$descriptor_const_s
{
	unsigned short	dsc$w_length;	/* length of data item in bytes,
					     or if dsc$b_dtype is DSC$K_DTYPE_V, bits,
					     or if dsc$b_dtype is DSC$K_DTYPE_P, digits (4 bits each) */
	unsigned char	dsc$b_dtype;	/* data type code */
	unsigned char	dsc$b_class;	/* descriptor class code = DSC$K_CLASS_S */
	const char	*dsc$a_pointer;	/* address of first byte of data storage */
};

#define $DESCRIPTOR_CONST(name,string) \
  struct dsc$descriptor_const_s name = { sizeof(string)-1, DSC$K_DTYPE_T, DSC$K_CLASS_S, string }

/* global variables */

/* member alignment is required to assure atomic volatile access */
#ifdef __DECC
#pragma member_alignment save
#pragma member_alignment
#endif
volatile struct {
   int lksb_l_status;
   int lksb_l_lock_id;
   int lksb_l_value;
} cache_lksb;
#ifdef __DECC
#pragma member_alignment restore
#endif

int cache_debug = 0;

int cache_saddr, cache_faddr; /* start and finish mapped virtual addresses */

extern int lib$getjpi();
extern void lib$signal(int, ...);
extern int lib$sig_to_ret(struct chf$signal_array*,struct chf$mech_array*);
extern int lib$establish(int (*)(struct chf$signal_array*, struct chf$mech_array*));

extern unsigned long hash_string(int, char *);
extern int open_log_file(const char *);
extern int write_log_file(const char *);
extern int close_log_file();

void cache_free_lock(CACHE *);

/*
 * Replacement "strcpy" routine - to retain parameter volatile attributes.
 * (borrowed from the GCC library)
 *
 * Copy string s2 to s1.  s1 must be large enough.  Return s1.
 */

volatile char *strcpyv(s1, s2)
register volatile char *s1, *s2;
{
	volatile char *os1;
	os1 = s1;
	while ( (*s1++ = *s2++) )
		;
	return(os1);
}
/*
 * Replacement "strcmp" routine - to retain parameter volatile attributes.
 * (borrowed from the GCC library)
 *
 * Compare strings:  s1>s2: >0  s1==s2: 0  s1<s2: <0
 */

int strcmpv(s1, s2)
register volatile char *s1, *s2;
{
	while (*s1 == *s2++)
		if (*s1++=='\0')
			return(0);
	return(*s1 - *--s2);
}

/*
 *  nosyslck
 *
 *  Turn off image installed syslck - BUT if user already had syslck,
 *  then leave it on (as there is no change in user functionality)
 */

void nosyslck(void)
{
  unsigned int authprivs[2], msyslck[2] ;
  int item = JPI$_PROCPRIV;

  msyslck[0] = PRV$M_SYSLCK;
  msyslck[1] = 0;
  cache_signal(lib$getjpi(&item,0,0,&authprivs,0,0));
  if (!(authprivs[0] & PRV$M_SYSLCK)) cache_signal(sys$setprv(0,msyslck,0,0));
}

/*
 *  syslck
 *
 *  Turn syslck on as a temp priv - assumes image was installed with SYSLCK
 */

void syslck(void)
{
  unsigned int msyslck[2];

  msyslck[0] = PRV$M_SYSLCK;
  msyslck[1] = 0;
  cache_signal(sys$setprv(1,msyslck,0,0));
}

/* Check to see if logical name NEWS_CACHE has been defined */
int cache_check_lnm(void)
{
  static int check_lnm = -1;
  int lnm_length;
  char exec_mode = PSL$C_EXEC,
       lnm_value[256];
  unsigned int trnlist[4]={(LNM$_STRING << 16) + LNM$C_NAMLENGTH,
			   ((unsigned int) &lnm_value),
			   ((unsigned int) &lnm_length), 0};

  if (check_lnm == -1) {
      $DESCRIPTOR_CONST( tabnam_desc, "LNM$SYSTEM" );
      $DESCRIPTOR_CONST( lognam_desc, CACHE_LOGNAME );

      check_lnm = sys$trnlnm( 0, &tabnam_desc, &lognam_desc, &exec_mode,
	    &trnlist );
  }
  return ( check_lnm & 1 );
}

/* set up lock for cacheing */
void cache_init_lock(void)
{
	static int cache_lock_init = 0;

	if (!cache_lock_init) {

	    int status;
	    $DESCRIPTOR_CONST( lock_desc, CACHE_LOCKNAME );

	    cache_lock_init = 1;
	    syslck();
	    status = sys$enqw( 0, LCK$K_NLMODE, &cache_lksb, LCK$M_SYSTEM,
		&lock_desc, 0, 0, 0, 0, 0, 0 );
	    /*nosyslck();*/
	    cache_signal(status);
	}
}

/* get exclusive lock for reading/updating cache */
void cache_get_lock( cache )
	CACHE *cache;
{
	int status;

 	cache_init_lock();
	status = sys$enqw( 0, LCK$K_EXMODE, &cache_lksb, LCK$M_CONVERT,
	    0, 0, 0, 0, 0, 0, 0 );
 	cache_signal( status );
	if (cache != NULL) {
	    if (cache->cache_lock_check == 1) { /* double check locking */
		/* something has gone seriously wrong with the locking! */
		open_log_file( CACHE_LOG_FILENAME );
		write_log_file( "Lock check variable already set to 1." );
		cache_free_lock(cache);
	    }
	    cache->cache_lock_check = 1;
	}
}

/* free up exclusive lock for reading/updating cache */
void cache_free_lock( cache )
	CACHE *cache;
{
	int status;

	cache->cache_lock_check = 0;
	cache_init_lock();
	status = sys$enqw( 0, LCK$K_NLMODE, &cache_lksb, LCK$M_CONVERT,
	    0, 0, 0, 0, 0, 0, 0 );
 	cache_signal( status );
}

/* initialize the contents of the cache */
void cache_initialize_cache( cache )
	CACHE *cache;
{
	int i;

	cache->cache_cid = 1; /* index 0 not used */
	cache->cache_found = 0;
	cache->cache_lost = 0;
	cache->cache_lock_check = 0;
	sys$gettim( cache->cache_zero_date );
	cache->cache_reset_date [1] = cache->cache_zero_date [1];
	cache->cache_reset_date [2] = cache->cache_zero_date [2];

	for (i = 1; i < CACHE_HASH_SIZE; i++)
	    cache->cache_hash_list [i] = 0;
	for (i = 1; i < CACHE_IDS_SIZE; i++) {
	    cache->cache_ids [i].cache_fptr = 0;
	    cache->cache_ids [i].cache_bptr = 0;
	    cache->cache_ids [i].cache_hval = 0;
	}
}

/* hash the id */
void cache_hash(id)
	CACHE_ID id;
{
	    printf("hash value = %lX\n", hash_string( strlen( id ), id ));
}

/* write out what is at the current id to the log file */
void cache_debug_cache_id( cache, msg )
	CACHE *cache;
	int msg;
{
	unsigned int cid;

	cid = cache->cache_cid;
	{
	    char message [128];

	    open_log_file( CACHE_LOG_FILENAME );
	    sprintf( message, "DEBUG %d: Hval = %d, Bptr = %d, Fptr = %d", msg,
		cache->cache_ids [cid].cache_hval,
		cache->cache_ids [cid].cache_bptr,
		cache->cache_ids [cid].cache_fptr );
	    write_log_file( message );
	    sprintf( message, "DEBUG 3: Fptr = %d",
		cache->cache_ids [cid].cache_fptr );
	    write_log_file( message );
	}
}

/* delete oldest id to free up a space for insertion */
int cache_free_cache( cache )
	CACHE *cache;
{
	int fptr, bptr, cid;
	unsigned short hval;

	cid = cache->cache_cid;
	fptr = cache->cache_ids [cid].cache_fptr;
	bptr = cache->cache_ids [cid].cache_bptr;
	hval = cache->cache_ids [cid].cache_hval;

	/* delete the link */
	if (bptr == 0) {
	  if (hval == 0) { /* empty list */
	  }
	  else { /* at head of list */
	    /* no need to check for fptr == 0 as cache_ids [0] not used */
	    cache->cache_ids [fptr].cache_bptr = 0;
	    cache->cache_hash_list [hval] = fptr;
	  }
	}
	else if (fptr == 0) { /* at tail of list */
	    cache->cache_ids [bptr].cache_fptr = 0;
	}
	else { /* at middle of list */
	    cache->cache_ids [fptr].cache_bptr = bptr;
	    cache->cache_ids [bptr].cache_fptr = fptr;
	}

	/* move along to next id - it now becomes the "oldest" */
	cache->cache_cid += 1;
	if (cache->cache_cid >= CACHE_IDS_SIZE)
	    cache->cache_cid = 1; /* wrap around */
	return( cid );
}

/* insert the id into the cache */
void cache_insert_cache( id, hvalue, hval, cache )
	CACHE_ID id;
	unsigned int hvalue;
	unsigned short hval;
	CACHE *cache;
{
	int ptr, cid;

	/* delete oldest id to free up space for insertion */
	cid = cache_free_cache( cache );

	/* replace existing id */
	strcpyv( cache->cache_ids [cid].cache_id, id );
	cache->cache_ids [cid].cache_hval = hval;
	cache->cache_ids [cid].cache_hvalue = hvalue;

	/* insert at front of linked list */
	ptr = cache->cache_hash_list [hval];
	cache->cache_ids [cid].cache_fptr = ptr;
	cache->cache_ids [cid].cache_bptr = 0;
	cache->cache_ids [ptr].cache_bptr = cid;
	/* no need to check for ptr == 0 as cache_ids [0] not used */
	cache->cache_hash_list [hval] = cid;
}

/* map in the contents of the cache */
CACHE *cache_map_cache(void)
{
	static int cache_map_init = 0;
	static int cache_map_addr = 0;
	static struct FAB cache_fab;
	int file_status, status;
	int inadr[2] = {0,0}, retadr[2];

	if (!cache_map_init) {

	    $DESCRIPTOR_CONST( cache_section_desc, CACHE_SECTION_NAME );
    
	    cache_get_lock(NULL);
	    cache_map_init = 1;
            cache_fab = cc$rms_fab;
	    cache_fab.fab$l_fna = CACHE_FILENAME;
	    cache_fab.fab$b_fns = strlen(cache_fab.fab$l_fna);
	    cache_fab.fab$b_fac = FAB$M_GET|FAB$M_PUT;
	    cache_fab.fab$b_shr = FAB$M_GET|FAB$M_PUT|FAB$M_UPI;
	    cache_fab.fab$l_fop |= FAB$M_UFO|FAB$M_CIF|FAB$M_CBT;
	    cache_fab.fab$l_alq = (sizeof(CACHE)+511)/512;
	    file_status = sys$create(&cache_fab);
	    cache_signal( file_status );
	    status = sys$crmpsc(inadr, retadr, 0, SEC$M_SYSGBL|SEC$M_GBL|SEC$M_WRT|SEC$M_EXPREG,
				&cache_section_desc, 0, 0, cache_fab.fab$l_stv,
				0, 0, 0, 0);
	    cache_signal( status );
	    cache_saddr = retadr[0];
	    cache_faddr = retadr[1];
	    cache_map_addr = cache_saddr;
	    if (file_status == RMS$_CREATED)
		cache_initialize_cache( (CACHE *)cache_map_addr );
	    /* printf( "start address = %X, end address = %X\n",
			cache_saddr, cache_faddr ); */
	    cache_free_lock( (CACHE *)cache_map_addr );
	}
	return( (CACHE *) cache_map_addr );
}

/* lookup the id in the cache */
int cache_lookup_cache( id, cache, update, debug)
	CACHE_ID id;
	CACHE *cache;
	int update, debug;
{
	int ptr;
	int found = 0;
	int paranoid = 0;
	unsigned int hvalue;
	unsigned short hval;

 	hvalue = hash_string( strlen( id ), id );
	hval = (short) ( hvalue % (CACHE_HASH_SIZE-1) + 1 );

	cache_get_lock( cache );
	/* search hash link list for id */
	ptr = cache->cache_hash_list [hval];
	paranoid = 0; /* counter to prevent infinite looping */
	while ((ptr != 0) && (!found) && (paranoid < 100)) {
	    paranoid += 1;
    	    if (hvalue == cache->cache_ids [ptr].cache_hvalue) {
  	  	found = (strcmpv(id, cache->cache_ids [ptr].cache_id ) == 0);
	    }
	    if (!found)
		ptr = cache->cache_ids [ptr].cache_fptr;
	  
	}
	if (update) {
	  if (!found) {
	    cache->cache_lost += 1;
	    cache_insert_cache( id, hvalue, hval, cache );
	  }
	}
	else
	  if (found)
	    cache->cache_found += 1;

	cache_free_lock( cache );
	return( found );
}

/* find the id in the cache */
int cache_find_cache( id, cache )
	CACHE_ID id;
	CACHE *cache;
{
	return( cache_lookup_cache( id, cache, 0, cache_debug ));
}

/* lookup and (if necessary) add the id into the cache */
int cache_add_cache( id, cache )
	CACHE_ID id;
	CACHE *cache;
{
	return( cache_lookup_cache( id, cache, 1, cache_debug ));
}

/* debug the cache */
int cache_debug_cache( toggle, debug )
    int toggle, debug;
{
	if (toggle) cache_debug = (!cache_debug);
	if (debug)
	  if (cache_debug)
	    printf( "Cache debug is now on.\n" );
	  else
	    printf( "Cache debug is now off.\n" );
	return( cache_debug );
}

int cache_exit_handler( s, cache )
	int s;
	CACHE *cache;
{
	int status;
	struct { int saddr, faddr; } inadr;

	cache_get_lock( cache );
	if (cache_debug)
	    printf( "Updating section...\n" );
	inadr.saddr = cache_saddr;
	inadr.faddr = cache_faddr;
	status = sys$updsecw( &inadr, 0, 0, 0, 0, 0, 0, 0 );
	if ((status != 1) && (cache_debug))
	    printf( "...error updating section %d\n", status );
	cache_free_lock( cache );
	return s;
}

void cache_declare_exit_handler( cache )
	CACHE *cache;
{
	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) cache_exit_handler;
	exitblk.exit_l_argc = 2;
	exitblk.exit_a_stat = (unsigned int) &exit_status_addr;
	exitblk.exit_l_arg1 = (unsigned int) cache;
	sys$dclexh( &exitblk );
}

/* Map the cache and check the cache for the id, inserting it if necessary.
 * Call CACHE_CHECK not CACHE_CHECK1 (which is used for trapping unforseen
 * errors). */
int cache_check1(id, add)
  char *id; int add;
{
 	static CACHE *cache;
	int lib$sig_to_ret();
	static int cache_check1_init = 0;

	/* Be paranoid - trap all errors */
	lib$establish( lib$sig_to_ret );

	if ( cache_check1_init == 0 ) {
		cache = cache_map_cache();
	  	/* force update section to file on exit */
		cache_declare_exit_handler( cache );
		cache_check1_init = 1;
	}
    
	return( cache_lookup_cache( id, cache, add, 0 ));
}

int cache_check(id, add)
/* Return 1 if id found in cache; 0 otherwise */
  char *id; int add;
{
	int status;
      	char message [128];
      	    
	if (!cache_check_lnm()) return 0;

	status = cache_check1(id, add);
	if ((status != 1) && (status != 0)) {
	    open_log_file( CACHE_LOG_FILENAME );
	    sprintf( message, "Invalid status returned by CACHE_CHECK = %d",
		status );
	    write_log_file( message );
	}
	return((status == 1));
}
