/*  File: blocksub.c
 *  Author: Jean Thierry-Mieg (mieg@mrc-lmb.cam.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1992
 * -------------------------------------------------------------------
 * Acedb 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
 * of the License, 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.
 * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description:
 * Exported functions:
 * HISTORY:
 * Last edited: Nov  9 13:54 1999 (fw)
 *	-	everywhere a "if(!(*q)->addr)" test is performed within
 *		a CHECKBLOCK #define, use   *q = KEY2LEX(key) to ensure
 *		that "q" is not a dangling pointer reference (see objcache.c too)
 * * Feb 27 13:19 1996 (srk)
 * Last edited: Feb 27 13:19 1996 (srk)
 * * Jan 22 10:24 1992 (mieg): 
    Session object should not be shared.
    Session and bat obj should be modified en place.
 * Created: Wed Jan 22 10:24:47 1992 (mieg)
 *-------------------------------------------------------------------
 */

/* $Id: blocksub.c,v 1.39 2003/06/13 01:43:49 mieg Exp $ */

#ifdef ACEDB5
void compilersDoNotLike2 (void *emptyFiles) {return ; }
#else  /* only used before ACEDB 5 */

      /***************************************************************/
      /**  File blocksubs.c :                                       **/
      /**  Handles the cache of the ACeDB program.                  **/
      /***************************************************************/
      /***************************************************************/
      /*                                                             */
      /*  Twelve routines are public :  avail,  rewrite, reallocate, */
      /*   show, mark&save, init,  get/write, pinn/unpinn.           */
      /*              blockfriend, blockNext.                        */
      /*      The cache is an area which contains BLOCKMAX blocks.   */
      /*                                                             */
      /* The cache has two piles controlled by get and pinn/unpinn : */
      /*   1 : the "usedblocks", a LILO stack, on top of which each  */
      /* block is put when  "get" (implying a load the first time    */
      /* or a pop, or a new). These blocks are lost, LILO way, when  */
      /* the cache is full, automatically.                           */
      /*   2 : the "pinnedblocks". Those blocks stay in the cache.   */
      /* This pile is controlled explicitely by blockpinn and unpinn */
      /* Pinns implies a get but unpinn just moves back the block to */
      /* the "usedblocks" list.                                      */
      /*                                                             */
      /* If an object is too large to fit in a single block it       */
      /* is written in a set of blocks linked to the rigth an        */
      /* handled as a whole by the stacks .                          */
      /*                                                             */
      /* blockmark specifies that a block has been modified.         */
      /* blockwrite forces a copy of block to disk, if marked.       */
      /* It has  no action on the cache.                             */
      /* It can be called explicitely. It is invoked by save.        */
      /* blockSave saves the marked block to disk. It is called      */
      /* from the main menu.                                         */
      /*                                                             */
      /* Blockshow is for debugging. Blockavail gives the status.    */
      /*                                                             */
      /* The actual disk handling is done in the separate file       */
      /* disksubs.c invoked only by the static routines load/unload. */
      /*                                                             */
      /*         R.Durbin & J.Thierry-Mieg.                          */
      /*                    last modified    7/3/1991 by JTM.        */
      /*                                                             */
      /***************************************************************/


/* n.b. The TURBOC compiler warns on each use of KEY2LEX(k) that there is
a possible loss of significant digits, which I dont understand since
an LEXP lxi=KEYTOLEX(k) is by itself a huge pointer which can hold
a long, KEY2LEX is defined in ACEDB.h */

#include "acedb.h"
#include "lex_bl_.h"
#include "disk_.h"   /* used by block load/unload*, disk_ used by blockOrder */
#include "lex.h"     /* used by blockshow */
#include "block.h"
#include "key.h"     /* used by blockshow */
#include "disk.h"    /* for function prototypes */
#include "chrono.h"
#include "session.h"
#include "whooks/sysclass.h" 
#include "pick.h"
#include "dbpath.h"

    /*The following are default values. 
      They are overridden by cachesize.wrm in getCacheSize() below 
      */
static int BLOCKMAX =  1000 ; /* default number of blocks in the cache */
int MAXKNOWNCACHE =  1000 ;    /* default number of cacheEntries in the cache, used by objcache.c */
extern int INITIALDISKSIZE ;

