/*-------------------------------------------------------------------------
 *
 * shmem.c--
 *    create shared memory and initialize shared memory data structures.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/backend/storage/ipc/shmem.c,v 1.4 1995/04/07 19:03:06 andrew Exp
 *
 *-------------------------------------------------------------------------
 */
/*
 * POSTGRES processes share one or more regions of shared memory.
 * The shared memory is created by a postmaster and is "attached to"
 * by each of the backends.  The routines in this file are used for
 * allocating and binding to shared memory data structures.
 *
 * NOTES:
 *	(a) There are three kinds of shared memory data structures
 *  available to POSTGRES: fixed-size structures, queues and hash 
 *  tables.  Fixed-size structures contain things like global variables
 *  for a module and should never be allocated after the process 
 *  initialization phase.  Hash tables have a fixed maximum size, but
 *  their actual size can vary dynamically.  When entries are added
 *  to the table, more space is allocated.  Queues link data structures 
 *  that have been allocated either as fixed size structures or as hash 
 *  buckets.  Each shared data structure has a string name to identify 
 *  it (assigned in the module that declares it).
 *
 *	(b) During initialization, each module looks for its
 *  shared data structures in a hash table called the "Binding Table".
 *  If the data structure is not present, the caller can allocate
 *  a new one and initialize it.  If the data structure is present, 
 *  the caller "attaches" to the structure by initializing a pointer 
 *  in the local address space.  
 * 	The binding table has two purposes: first, it gives us
 *  a simple model of how the world looks when a backend process 
 *  initializes.  If something is present in the binding table,
 *  it is initialized.  If it is not, it is uninitialized.  Second,
 *  the binding table allows us to allocate shared memory on demand
 *  instead of trying to preallocate structures and hard-wire the
 *  sizes and locations in header files.  If you are using a lot
 *  of shared memory in a lot of different places (and changing
 *  things during development), this is important.
 *
 *	(c) memory allocation model: shared memory can never be 
 *  freed, once allocated.   Each hash table has its own free list,
 *  so hash buckets can be reused when an item is deleted.  However,
 *  if one hash table grows very large and then shrinks, its space
 *  cannot be redistributed to other tables.  We could build a simple
 *  hash bucket garbage collector if need be.  Right now, it seems
 *  unnecessary.
 *
 *   	See InitSem() in sem.c for an example of how to use the
 *  binding table.
 *
 */
#include <stdio.h>
#include <string.h>
#include "postgres.h"
#include "storage/ipc.h"
#include "storage/shmem.h"
#include "storage/spin.h"
#include "utils/hsearch.h"
#include "utils/elog.h"

/* shared memory global variables */

unsigned long  ShmemBase = 0;	/* start and end address of
				 * shared memory
				 */
static unsigned long  ShmemEnd = 0;
static unsigned long  ShmemSize = 0;	/* current size (and default) */

SPINLOCK      ShmemLock;	/* lock for shared memory allocation */

SPINLOCK      BindingLock;	/* lock for binding table access */

static unsigned long *ShmemFreeStart = NULL;	/* pointer to the OFFSET of
						 * first free shared memory
						 */
static unsigned long *ShmemBindingTabOffset = NULL; /* start of the binding
						     * table (for bootstrap)
						     */
static int  ShmemBootstrap = FALSE;	/* flag becomes true when shared mem
					 * is created by POSTMASTER
					 */

static HTAB *BindingTable = NULL;

/* ---------------------
 * ShmemBindingTabReset() - Resets the binding table to NULL....
 * useful when the postmaster destroys existing shared memory
 * and creates all new segments after a backend crash.
 * ----------------------
 */
void
ShmemBindingTabReset()
{
    BindingTable = (HTAB *)NULL;
}

/*
 *  CreateSharedRegion() --
 *
 *  This routine is called once by the postmaster to
 *  initialize the shared buffer pool.  Assume there is
 *  only one postmaster so no synchronization is necessary
 *  until after this routine completes successfully.
 *
 * key is a unique identifier for the shmem region.
 * size is the size of the region.
 */
static IpcMemoryId ShmemId;

