/*
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
 
    This library 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
    Library General Public License for more details.
 
    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
    MA 02111-1307, USA
 
    Copyright (C) 2004 Mark L. Woodward
    If you want support or to professionally license this library, the author
    can be reached at info@mohawksoft.com
*/ 
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/sequence.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "sys/types.h"
#include "sys/mman.h"
#include "semaphore.h"
#include "fcntl.h"
#include "unistd.h"
#include "sys/ipc.h"
#include "sys/shm.h"

typedef int MEM_KEY	;

enum type
{
	VSTR,
	VINT,
	VUNKNOWN
};

#define MEM_NAME 	0x7DEAF2
#define MEM_PERM	0x1FF
#define VAR_MAGIC	0xDEADBEEF
#define MAX_VARS	32
#define MAX_NAME	16
#define MAX_SZVAL	256

#ifndef MIN
#define MIN(a,b)	(((a)>(b))?(b):(a))
#endif

typedef struct shared_var
{
	int	inuse;			// "inuse" flag, timestamp
	int	created;		// When it was created
	int 	ndx;			// Index within system
	int	type;			// Type of data
	int	nval;			// Integer value
	char	name[MAX_NAME];		// Name of variable
	char	szval[MAX_SZVAL];	// String value
}SHAREDVAR;


typedef struct shared_memory_block
{
	int		magic;		// Magic number
	int 		nvars;		// Peak number of variables
	sem_t		lock;		// Semaphore lock
	SHAREDVAR	vars[MAX_VARS];	// Variables
}MEMBLOCK;

/* Internal functions */
static void *ShmOpen(MEM_KEY key, int size);
static void *ShmCreate(MEM_KEY key, int size);
static text *strtopg(char *string);
static char *pgtostr(char *buffer, int cb, text *text);
static int LowSetVal(char *name, int type, void *val);
static int LowAddVal(char *name, int nval);
static int LowSubVal(char *name, int nval);
static void *CreateMemSegment(MEM_KEY key);
static void *OpenMemSegment(MEM_KEY key);
static MEMBLOCK *GetMemBlock(MEM_KEY key);
static void ReleaseMemBlock(MEMBLOCK *p);
static SHAREDVAR *FindVar(MEMBLOCK *block, char *name);
static SHAREDVAR *AllocateVar(MEMBLOCK *block, char *name, int type);
static int FreeVar(MEMBLOCK *block, char *name);

/* PostgreSQL public functions */
Datum SetIntVal(PG_FUNCTION_ARGS);
Datum GetIntVal(PG_FUNCTION_ARGS);
Datum SetStrVal(PG_FUNCTION_ARGS);
Datum GetStrVal(PG_FUNCTION_ARGS);
Datum AddIntValue(PG_FUNCTION_ARGS);
Datum SubIntValue(PG_FUNCTION_ARGS);
Datum RemoveShared(PG_FUNCTION_ARGS);
Datum SwapTextValue(PG_FUNCTION_ARGS);
Datum SwapIntValue(PG_FUNCTION_ARGS);
Datum RemoveLRU(PG_FUNCTION_ARGS);
Datum GetLRU(PG_FUNCTION_ARGS);
Datum GetLastAccessShared(PG_FUNCTION_ARGS);
Datum GetCreatedShared(PG_FUNCTION_ARGS);

/* PostgreSQL function structs */
PG_FUNCTION_INFO_V1(SetIntVal);
PG_FUNCTION_INFO_V1(GetIntVal);
PG_FUNCTION_INFO_V1(SetStrVal);
PG_FUNCTION_INFO_V1(GetStrVal);
PG_FUNCTION_INFO_V1(AddIntValue);
PG_FUNCTION_INFO_V1(SubIntValue);
PG_FUNCTION_INFO_V1(RemoveShared);
PG_FUNCTION_INFO_V1(SwapTextValue);
PG_FUNCTION_INFO_V1(SwapIntValue);
PG_FUNCTION_INFO_V1(RemoveLRU);
PG_FUNCTION_INFO_V1(GetLRU);
PG_FUNCTION_INFO_V1(GetLastAccessShared);
PG_FUNCTION_INFO_V1(GetCreatedShared);

/* Static memblock pointer */
static MEMBLOCK * s_block = NULL;

/* Opens existing shared memory block */
static void *ShmOpen(MEM_KEY key, int size)
{
	int n = shmget(key, size, 0660);

	if(n == -1)
		return NULL;
	return shmat(n, NULL, SHM_R | SHM_W);
}