static void   blockload   (LEXP *q, ALLOCP v, KEY key);
static ALLOCP blockgrab   (KEY key) ;
static void   blockfree   (ALLOCP v);
static void   blockunload (ALLOCP v);
static BOOL   blockisload (ALLOCP v, KEY k, LEXP *q);
static void   blockisunld (ALLOCP v);
static int    blocknewdsk (BP p);
static BOOL   blockNextKey (void **iterator, BP p, KEY *kp) ;
static ALLOCP freeblocks = 0 ,
              pinnedblocks = 0 ,
              usedbltop = 0 ,
              usedblend = 0 ;

/*
#define TESTBLOCK
#define CHECKBLOCK
*/

extern void invokeDebugger(void) ;
static int npinned = 0, nfree = 0, nused = 0 ;

/**************************************************************/
static int  nc1r = 0, nc1w = 0, ncc1r = 0, ncc1w = 0 ;

void blockStatus(int *nc1rp, int* nc1wp, int *ncc1rp, int* ncc1wp)
{
  *nc1rp = nc1r ; *nc1wp = nc1w ; *ncc1rp = ncc1r ; *ncc1wp = ncc1w ;
}

/**************************************************************/

BOOL blockCheck (void)
{ char *vp, *vp0 = 0 ;
  ALLOCP u,v ;
  static Associator table = 0 ;
  int m, n = 0 , nu = 0, nf = 0, np = 0 ;
  int list, badlist ;
  static char* listNames[] = {"","FREE","PINNED","USED"} ;
  BOOL result = TRUE ;

  if (table)
    assClear (table) ;
  else
    table = assCreate () ;
  
  list = 1 ; m = n ;
  for (v = freeblocks ; v ; v = v->next)
    { ++n ; nf++;
      if (!assInsert (table, v, vp0 + list))
	goto loop_found ;
      if (v->next && v->next->up != v)
	{ messout (
 "blockCheck: bad link at %d in %s list 0x%x -> 0x%x back to 0x%x",
		   n-m, listNames[list], v, v->next, v->next->up) ;
	  result = FALSE ;
	}
    }
  list = 2 ; m = n ;
  for (u = pinnedblocks ; u ; u = u->next)
    for (v = u ; v ; v = v->right)
      { ++n ; np++;
	if (!assInsert (table, v, vp0 + list))
	  goto loop_found ;
	if (v->next && v->next->up != v)
	{ messout (
 "blockCheck: bad link at %d in %s list 0x%x -> 0x%x back to 0x%x",
		   n-m, listNames[list], v, v->next, v->next->up) ;
	  result = FALSE ;
	}
      }
  list = 3 ; m = n ;
  for (u = usedbltop ; u ; u = u->next)
    for (v = u ; v ; v = v->right)
      { ++n ; nu++ ;
	if (!assInsert (table, v, vp0 + list))
	  goto loop_found ;
	if (v->next && v->next->up != v)
	{ messout (
 "blockCheck: bad link at %d in %s list 0x%x -> 0x%x back to 0x%x",
		   n-m, listNames[list], v, v->next, v->next->up) ;
	  result = FALSE ;
	}
      }

  if (n != BLOCKMAX)
    { messout ("blockCheck found %d blocks instead of %d\n"
	       "%d free, %d used %d pinned",
	       n, BLOCKMAX, nf, nu,np) ;
      result = FALSE ;
    }

  return result ;

 loop_found:
  assFind (table, v, &vp) ;
  badlist = vp - vp0 ;
  messout (
"blockCheck: loop found from 0x%x in %s list to %s list - %d'th item",
	   v, listNames[list], listNames[badlist], n) ;
  return FALSE ;
}

int blockMax (void)  /* for status */
{   
  return (BLOCKMAX * sizeof(BLOCK)) >> 10 ;           ;
}

/*************************************************************/
   /* import cache size or used defaults */

static FREEOPT cacheSizeOptions[] =
{
  {3, "Cache"},
  {'1', "CACHE1"},
  {'2', "CACHE2"},
  {'3', "DISK"},
  } ;

static void getCacheSize()
{
  int n  ; KEY option ;
  char *cp = dbPathFilName("wspec", "cachesize","wrm","r", 0) ;
  FILE *fil = cp ? filopen(cp, 0, "r") : 0 ;
    
  if (cp)
    messfree(cp);

  if(!fil)
    return ;

  while(freeread(fil))
    if (freekey(&option, cacheSizeOptions))
      switch (option) 
	{
	  case '1':
	  freenext() ; freestep ('='); freenext() ;
	  if (freeint(&n))
	    BLOCKMAX = n ;
	  else
	    messerror("In semkey.wrm, I cannot read the number after CACHE1") ;
	  break ;
	  case '2':
	  freenext() ; freestep ('='); freenext() ;
	  if (freeint(&n))
	    MAXKNOWNCACHE = n ;
	  else
	    messerror("In semkey.wrm, I cannot read the number after CACHE2") ;
	  break ;
	  case '3':
	  freenext() ; freestep ('='); freenext() ;
	  if (freeint(&n))
	    INITIALDISKSIZE = n;
	  else
	    messerror("In semkey.wrm, I cannot read the number after CACHE2") ;
	  break ;
	}
  filclose(fil) ;
}