void
ShmemCreate(unsigned int key, unsigned int size)
{
    if (size)
	ShmemSize = size;
    /* create shared mem region */
    if ((ShmemId=IpcMemoryCreate(key,ShmemSize,IPCProtection))
	==IpcMemCreationFailed) {
	elog(FATAL,"ShmemCreate: cannot create region");
	exit(1);
    }
    
    /* ShmemBootstrap is true if shared memory has been
     * created, but not yet initialized.  Only the 
     * postmaster/creator-of-all-things should have
     * this flag set.
     */
    ShmemBootstrap = TRUE;
}

/*
 *  InitShmem() -- map region into process address space
 *	and initialize shared data structures.
 *
 */
int
InitShmem(unsigned int key, unsigned int size)
{
    Pointer 	sharedRegion;
    unsigned long currFreeSpace;
    
    HASHCTL 	info;
    int 		hash_flags;
    BindingEnt *	result,item;
    bool	found;
    IpcMemoryId	shmid;
    
    /* if zero key, use default memory size */
    if (size)
	ShmemSize = size;
    
    /* default key is 0 */
    
    /* attach to shared memory region (SysV or BSD OS specific) */
    if (ShmemBootstrap && key == PrivateIPCKey)
	/* if we are running backend alone */
	shmid = ShmemId;
    else
	shmid = IpcMemoryIdGet(IPCKeyGetBufferMemoryKey(key), ShmemSize);
    sharedRegion = IpcMemoryAttach(shmid);
    if (sharedRegion == NULL) {
	elog(FATAL,"AttachSharedRegion: couldn't attach to shmem\n");
	return(FALSE);
    }
    
    /* get pointers to the dimensions of shared memory */
    ShmemBase = (unsigned long) sharedRegion;
    ShmemEnd  = (unsigned long) sharedRegion + ShmemSize;
    currFreeSpace = 0;
    
    /* First long in shared memory is the count of available space */
    ShmemFreeStart = (unsigned long *) ShmemBase;
    /* next is a shmem pointer to the binding table */
    ShmemBindingTabOffset = ShmemFreeStart + 1;
    
    currFreeSpace += 
	sizeof(ShmemFreeStart) + sizeof(ShmemBindingTabOffset);
    
    /* bootstrap initialize spin locks so we can start to use the
     * allocator and binding table.
     */
    if (! InitSpinLocks(ShmemBootstrap, IPCKeyGetSpinLockSemaphoreKey(key))) {
	return(FALSE);
    }
    
    /* We have just allocated additional space for two spinlocks.
     * Now setup the global free space count 
     */
    if (ShmemBootstrap) {
	*ShmemFreeStart = currFreeSpace;
    }
    
    /* if ShmemFreeStart is NULL, then the allocator won't work */
    Assert(*ShmemFreeStart);
    
    /* create OR attach to the shared memory binding table */
    info.keysize = BTABLE_KEYSIZE;
    info.datasize = BTABLE_DATASIZE;
    hash_flags = (HASH_ELEM);
    
    /* This will acquire the binding table lock, but not release it. */
    BindingTable = ShmemInitHash("BindingTable",
				 BTABLE_SIZE,BTABLE_SIZE,
				 &info,hash_flags);
    
    if (! BindingTable) {
	elog(FATAL,"InitShmem: couldn't initialize Binding Table");
	return(FALSE);
    }
    
    /* Now, check the binding table for an entry to the binding
     * table.  If there is an entry there, someone else created
     * the table.  Otherwise, we did and we have to initialize it.
     */
    memset(item.key, 0, BTABLE_KEYSIZE);
    strncpy(item.key,"BindingTable",BTABLE_KEYSIZE);
    
    result = (BindingEnt *) 
	hash_search(BindingTable,(char *) &item,HASH_ENTER, &found);
    
    
    if (! result ) {
	elog(FATAL,"InitShmem: corrupted binding table");
	return(FALSE);
    }
    
    if (! found) {
	/* bootstrapping shmem: we have to initialize the 
	 * binding table now.
	 */
	
	Assert(ShmemBootstrap);
	result->location = MAKE_OFFSET(BindingTable->hctl);
	*ShmemBindingTabOffset = result->location;
	result->size = BTABLE_SIZE;
	
	ShmemBootstrap = FALSE;
	
    }  else {
	Assert(! ShmemBootstrap);
    }
    /* now release the lock acquired in ShmemHashInit */
    SpinRelease (BindingLock);
    
    Assert (result->location == MAKE_OFFSET(BindingTable->hctl));
    
    return(TRUE);
}