/* Creates a shared memory block */
static void *ShmCreate(MEM_KEY key, int size)
{
	int n = shmget(key, size, 0660 | IPC_CREAT);

	if(n == -1)
	{
		elog(ERROR, "Can't create shared memory: %s", strerror(errno));
		return NULL;
	}
	return shmat(n, NULL, SHM_R | SHM_W);
}

/* Makes a postgres text * from a C string */
static text *strtopg(char *string)
{
	int slen = strlen(string);
	int cb = slen + VARHDRSZ;
	text * t = (text *)palloc(cb);
	if(t)
	{
		VARATT_SIZEP(t)=cb;
		strncpy(VARDATA(t),string, slen);
	}
	return t;
}
/* Converts/copies postgres text to C string. */
static char *pgtostr(char *buffer, int cb, text *text)
{
	int len = VARSIZE(text)-VARHDRSZ;
	len = MIN(len,cb-1);
        memcpy(buffer, VARDATA(text), len);
        buffer[len]=0;
        return buffer;
}
/* Create and init a shared memory segment */
static void *CreateMemSegment(MEM_KEY key)
{
	void *p = ShmCreate(key, sizeof(MEMBLOCK));
	if(p)
	{
		memset(p, 0, sizeof(MEMBLOCK));
		s_block = (MEMBLOCK *)p;
		s_block->nvars=0;
		sem_init(&s_block->lock, 1, 1);
		s_block->magic = VAR_MAGIC;
	}
	return p;
}
/* Open an existing memory segment */
static void *OpenMemSegment(MEM_KEY key)
{
	void *p = ShmOpen(key, sizeof(MEMBLOCK));

	if(p)
	{
		s_block = (MEMBLOCK *)p;
		// Make sure race condition does not exist!
		while(s_block->magic != VAR_MAGIC)
			usleep(1);
	}
	return p;
}

/* Get and lock the formatted shared memory block */
static MEMBLOCK *GetMemBlock(MEM_KEY key)
{
	if(s_block == NULL)
	{
		if(!OpenMemSegment(key))
		{
			if(!CreateMemSegment(key))
			{
				return NULL;
			}
		}
	}
	sem_wait(&s_block->lock);
	return s_block;
}

/* Releases the formatted shared memory block */
static void ReleaseMemBlock(MEMBLOCK *p)
{
	sem_post(&p->lock);
}

/* Find a variable in the memory block */
static SHAREDVAR *FindVar(MEMBLOCK *block, char *name)
{
	int i;
	SHAREDVAR *pvar=NULL;

	for(i=0; i < block->nvars; i++)
	{
		SHAREDVAR *pvarT = &block->vars[i];
		if(pvarT->inuse && strcmp(pvarT->name, name)==0)
		{
			pvar = pvarT;
			break;
		}
	}
	return pvar;
}
/* Find the least recent variable in the memory block */
static SHAREDVAR *FindLRUVar(MEMBLOCK *block)
{
	int i;
	SHAREDVAR *pvar=NULL;

	for(i=0; i < block->nvars; i++)
	{
		SHAREDVAR *pvarT = &block->vars[i];
		if(!pvar)
		{
			if(pvarT->inuse)
				pvar = pvarT;
		}
		else if(pvarT->inuse && (pvarT->inuse < pvar->inuse))
		{
			pvar = pvarT;
		}
	}
	return pvar;
}

/* Allocate a variable out of the shared memory block */
static SHAREDVAR *AllocateVar(MEMBLOCK *block, char *name, int type)
{
	int i;
	SHAREDVAR *pvar=NULL;

	for(i=0; i < block->nvars; i++)
	{
		if(strcmp(block->vars[i].name, name)==0)
		{
			elog(ERROR, "Variable '%s' exists", name);
			return NULL;
		}
		if(block->vars[i].inuse == 0)
		{
			pvar = &block->vars[i];
		}
	}
	if(!pvar)
	{
		if(block->nvars >= MAX_VARS)
		{
			elog(ERROR, "Too many variables");
			return NULL;
		}
		pvar = &block->vars[block->nvars];
		pvar->ndx = block->nvars++;
	}

	strcpy(pvar->name, name);
	pvar->type = type;
	pvar->created = pvar->inuse = time(0);
	return pvar;
}
/* Mark a variable as free, clear its name */
static int FreeVar(MEMBLOCK *block, char *name)
{
	int i;

	for(i=0; i < block->nvars; i++)
	{
		if(strcmp(block->vars[i].name, name)==0)
		{
			block->vars[i].name[0] = 0;
			block->vars[i].created = block->vars[i].inuse = 0;
			break;
		}
	}
	return i;
}