/********************************************************************/
                      /* constructs the Cache */
                      /*Allocates m<=n blocks and their control area*/
                      /* returns the number m really allocated*/
                      /*Called at least once on entering the program*/
                         /*     Public;  Calls nothing */
void blockDoInit(BOOL extend)
{ int n ;
  void *v,*w;
  ALLOCP p;
  BP q;
  register unsigned int i;
  register int m;
  static BOOL firstpass = TRUE ;
  static int blocmax ;

  if (!extend && !firstpass)
    return ;
  
  if (firstpass)
    { freeblocks=
	pinnedblocks=usedbltop=usedblend=(ALLOCP )NULL;
      getCacheSize() ;
      firstpass = FALSE ;
      blocmax = BLOCKMAX ;
    }
  else
    BLOCKMAX += blocmax ;
  
/*  printf("blockDoInit now: %d\n", BLOCKMAX) ; */
  n = blocmax ;
  i=(unsigned int)n*(unsigned int)sizeof(struct alloc);
  v = messalloc(i) ;
  
  i=(unsigned int)n*(unsigned int)sizeof(BLOCK);
  w = messalloc(i) ;

  memset((char *)w,0,(int)i);
  m=n;p=(ALLOCP)v; q=(BP)w;
 
  while(m--) 
    { p->next=freeblocks; 
      p->up=p->right = 0;
      p->p=q;
      p->ismodified=0;
      p->ispinned=0;
      if (freeblocks) freeblocks->up=p;
      freeblocks=p;
      p++;
      q++;
      nfree++;
    }
}

void blockInit(void)
{ blockDoInit (FALSE) ;
}

/**************************************************************/

static int blockOrder(void *a, void*b)
{
 return 
 (int)(*(DISK *)
           (((ALLOCP)a)->p) - *(DISK *)(((ALLOCP)b)->p) ) ;
}

/**************************************************************/

                 /* Called when saving a session 
		  * or when there is no more freeblocks.
		  * Gathers all or a fraction of flagged blocks
		  * and saves them in order, to try better the
		  * disk performances. 
                  * Returns the number of blocks written
		  *  Public;   Calls blockunload 
		  * Called by saveAll and blockGrab
		  */

void blockSave(BOOL whole)
{
  static Array a = 0 ;
  register int i = 0 , m, n, max ;
  ALLOCP v ;
  
 /* check on calls that isWriteAccess == TRUE */
  
  chrono("blockSave");
  
  v = usedblend ;
  if (!v)
    return ;
  
  m = 0 ;
  while(m++, (m < 100) && (v = v->up)) ; /* mieg, june 2003 */
  m /= 2 ;  
  
  a = arrayReCreate(a, 200, ALLOCP) ;
  v = usedblend ; n = 0 ; i = 0 ;
  while (v)
    {
      if(v->ismodified)
	array(a,i++,ALLOCP) = v ;
      v = v->up ;
      if(!whole && n++ > m)
	break ;
    }
  arraySort(a,blockOrder); 
  max = arrayMax(a) ;
  for(i=0;i<max;i++)
    blockunload(arr(a,i,ALLOCP)) ;

  chronoReturn() ;  
}

/******************************************************************/
                          /* Gives the cache status*/
                          /*    Public;  Calls nothing*/

void blockavail(int *used,int *pinned,int *free,int *modif)
{
  register ALLOCP v, w;
  register int i;

  i=0;v=usedbltop;
    while((w=v))
      { while ((w=w->right)) i++;
	v=v->next; i++;}
    *used=i;
  i=0;v=pinnedblocks;
    while((w=v))
      { while ((w=w->right)) i++;
	v=v->next; i++;}
    *pinned=i;
  i=0;v=freeblocks;
    while(v) {v=v->next; i++;}
    *free=i;
  i=0;v = usedblend ;
    while((w=v))
      { if(v->ismodified)
	  { while ((w=w->right)) i++;
	    i++ ;
	  }
	v=v->up;
      }
    *modif=i;
}