/*
 * ShmemAlloc -- allocate word-aligned byte string from
 * 	shared memory
 *
 * Assumes ShmemLock and ShmemFreeStart are initialized.
 * Returns: real pointer to memory or NULL if we are out
 * 	of space.  Has to return a real pointer in order 
 *  	to be compatable with malloc().
 */
long *
ShmemAlloc(unsigned long size)
{
    unsigned long tmpFree;
    long *newSpace;
    
    /*
     * ensure space is word aligned.
     *
     * Word-alignment is not good enough. We have to be more
     * conservative: doubles need 8-byte alignment. (We probably only need
     * this on RISC platforms but this is not a big waste of space.) 
     *                                                - ay 12/94
     */
    if (size % sizeof(double))
	size += sizeof(double) - (size % sizeof(double));
    
    Assert(*ShmemFreeStart);
    
    SpinAcquire(ShmemLock);
    
    tmpFree = *ShmemFreeStart + size;
    if (tmpFree <= ShmemSize) {
	newSpace = (long *)MAKE_PTR(*ShmemFreeStart);
	*ShmemFreeStart += size;
    } else {
	newSpace = NULL;
    }
    
    SpinRelease(ShmemLock); 
    
    if (! newSpace) {
	elog(NOTICE,"ShmemAlloc: out of memory ");
    }
    return(newSpace);
}

/*
 * ShmemIsValid -- test if an offset refers to valid shared memory 
 * 
 * Returns TRUE if the pointer is valid.
 */
int
ShmemIsValid(unsigned long addr)
{
    return ((addr<ShmemEnd) && (addr>=ShmemBase));
}

/*
 * ShmemInitHash -- Create/Attach to and initialize 
 * 	shared memory hash table.
 *
 * Notes:
 *
 * assume caller is doing some kind of synchronization
 * so that two people dont try to create/initialize the
 * table at once.  Use SpinAlloc() to create a spinlock
 * for the structure before creating the structure itself.
 */
HTAB *
ShmemInitHash(char *name,	/* table string name for binding */
	      long init_size, 	/* initial size */
	      long max_size, 	/* max size of the table */
	      HASHCTL *infoP,	/* info about key and bucket size */
	      int hash_flags)	/* info about infoP */
{
    bool	found;
    long  *	location;
    
    /* shared memory hash tables have a fixed max size so that the
     * control structures don't try to grow.  The segbase is for
     * calculating pointer values.  The shared memory allocator
     * must be specified.
     */
    infoP->segbase = (long *) ShmemBase;
    infoP->alloc = ShmemAlloc;
    infoP->max_size = max_size;
    hash_flags |= HASH_SHARED_MEM;
    
    /* look it up in the binding table */
    location = 
	ShmemInitStruct(name,my_log2(max_size) + sizeof(HHDR),&found);
    
    /* binding table is corrupted.  Let someone else give the 
     * error message since they have more information 
     */
    if (location == NULL) {
	return(0);
    }
    
    /* it already exists, attach to it rather than allocate and
     * initialize new space 
     */
    if (found) {
	hash_flags |= HASH_ATTACH;
    }
    
    /* these structures were allocated or bound in ShmemInitStruct */
    /* control information and parameters */
    infoP->hctl = (long *) location;
    /* directory for hash lookup */
    infoP->dir = (long *) (location + sizeof(HHDR));
    
    return(hash_create(init_size, infoP, hash_flags));;
}

/*
 * ShmemPIDLookup -- lookup process data structure using process id
 *
 * Returns: TRUE if no error.  locationPtr is initialized if PID is
 *	found in the binding table.
 *
 * NOTES:
 * 	only information about success or failure is the value of
 *	locationPtr.
 */
bool
ShmemPIDLookup(int pid, SHMEM_OFFSET* locationPtr)
{
    BindingEnt *	result,item;
    bool	found;
    
    Assert (BindingTable);
    memset(item.key, 0, BTABLE_KEYSIZE);
    sprintf(item.key,"PID %d",pid);
    
    SpinAcquire(BindingLock);
    result = (BindingEnt *) 
	hash_search(BindingTable,(char *) &item, HASH_ENTER, &found);
    
    if (! result) {
	
	SpinRelease(BindingLock);
	elog(WARN,"ShmemInitPID: BindingTable corrupted");
	return(FALSE);
	
    } 
    
    if (found) {
	*locationPtr = result->location;
    } else {
	result->location = *locationPtr;
    }
    
    SpinRelease(BindingLock);
    return (TRUE);
}