/* Internal routine to allocate (if nessisary) and set a variable */
static int LowSetVal(char *name, int type, void *val)
{
	SHAREDVAR *pvar;

	MEMBLOCK *block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		return -1;
	}

	pvar = FindVar(block, name);

	if(!pvar)
		pvar = AllocateVar(block, name, type);

	if(pvar)
	{
		pvar->inuse = time(0);

		if(pvar->type != type)
		{
			elog(WARNING, "Value type being reassigned");
			pvar->type = type;
		}

		if(type == VSTR)
		{
			strncpy(pvar->szval, (char *)val, sizeof(pvar->szval));
			pvar->nval = atoi((char *)val); // You never REALLY know, you know?
		}
		else if(type == VINT)
		{
			pvar->nval = (int) val;
			snprintf(pvar->szval, sizeof(pvar->szval), "%d", (int)val);
		}
	}
	ReleaseMemBlock(block);

	return (pvar) ? pvar->ndx : -1;
}

/* Funky internal routine to find a variable and add to it. This is for
 * pseudo sequences and counters shafred across processes, but not on disk.*/
static int LowAddVal(char *name, int nval)
{
	int retval=-1;
	SHAREDVAR *pvar=NULL;
	MEMBLOCK *block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		return -1;
	}

	pvar = FindVar(block, name);

	if(!pvar)
		elog(ERROR, "Variable %s does not exist", name);
	else
	{
		pvar->inuse = time(0);
		pvar->nval += nval;
		snprintf(pvar->szval, sizeof(pvar->szval), "%d", nval);
		retval = pvar->nval;
	}
	ReleaseMemBlock(block);

	return retval;
}
/* Like LowAddVal, except used to subtract */
static int LowSubVal(char *name, int nval)
{
	int retval=-1;
	SHAREDVAR *pvar=NULL;
	MEMBLOCK *block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		return -1;
	}

	pvar = FindVar(block, name);

	if(!pvar)
		elog(ERROR, "Variable %s does not exist", name);
	else
	{
		pvar->inuse = time(0);
		pvar->nval -= nval;
		snprintf(pvar->szval, sizeof(pvar->szval), "%d", nval);
		retval = pvar->nval;
	}
	ReleaseMemBlock(block);

	return retval;
}

/* Public PG routine to set an integer value */
Datum SetIntVal(PG_FUNCTION_ARGS)
{
	char name[MAX_NAME];
	int val;

	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	val = PG_GETARG_INT32(1);

	int retval = LowSetVal(name, VINT, (void *)val);

	PG_RETURN_INT32(retval);
}

/* Public PG routine to set a text value */
Datum SetStrVal(PG_FUNCTION_ARGS)
{
	char name[MAX_NAME];
	char val[MAX_SZVAL];

	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	pgtostr(val, sizeof(val), PG_GETARG_TEXT_P(1));
	
	int retval = LowSetVal(name, VSTR, (void *)val);

	PG_RETURN_INT32(retval);
}

/* Public PG routine to get a text value */
Datum GetStrVal(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	SHAREDVAR *pvar=NULL;
	char name[MAX_NAME];
	text *retval = NULL;

	if(fcinfo->nargs != 1)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_NULL();
	}

	pvar = FindVar(block, name);

	if(pvar)
	{
		retval = strtopg(pvar->szval);
		pvar->inuse = time(0);
	}

	ReleaseMemBlock(block);

	if(retval)
	{
		PG_RETURN_NULL();
	}
	else
	{
		PG_RETURN_TEXT_P(retval);
	}
}
/* Public PG routine to get an integer value */
Datum GetIntVal(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	char name[MAX_NAME];
	SHAREDVAR * pvar;
	int retval = -1;

	if(fcinfo->nargs != 1)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));

	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_NULL();
	}

	pvar = FindVar(block, name);


	if(pvar)
	{
		pvar->inuse = time(0);
		retval = pvar->nval;
	}

	ReleaseMemBlock(block);

	PG_RETURN_INT32(retval);
}