/****************************************************************/
    /* For debugging, prints out the content of the cache
       Public;  Calls nothing
    */
#ifdef TESTBLOCK
#include "graph.h"
/**************************************************************/
             /*blockshow_keys, static, called by blockshow*/
static void blockshwks(ALLOCP v,int *line)
{ int i=1;
 KEY key=0;
 char *cp ;
 void *iter;

 if(!(v->ismodified))
 while(blockNextKey(&iter,v->p,&key))
   {if(i>=80)                
      { (*line)++;i=1; }
   cp = messprintf (" : %s ",name(key)) ;
   graphText(cp,i,*line);
   i += strlen(cp) ;
  }
 (*line)++;
}
/**************************************************************/
             /*blockshow_keys, static, called by blockshow*/
static void blockshmod(ALLOCP v,int *line)
{  int i=1;
 KEY key=0;
 char *cp ;
 void *iter;

 if(v->ismodified)
 while(blockNextKey(&iter,v->p,&key))
  {if(i>80)                   
     { (*line)++;i=1;}
   cp = messprintf (" : %s ",name(key)) ;
   graphText(cp,i,*line);
   i += strlen(cp) ;
  }
 (*line)++;
 }

#endif

/**************************************************************/

void blockshow(void)
{
#ifdef TESTBLOCK
  register ALLOCP v ;
  int line = 1 ,bused,bfree,bpinned,bmodif;
  int nc1r, nc1w, ncc1r, ncc1w ;

  graphClear() ;
  blockStatus (&nc1r, &nc1w, &ncc1r, &ncc1w) ;
  graphText(messprintf(
  "Cache1 usage : %d obj served, %d saved, %d read from disk, %d saved to disk",
               nc1r, nc1w, ncc1r, ncc1w),
            1,line++);
  line++ ;
  blockavail(&bused,&bpinned,&bfree,&bmodif);
  graphText(messprintf(
  "Cache1 present content: %d used, %d modified, %d pinned, %d freeblocks",
               bused,bmodif,bpinned,bfree),
            1,line++);

  graphText("Modified blocks  ",1,line) ; line++ ;
  for (v = usedblend ; v ; v = v->up)
    if (v->ismodified)
      blockshmod(v,&line) ;

  line += 2 ; graphText("Pinned blocks  ",1,line) ; line++ ;
  for(v = pinnedblocks ; v ; v = v->next)
    blockshwks(v,&line);

  line += 2 ; graphText("Used blocks  ",1,line) ; line++ ;
  for(v = usedbltop ; v ; v = v->next)
    blockshwks(v,&line);

  graphTextBounds (100, 5+line);
  graphRedraw() ;
#endif
}

/*****************************************************************/
      /*Returns a pinned block to the top of the usedblock line*/
                   /*      Public;   Calls nothing.  */
void blockunpinn(KEY kk)
{
 LEXP q=KEY2LEX(kk);
 ALLOCP u,v,w;

 chrono("blockunpinn");
 v=q->addr;
 if (v->ismodified) nc1w++ ; /* statistics */
 if(!v->ispinned)
   messcrash(
	     "Unbalanced pinning found in blockunpinn \n%s",
	     name(kk));
             
 if(--(v->ispinned))
   {     chronoReturn() ; return;}
             /* the block is pinned in some other way*/
 if(v->ismodified & 2)
   blockunload(v); /*a blockwrite is pending*/
   
                                    u=v->up;
                                    w=v->next;
                                    if(!u) pinnedblocks=w;
                                       else u->next=w;
                                    if (w) w->up=u;
                                    v->next=usedbltop;
                                    v->up=(ALLOCP)NULL;
                                    if (usedbltop) usedbltop->up=v;
                                    usedbltop=v;
                                    if(!usedblend) usedblend=v;

   npinned-- ; nused++ ;
  if (usedblend && usedblend->ispinned) invokeDebugger() ;

 chronoReturn() ; return ;
}
/*****************************************************************/
                      /* Clears the disk address of an object */
                      /* and reallocate a new empty block for it */
                      /* This sub does not touch the former block */
                      /*  Public ;  Called by BSstore     */
                      /*            Calls blockpinn */
void blockreallocate(KEY kk,BP *p)
{
 LEXP q=KEY2LEX(kk);
 
 chrono("blockreallocate");

 q->addr=0;
 lexSetDisk(kk, 0);
  blockpinn(kk,p) ;

 chronoReturn() ;
}

