/*
 * shmpool.c
 * routines which implement a shared memory pool for many processes
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>

#include "shmpool.h"
#include "message.h"
#include "diglobals.h"
#include "func_private.h"
#include "sync.h"
#include "debug.h"

static char const   rcsid[] = "$Id: shmpool.c,v 1.13 1998/03/20 21:34:43 abakun Exp $";

#define SHMMAGIC    "shm"

struct shmpoolent  *pool = NULL;
size_t              blocksize;
int                 poolsize;
key_t               shmlockkey;	/* for synchronize */
void               *sharedmemaccess;	/* for synchronize */

#define       POOLENTRYADDR(p)    (struct shmpoolent *) (((char *) pool) + ((p) * (sizeof(struct shmpoolent) + blocksize)))
#define       POOLENTRY(addr)     ((char *) (addr) - (char *) pool) / (sizeof(struct shmpoolent) + blocksize)


/* creates the public shared memory pool taking the number of blocks and the blocksize
 * returns the shmid of the shared memory segment created
 * the passed values are also written into the global variables area
 */
func_public int     shm_create(key_t shmkey, int numblocks, size_t theblocksize)
{
    int                 shmid;
    int                 x;

    blocksize = theblocksize;
    poolsize = numblocks;

    DB(150, (stderr, "sizeof(shmpoolent) = 0x%08x (%d)\n", sizeof(struct shmpoolent), sizeof(struct shmpoolent)));
    DB(150, (stderr, "each block size    = 0x%08x (%d)\n", x = sizeof(struct shmpoolent) + blocksize, x));

    shmid = shmget(shmkey, x = numblocks * (sizeof(struct shmpoolent) + blocksize), IPC_CREAT | IPC_EXCL | DI_IPCPERMS);
    if (shmid < 0) {
	int                 s = numblocks * (sizeof(struct shmpoolent) + blocksize);
	char               *e = strerror(errno);
	DB(-1, (stderr, "creating shared memory pool (key 0x%x, size 0x%x): %s\n", shmkey, s, e));
	exit(1);
    }
    DB(150, (stderr, "total shmsize      = 0x%08x (%d)\n", x, x));

    /* attempt to lock the shared memory into ram, 
     * this'll fail if we are not the superuser, but we tried
     */
    shmctl(shmid, SHM_LOCK, 0);
    {
	char               *e = strerror(errno);
	DB(80, (stderr, "locking shared memory into ram: %s\n", e));
    }

    diglobals_set(DIG_BLOCKSIZE, &blocksize, sizeof(blocksize));
    diglobals_set(DIG_SHM_POOLSIZE, &poolsize, sizeof(poolsize));

    DB(80, (stderr, "shmid = 0x%x\n", shmid));
    diglobals_set(DIG_SHMAT_SHMID, &shmid, sizeof(shmid));

    return shmid;

}


/* this function will get all it needs using the global variable interface
 */
func_public void    shm_start()
{
    int                 shmid;
    static int          shmpool_started = 0;
    int                 i;

    if (shmpool_started)
	return;

    diglobals_get(DIG_BLOCKSIZE, &blocksize, sizeof(blocksize));
    diglobals_get(DIG_SHM_POOLSIZE, &poolsize, sizeof(poolsize));

    diglobals_get(DIG_SHM_SYNC_KEY, &shmlockkey, sizeof(shmlockkey));
    DB(80, (stderr, "sync_on(0x%x)\n", shmlockkey));
    sharedmemaccess = sync_on(shmlockkey);

    diglobals_get(DIG_SHMAT_SHMID, &shmid, sizeof(shmid));
    DB(80, (stderr, "DIG_SHMAT_SHMID = 0x%x\n", shmid));

    pool = (struct shmpoolent *) shmat(shmid, 0, 0);
    if (pool == (struct shmpoolent *) -1) {
	char               *e = strerror(errno);
	DB(-1, (stderr, "shmat(%d, 0, 0) = %p (errno = %d, %s)\n", shmid, pool, errno, e));
	/* FIXME: do we need to do this for this application? */
	shmctl(shmid, IPC_RMID, 0);	/* mark it for removal, after everyone detach */
	exit(1);
    }
    for (i = 0; i < poolsize; i++) {
	struct shmpoolent  *this = POOLENTRYADDR(i);
	this->poolblknum = i;
	strncpy(this->magic, SHMMAGIC, 4);	/* MAGIC */
    }

    shmpool_started = 1;

}


/* search the pool for an entry that has a refs count of 0,
 * add one to the ref count, and return a pointer to that entry
 */
func_private struct shmpoolent *shm_alloc_poolent()
{
    int                 p;
    struct shmpoolent  *this;

    synchronize(sharedmemaccess) {
	for (p = 0; p < poolsize; p++) {
	    this = POOLENTRYADDR(p);
	    if (strcmp(this->magic, SHMMAGIC) != 0) {	/* MAGIC */
		DB(-1, (stderr, "shmpoolent %d at %p contains invalid magic\n", p, this));
	    }
	    if (this->refs == 0) {
		this->refs++;
		break;
	    }
	}
    }

    if (p == poolsize) {
	DB(80, (stderr, "shmpool full\n"));
	return NULL;
    }
    return this;

}