/* See LowAddVal, but this adds a value to a variable. The variable must already exist. */
Datum AddIntValue(PG_FUNCTION_ARGS)
{
	char name[MAX_NAME];
	int val;
	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	val = PG_GETARG_INT32(1);
	val = LowAddVal(name, val);
	PG_RETURN_INT32(val);
}
/* See AddIntValue, but this subtracts a value from a variable. The variable must already exist. */
Datum SubIntValue(PG_FUNCTION_ARGS)
{
	char name[MAX_NAME];
	int val;
	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	val = PG_GETARG_INT32(1);
	val = LowSubVal(name, val);
	PG_RETURN_INT32(val);
}
Datum SwapIntValue(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	SHAREDVAR * pvar;
	char name[MAX_NAME];
	int retval=-1;
	int val;

	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	val = PG_GETARG_INT32(1);
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar = FindVar(block, name);

	if(!pvar)
	{
		elog(WARNING, "Variable not set");
	}
	else
	{
		pvar->inuse = time(0);
		retval = pvar->nval;
		pvar->nval = val;
		snprintf(pvar->szval, sizeof(pvar->szval), "%d", val);
	}
	ReleaseMemBlock(block);
	PG_RETURN_INT32(retval);
}
Datum SwapTextValue(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	SHAREDVAR * pvar;
	char name[MAX_NAME];
	char val[MAX_SZVAL];
	char retval[MAX_SZVAL];

	if(fcinfo->nargs != 2)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	pgtostr(val, sizeof(val), PG_GETARG_TEXT_P(1));
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar = FindVar(block, name);

	if(!pvar)
	{
		elog(WARNING, "Variable not set");
	}
	else
	{
		pvar->inuse = time(0);
		memcpy(retval, pvar->szval, sizeof(retval));
		memcpy(pvar->szval, val, sizeof(val));
		pvar->nval = atoi(val);
	}
	ReleaseMemBlock(block);
	PG_RETURN_TEXT_P(strtopg(retval));
}

/* Marks a variable as deleted. */
Datum RemoveShared(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	char name[MAX_NAME];
	int retval;

	if(fcinfo->nargs != 1)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	retval = FreeVar(block, name);
	ReleaseMemBlock(block);
	PG_RETURN_INT32(retval);
}
/* Marks a the LRU variable as deleted. */
Datum RemoveLRU(PG_FUNCTION_ARGS)
{
	SHAREDVAR *pvar;
	MEMBLOCK *block;
	text *retval=NULL;

	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar  = FindLRUVar(block);
	
	if(pvar)
	{
		retval = strtopg(pvar->name);
		pvar->inuse = 0;
		pvar->name[0]=0;
	}
	ReleaseMemBlock(block);
	if(retval)
	{
		PG_RETURN_TEXT_P(retval);
	}
	else
	{
		PG_RETURN_NULL();
	}
}
/* Gets the LRU variable. */
Datum GetLRU(PG_FUNCTION_ARGS)
{
	SHAREDVAR *pvar;
	MEMBLOCK *block;
	text *retval=NULL;

	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar  = FindLRUVar(block);
	
	if(pvar)
	{
		retval = strtopg(pvar->name);
	}
	ReleaseMemBlock(block);
	if(retval)
	{
		PG_RETURN_TEXT_P(retval);
	}
	else
	{
		PG_RETURN_NULL();
	}
}

/* Gets the last access time of variable */
Datum GetLastAccessShared(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	SHAREDVAR *pvar;
	char name[MAX_NAME];
	int retval = -1;

	if(fcinfo->nargs != 1)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar = FindVar(block, name);

	if(pvar)
		retval = pvar->inuse;

	ReleaseMemBlock(block);

	PG_RETURN_INT32(retval);

}
/* Gets the creation time of variable */
Datum GetCreatedShared(PG_FUNCTION_ARGS)
{
	MEMBLOCK *block;
	SHAREDVAR *pvar;
	char name[MAX_NAME];
	int retval = -1;

	if(fcinfo->nargs != 1)
	{
		elog(ERROR, "Don't know how to use %d args", fcinfo->nargs);
		PG_RETURN_INT32(-1);
	}

	pgtostr(name, sizeof(name), PG_GETARG_TEXT_P(0));
	
	block = GetMemBlock(MEM_NAME);

	if(!block)
	{
		elog(ERROR, "No shared memory block");
		PG_RETURN_INT32(-1);
	}

	pvar = FindVar(block, name);

	if(pvar)
		retval =  pvar->created;

	ReleaseMemBlock(block);

	PG_RETURN_INT32(retval);

}