/********************************************************************/
               /*pinns a block, it must then be unpinned explicitely*/
                      /*before the memory can be recovered*/
                      /* return the number of blocks used */
                      /*    Public;   Calls blockget */
static ALLOCP activeBlock = 0 ;
int blockpinn(KEY kk,BP *p)
{
  LEXP q=KEY2LEX(kk);
  ALLOCP u,v=q->addr,w;
  int n = 0;
  chrono("blockpinn");
  nc1r++ ;
  if (!v)
    { v = blockgrab(kk) ; /* get a new block */
/* NOTE: blockgrab calls blockfree which calls blockisunld which calls */
/* blockNextKey which calls KEY2LEX, which can extend the lexi2 array and */
/* leave q as a dangling pointer. Let this be a dire warning to all those  */
/* who have sold their soul to the devil whose name is FORTRAN. Arrays and */
/* pointers make a volatile mix. Here we re-call KEY2LEX to fix the problem */
      q = KEY2LEX(kk);
    }
  else                /* get it from used list */
    { if (v->ispinned)
	messcrash("Double pinning of %s",name(kk));
      u=v->up;
      w=v->next;
      if(!u) 
	usedbltop=w;
      else
	u->next=w;
      if(w)
	w->up=u;
      else
	usedblend=u;

      if (usedblend && usedblend->ispinned) invokeDebugger() ;
      nused-- ;

    }
  v->ispinned++ ;

  npinned++ ;

  v->next = pinnedblocks;
  v->up = 0 ;
  if (pinnedblocks)
    pinnedblocks->up = v;
  pinnedblocks = v;
 
  if(!q->addr)
    {
      if(q->dlx.dk)
	{
	  blockload(&q,v,kk) ;
	  if (q->addr != v)
	    messcrash("blockload failed key %d %s",
		      kk,name(kk)) ;
	}
      else
	{
	  v->ismodified=1;
	  q->addr = v;
	  v->p->h.key = kk ;
	}
    }

  *p=v->p;
  
  activeBlock = v ;
  n = 1;        /* counts the cache blocks used by this object */
  while ((v = v->right)) n++ ;
  
  chronoReturn() ;
  return n ;
}

/*********************************************************************/

BOOL blockNext(BP *bpp)
{ 
 if (activeBlock->p != *bpp)
   messcrash("blockNext called out of context");
 if ((activeBlock = activeBlock->right))
   {
     *bpp = activeBlock->p ;
     chrono ("blockNextFound") ;
     chronoReturn() ;     return TRUE ;
   }
 return FALSE ;
}

/*********************************************************************/

BP blockGetContinuation(int nc)
{ register ALLOCP v =  activeBlock ;
  while(v->up && (v->up->right == v))
    v = v->up ;

  while(nc--)
    { 
      if(v->right)
	v = v->right ;
      else
	messcrash("Cant find continuation %d of block %s",
		  nc, name(v->p->h.key));
    }
  activeBlock = v ;
  return v->p ;
}

/*********************************************************************/

void blockSetNext(BP *bpp)
{
  if (activeBlock->p != *bpp)
    messcrash("blockSetNext called out of context");
  if (!(activeBlock->right))
    activeBlock->right = blockgrab(0) ;

  activeBlock->right->up = activeBlock ;
  activeBlock = activeBlock->right ;
  *bpp = activeBlock->p ;
}

/*********************************************************************/

void blockSetEnd(BP bp)
{ 
  ALLOCP a;
  
  if (!activeBlock || activeBlock->p != bp)
    messcrash("blockSetEnd called out of context");
  a = activeBlock->right ; /* so a not on used or pinned list */
  activeBlock->right = 0 ;
  while(a)
    { diskfree(a->p) ;
      a->up = 0;
      a->ispinned = FALSE ;
      a->ismodified = FALSE ;
      a->next = freeblocks ;
      if (freeblocks)
	freeblocks->up = a;
      freeblocks = a;
      a = a->right ;
      freeblocks->right = 0 ; /* must be final */
    }
}

/*********************************************************************/
   /* Release the whole disk space */