func_private void   shm_release_poolent(struct shmpoolent *pe)
{

    synchronize(sharedmemaccess) {
	pe->refs--;
	if (pe->refs <= 0) {
	    memset(pe, 0, sizeof(struct shmpoolent) + blocksize);
	    pe->poolblknum = POOLENTRY(pe);
	    strncpy(pe->magic, SHMMAGIC, 4);	/* MAGIC */
	    pe->nextnum = -1;
	}
    }

}


/* unlinks the chain that starts at head
 */
func_public void    shm_unlink_chain(struct shmpoolent *head)
{
    struct shmpoolent  *next;
    char                chainstr[512];

    chainstr[0] = 0;
    while (head != NULL) {
	sprintf(chainstr, "%s->%3d", chainstr, head->poolblknum);
	next = head->next;
	shm_release_poolent(head);
	head = next;
    }
    DB(550, (stderr, "unlinked chain: %s\n", chainstr));

}



/* creates a chain of the specified length and returns a pointer to the head
 * all the links are joined by the ->next element.
 * the ref count of all the links is increased by one
 * if it is unable to create a chain of the required length, then NULL is returned
 */
func_public struct shmpoolent *shm_link_chain(int len)
{
    int                 curlen = 0;
    struct shmpoolent  *head = NULL;
    struct shmpoolent  *curr = NULL;
    struct shmpoolent  *last = NULL;

    while (curlen < len) {
	curr = shm_alloc_poolent();
	if (curr == NULL)
	    break;
	if (head == NULL)
	    head = curr;
	if (last != NULL) {
	    last->next = curr;
	    last->nextnum = POOLENTRY(curr);
	}
	curr->next = NULL;
	last = curr;
	curlen++;
    }

    if (curr == NULL) {
	DB(80, (stderr, "unable to create chain of length %d , unlinking...\n", len));
	shm_unlink_chain(head);
	return NULL;
    }
    if (1) {			/* FIXME: FOR DEBUGGING */
	char                chainstr[512];
	struct shmpoolent  *c = head;

	chainstr[0] = 0;
	while (c != NULL) {
	    sprintf(chainstr, "%s->%3d", chainstr, c->poolblknum);
	    c = c->next;
	}
	DB(550, (stderr, "linked chain  : %s\n", chainstr));

    }
    return head;

}


func_public int     shm_addr_to_offset(struct shmpoolent *addr)
{
    return POOLENTRY(addr);
}


func_public struct shmpoolent *shm_offset_to_addr(int p)
{
    return POOLENTRYADDR(p);
}



/*
 * $Log: shmpool.c,v $
 * Revision 1.13  1998/03/20 21:34:43  abakun
 * in the case where we exit() from shm_start, the exit code is now 1
 * (it was -1, which was inconsistant with the rest of the code)
 *
 * Revision 1.12  1998/03/19 23:42:58  abakun
 * added shm_addr_to_offset, which will be needed when communicating between the bd, nl and dl
 * added shm_offset_to_addr, which will be needed for the same
 *
 * Revision 1.11  1998/03/19 23:08:56  abakun
 * added inclusion of message.h
 *
 * Revision 1.10  1998/03/19 22:30:47  abakun
 * changed DISHMPERMS to DI_IPCPERMS, now defined in diglobals.h
 *
 * Revision 1.9  1998/03/19 22:25:47  abakun
 * speeld DISHMPERMS wrong in the code
 *
 * Revision 1.8  1998/03/19 22:24:36  abakun
 * moved the permissions for the created shared memory to a macro: DI_SHMPERMS
 * made it 0666, by default.  Production code should run under the same user and should be
 * 0600 or 0644
 *
 * Revision 1.7  1998/03/17 22:54:32  abakun
 * various bugs found when during testing were fixed
 * most of them were off-by-one errors concerning searching through the shmpool
 * moved calculation of the poolent's address and index to macros
 *
 * Revision 1.6  1998/03/17 02:39:21  abakun
 * was having problems as to where the diglobals were being set: fixed
 *
 * Revision 1.5  1998/03/16 22:04:33  abakun
 * changed references to sys_errlist[errno] to strerror(errno)
 *
 * Revision 1.4  1998/03/16 17:44:52  abakun
 * fixed various compilation errors and warnings
 *
 * Revision 1.3  1998/03/16 17:24:40  abakun
 * shm_start can only be called once now, if it is called more than once, it will just return
 *
 * Revision 1.2  1998/03/10 21:59:49  abakun
 * complete change from version 1.1
 * instead of using multiple pools which we can expand to, we have a static pool size
 * the functions in shmpool.c will return NULL in cases where they can't succeed, unlike
 * before, where we assumed they would always succeed (given unlimited system limits)
 *
 * Revision 1.1  1998/03/02 17:24:27  abakun
 * Initial revision
 *
 */