/*
 * ShmemPIDDestroy -- destroy binding table entry for process
 *	using process id
 *
 * Returns: offset of the process struct in shared memory or
 *	INVALID_OFFSET if not found.
 *
 * Side Effect: removes the entry from the binding table
 */
SHMEM_OFFSET
ShmemPIDDestroy(int pid)
{
    BindingEnt *	result,item;
    bool	found;
    SHMEM_OFFSET  location;
    
    Assert(BindingTable);
    
    memset(item.key, 0, BTABLE_KEYSIZE);
    sprintf(item.key,"PID %d",pid);
    
    SpinAcquire(BindingLock);
    result = (BindingEnt *) 
	hash_search(BindingTable,(char *) &item, HASH_REMOVE, &found);
    
    if (found)
	location = result->location;
    SpinRelease(BindingLock);
    
    if (! result) {
	
	elog(WARN,"ShmemPIDDestroy: PID table corrupted");
	return(INVALID_OFFSET);
	
    } 
    
    if (found)
	return (location);
    else {
	return(INVALID_OFFSET);
    }
}

/*
 * ShmemInitStruct -- Create/attach to a structure in shared
 * 	memory.
 *
 *  This is called during initialization to find or allocate
 *     	a data structure in shared memory.  If no other processes
 *	have created the structure, this routine allocates space
 *	for it.  If it exists already, a pointer to the existing
 * 	table is returned.  
 *
 *  Returns: real pointer to the object.  FoundPtr is TRUE if
 *	the object is already in the binding table (hence, already
 *	initialized).
 */
long *
ShmemInitStruct(char *name, unsigned long size, bool *foundPtr)
{
    BindingEnt *	result,item;
    long * structPtr;

    strncpy(item.key,name,BTABLE_KEYSIZE);
    item.location = BAD_LOCATION;
    
    SpinAcquire(BindingLock);
    
    if (! BindingTable) {
	/* Assert() is a macro now. substitutes inside quotes. */
	char *strname = "BindingTable";
	
	/* If the binding table doesnt exist, we fake it.
	 *
	 * If we are creating the first binding table, then let 
	 * shmemalloc() allocate the space for a new HTAB.  Otherwise,
	 * find the old one and return that.  Notice that the
	 * BindingLock is held until the binding table has been completely
	 * initialized.
	 */
	Assert (! strcmp(name,strname)) ;
	if (ShmemBootstrap) {
	    /* in POSTMASTER/Single process */
	    
	    *foundPtr = FALSE;
	    return((long *)ShmemAlloc(size));
	    
	} else {
	    Assert (ShmemBindingTabOffset);
	    
	    *foundPtr = TRUE;
	    return((long *)MAKE_PTR(*ShmemBindingTabOffset));
	}
	
	
    } else {
	/* look it up in the bindint table */
	result = (BindingEnt *) 
	    hash_search(BindingTable,(char *) &item,HASH_ENTER, foundPtr);
    }
    
    if (! result) {
	
	SpinRelease(BindingLock);
	
	elog(WARN,"ShmemInitStruct: Binding Table corrupted");
	return(NULL);
	
    } else if (*foundPtr) {
	/*
	 * Structure is in the binding table so someone else has allocated 
	 * it already.  The size better be the same as the size we are 
	 * trying to initialize to or there is a name conflict (or worse).
	 */
	if (result->size != size) {
	    SpinRelease(BindingLock);
	    
	    elog(NOTICE,"ShmemInitStruct: BindingTable entry size is wrong");
	    /* let caller print its message too */
	    return(NULL);
	}
	structPtr = (long *)MAKE_PTR(result->location);
    } else {
	
	/* It isn't in the table yet. allocate and initialize it */
	structPtr = ShmemAlloc((long)size);
	if (! structPtr) {
	    /* out of memory */
	    Assert (BindingTable);
	    (void) hash_search(BindingTable,(char *) &item,HASH_REMOVE, foundPtr);
	    SpinRelease(BindingLock);
	    *foundPtr = FALSE;
	    
	    elog(NOTICE,"ShmemInitStruct: cannot allocate '%s'",
		 name);
	    return(NULL);
	} 
	result->size = size;
	result->location = MAKE_OFFSET(structPtr);
    }
    Assert (ShmemIsValid((unsigned long)structPtr));
    
    SpinRelease(BindingLock);
    return(structPtr);
}