void blockSetEmpty(BP bp)
{ 
  ALLOCP a,u,w ;
  
  if (!activeBlock || activeBlock->p != bp)
    messcrash("blockSetEmpty called out of context");
  a = activeBlock ;
  if (!a->ispinned)
    messcrash ("block must be pinned to call blockSetEmpty") ;

  u = a->up ;		/* unhook from pinned list */
  w = a->next;
  if (!u) pinnedblocks = w ;
  else u->next = w ;
  if (w) w->up = u ;
  
  while(a)
    { diskfree(a->p) ;
      a->up = 0 ;
      a->ispinned = FALSE ;
      a->ismodified = FALSE ;
      a->next = freeblocks ;
      if (freeblocks)
	freeblocks->up = a ;
      freeblocks = a ;
      a = a->right ;
      freeblocks->right = 0 ;	/* must be final */
    }
}

/*******************************************************************/
              /* to load a block from disk   : return 0 if success */
              /*  Static; called by blockget.                      */
              /*          Calls diskblockread.      */

static void blockload(LEXP *q, ALLOCP v, KEY key)
{ 
  DISK d=(*q)->dlx.dk;
  int nn = 0 ;
  
  chrono("blockload");
  ncc1r++ ;

  /* Read the first block from q->dlx.dkess */
  diskblockread(v->p,d) ;
  /* it may be shared */
  if (!blockisload(v,key,q))
    {
      if (!sessionCheckSuperBlock())
	{
	  /* the superblock may inconsistent, it is possible that
	   * another process has ignored the readlock and purged
	   * the session that we're looking at, so our block-tables
	   * are out-of-date. The failure of this check is the usual
	   * reason for this blockload-error, so we just exit. */

	  messExit ("Sorry, while this program was running another "
		              "process updated the database.\n"
		    "// It is likely that the other process purged "
		              "the data that you are trying to read.\n"
		    "// You must re-run acedb to start with an "
			      " up-to-date version of the database.\n"
		    "// This program will now exit...");
	}

      /* it didn't exit, so the superblock is OK, 
	 so something else must have gone wrong */
      messcrash ("blockload loaded a wrong block: "
		 "%x = %s:%s in place of %x = %s:%s\n",
		 v->p->h.key, className(v->p->h.key), name(v->p->h.key), 
		 key, className(key), name(key)) ;
    }

  /* Read the continuations directly */
#ifdef CHECKBLOCK
  *q = KEY2LEX(key) ;
  if(!(*q)->addr)
    messcrash("key %d %s key2lex= %d", key, name(key), KEY2LEX(key)) ;
#endif
  
  while ((d = v->p->h.nextdisk))
    {
      v->right = blockgrab(key) ;
      v->right->up = v ;
      v = v->right ;
      nn++ ;
#ifdef CHECKBLOCK
      *q = KEY2LEX(key) ;
      if(!(*q)->addr)
	messcrash("key %d %s key2lex= %d", key, name(key), KEY2LEX(key)) ;
#endif
      /* they are private */
      diskblockread(v->p,d) ;
      if (key != v->p->h.key)
	messcrash(" blockload loaded a wrong block : %s\n",
		  name(key)) ;
    }
#ifdef CHECKBLOCK
  *q = KEY2LEX(key) ;
  if(!(*q)->addr)
    messcrash("key %d %s key2lex= %d", key, name(key), KEY2LEX(key)) ;
#endif
 chronoReturn() ; 
}

/*****************************************************************/
                     /*returns a free allocp address or NULL*/
                     /*  Static; called byblockload */
                     /*          calls blockfree */
static ALLOCP blockgrab(KEY kk)
{
  ALLOCP v;
  int recursive = 0 ;
  static KEY key ;
  chrono("blockgrab");
  
  if(recursive)
    messcrash("recursive call of blockgrab");
  recursive++ ;
  if (kk) key = kk ;
 blfree:
  if(!freeblocks && usedblend) 
    { if (isWriteAccess())
	blockSave(FALSE) ;
      blockfree(usedblend);
    }
  
  if(!freeblocks) {
    if (isWriteAccess() || pickList[class(key)].protected)
      { if (recursive++ < 2)
	  { blockDoInit (TRUE) ;
	    goto blfree ;
	  }
	else
	  messcrash("Error : blockgrab failure(%d).\n"
		"All blocks are pinned. "
		"Balance Pinn/Unpinn or Increase CACHE1 which is "
		"defined in the configuration file cachesize.wrm.",
		    BLOCKMAX
	      ) ;
      }
    else
      {
	if(!isWriteAccess())
	  { 
	    if (messQuery("The cache1 is full\n"
			  "Do you want write access ?"))
	      {
		if(sessionGainWriteAccess()) 
		  goto blfree ;	/* could get write access */
	      }
	  }
	/* still no write access */

	if(recursive++ < 2)
	  {
	    blockDoInit (TRUE) ;
	    goto blfree ;
	  }
	else
	  messExit("Block grab : the cache1 is full "
		   "(see wspec/cachesize.wrm) "
		   "and write access is denied.") ;
      }
  }
  v=freeblocks;
  freeblocks=v->next;

  nfree-- ;
  if (v->ispinned || v->ismodified) invokeDebugger() ;
  if (freeblocks && freeblocks->ispinned) invokeDebugger() ;

  if (freeblocks) 
    freeblocks->up=(ALLOCP)NULL;
  v->right = v->next= v->up = (ALLOCP)NULL;
  v->ispinned = v->ismodified = recursive = FALSE ;
  memset(v->p,0,sizeof(BLOCK)) ;
  chronoReturn() ; return(v);
}

/*******************************************************************/
                        /*frees the relevant block*/
                        /*  Static; called by blockgrab */
                        /*          calls blockunload */
static void blockfree(ALLOCP v)
{                       /*allways unpinn before free*/
  ALLOCP u,w;


  if(!isWriteAccess())
    while(v && v->ismodified)
      v = v->up ;   /* buffer the modified blocks */
  
  if(!v)
    return ;

  chrono("blockfree");
  blockunload(v) ;
  blockisunld(v);
  
  u=v->up;
  w=v->next;
  if(!u) usedbltop=w;
  else u->next=w;
  if(w) w->up=u;
  else usedblend=u;
  v->next=freeblocks;
  v->up=(ALLOCP)NULL;
  if (freeblocks) freeblocks->up=v;
  freeblocks=v;

  nused-- ; nfree++ ;
  if (usedblend && usedblend->ispinned) invokeDebugger() ;
  if (freeblocks->ispinned) invokeDebugger() ;


  while ((w=v->right))
    { 
      freeblocks = w ;

      if (freeblocks->ispinned) invokeDebugger() ;
      nfree++ ;

      v->up = w;
      v->right = 0 ;
      w->up = 0;
      w->next = v;
      v = w;
    }
 chronoReturn() ;
}
/**************************************************************/
  /* to mark a key   as modified : returns 0 */
                      /*  Public ; calls nothing */
void blockmark(KEY k)
{
  LEXP q=KEY2LEX(k);
  ALLOCP v=q->addr ;

  chrono("blockmark");
  if(!v) 
    messcrash(" blockmarking an unloaded key :%s\n",
                            name(k));
  v->ismodified |= 1;
  if(!(v->ispinned))
    messcrash("WARNING : blockmarking an unpinned block %s",name(k));

 chronoReturn() ;
}

/**************************************************************/
               /* to rewrite a block sharing the addresses*/
                      /*  Public ; calls nothing */
                      /* called by BSstore */
void blockrewrite(KEY key)
{ LEXP q = KEY2LEX(key) ;
 ALLOCP v = q->addr;

 chrono("blockrewrite");

 blockisload(v,key, &q);               /*to share memory address*/
 v->ismodified |= 1 ; /* == blockmark ,  to ensure rewriting*/
 blockunpinn(key);

 chronoReturn() ;
}

/**************************************************************/
  /* to force-write a key to disk if it is modified : return 0 if success */
                      /*  Public ;  Calls blockunload*/
void blockwrite(KEY k)
{
  LEXP q=KEY2LEX(k);
  ALLOCP v=q->addr;

  chrono("blockwrite");
  if (v)      /*else key is not in memory*/
    {
      if(v->ispinned)
	{
	  messout("%s%s",
		  "Sorry : blockwrite invoking a pinned block,",
		  "\n this is not an error, just a delay");
	  v->ismodified |=2; /*will unload as soon as unpinned*/
	}
      else
	blockunload(v) ;
    }
 chronoReturn() ;
}


/**************************************************************/
             /* to unload a block to disk   : return 0 if success */
             /*        Static, called by blockfree write and save */
             /*                calls diskalloc and diskblockwrite */
static void blockunload(ALLOCP v)
{
  ALLOCP w ;
  BP p=v->p, q;
  KEY key = 0 ; /* to init blockNextKey */
  void *iter;

  
  if(!v->ismodified) 
     return ;
  
  if (!isWriteAccess())
    return ;

  v->ismodified=0;

  chrono("blockunload");
  ncc1w++ ;

  if (!blockNextKey(&iter,v->p,&key))
    {    /* i.e. the block is really empty */
      diskfree(v->p) ;
      chronoReturn() ;
      return ;
    }
  v->p->h.key = key ;
  p = v->p ;
 
  diskalloc(p) ;
  blocknewdsk(p) ;
      
  while(v)
    { p = v->p ;
      if ((w = v->right))
	{ q = w->p ;
	  q->h.key = key ;
	  diskalloc(q) ;
	  p->h.nextdisk = q->h.disk ;
	}
      else
	p->h.nextdisk = 0 ;
  
      diskblockwrite(p) ;

      v = v->right ;
    }
  chronoReturn() ;
}

/**************************************************************/
/**************************************************************/
              /* block is loaded static, called by blockload*/
static BOOL blockisload (ALLOCP v, KEY k, LEXP *q)
{ LEXP q1 ;
  KEY key=0;
  BOOL found = FALSE ;
  void *iter;

  while(blockNextKey(&iter,v->p,&key))
	{ q1 = KEY2LEX(key) ;
	  q1->addr = v ;
	  if(key==k)
	    found = TRUE ;  /*k has been found as hoped */
	}
  *q = KEY2LEX(k) ;  /* may move because of arrayExtend(lex) */
  return found ;
}
/**************************************************************/
              /* block is unloaded static, called by blockfree*/
static void blockisunld (ALLOCP v)
{ LEXP q ;
  KEY key=0;
  BP p = v->p ;
  void *iter;

  while(blockNextKey(&iter,p,&key))
   { 
     /* KEY2LEX can cause a lex table table to be read in, which might
	spill blocks from the cache, which will cause recusive death.
	If the lex for a class is not in core, then no members of that
	clss can be in core, so we can skip the rest of this. */
     if (lexTableRead(key))
       {
	 q = KEY2LEX(key) ;
	 if (q->addr == v)
	   { q->addr = 0 ; lex2clear(key) ; }
       }
   }
}

/**************************************************************/
              /* block new disk address static, called by blockload*/
              /* returns the number of objects in the block */
static int  blocknewdsk (BP p)
{
 KEY key=0;
 register int i=0;
  DISK d = p->h.disk;
 void *iter;
/*
 if(p->h.key == _continuationKey)
   return 1 ;
*/
 while(blockNextKey(&iter,p,&key))
               {
		 i++;
                 lexSetDisk(key, d);
	       }
 return i;
}
/**************************************************************/
        /* Returns a loaded unpinned key of same class, or key.
	 * Looks only for keys of same class, already touched
	 * during the same session and still in the cache.
         * Called from bstree.c : bsTreeFindBlock 
	 *  only when key has never been saved before. 
	 */

KEY blockfriend(KEY key)
{
  register  int t = class(key);
  register ALLOCP v = usedbltop ; 
  KEY k = 0;  
  int i = 100 ; 
  void *iter=NULL;
  if(!v)
    return key ;
    
   /* Session object should not be shared.
    * Suppose you did, 
    * may be in session 8 you share session 5 block, hence 
    * session 5 would be relocated in a block allocated by session 8.
    * If you then destroyed session 8 before session 5, the 
    * session 5 obj would be lost.
    *
    * Caveat: this also holds for Bat obj if at some point shared
    * arrays were introduced.
    */
  if(class(key) == _VSession  || class(key) == _VBat)
    return key ;

  chrono("blockfriend") ;

  while (i-- && v)
    {
      if(
	 !v->right &&
	 (   v->ismodified 
	  || (v->p->h.session == thisSession.session)
	  || (v->p->h.session == 0)
          )
	 && blockNextKey(&iter,v->p,&k) && (class(k) == t)   
	 )
	{ key = k ; break ; } 
      k = 0 ;
      blockNextKey(&iter, NULL, NULL); /* Abort the iteration */
      v = v->next ;
    }
  chronoReturn() ; return(key) ;
}
/**************************************************************/
extern BOOL Bnextkey(void **iterator, BP p, KEY *key) ; /* in bsubs */

              /* block is loaded static, called by blockload*/
static BOOL blockNextKey (void **iterator, BP p, KEY *kp)
{
  if (!p) /* abort */
     {
       if (*iterator)
	 return Bnextkey(iterator,p,kp);
       else
	 return FALSE;
     }

   switch(p -> h.type )
    {
    case 'B'  :
      return Bnextkey(iterator,p,kp) ;
    default:
      *iterator = NULL;
      if (*kp)
	return FALSE ;   /* single object by block */
      return (*kp= p-> h.key) ? TRUE : FALSE ;
    }
}

#endif /* ACEDB4 */

/**************************************************************/
/**************************************************************/
