#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "../include/shm.h"

void *SHMNew(int length, int *id);
void *SHMRef(int id, int *length);
void SHMUnref(void *ptr);

int SHMGetLength(int id);
int SHMGetRefs(int id);


#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#define MAX(a,b)        ((a) > (b) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)     (((x) < 0) ? ((x) * -1) : (x))


/*
 *      Allocates a new shared memory segment with the specified length
 *      in bytes.  The new shared memory segment will have exactly 1
 *	reference (attachment) count.
 *
 *      Returns the pointer and id of the new shared memory segment with
 *      one attachment to this process on success, or NULL on error.
 */
void *SHMNew(int length, int *id)
{
	void *ptr;


	if((length <= 0) || (id == NULL))
	    return(NULL);

	/* Create a new shared memory segment. */
	*id = shmget(
	    IPC_PRIVATE,
	    length,
	    IPC_CREAT | 0777
	);
	if(*id < 0)
	    return(NULL);

	/* Attach shared memory to this process, this is to increase
	 * the number of attaches to 1 on this new segment.
	 */
	ptr = shmat(*id, NULL, 0);
	if(ptr == (void *)-1)
	    return(NULL);

	/* Destroy this new shared memory segment, since we have one
	 * attach to it, this shared memory segment will not actually
	 * be destroyed until the total number of detaches reaches 0.
	 */
	shmctl(*id, IPC_RMID, NULL);

	return(ptr);
}

/*
 *      Attaches the shared memory segment specified by id to this
 *      process.
 *
 *      On success the shared memory segment pointer and length in bytes
 *	are returned, or NULL is returned on error.
 */
void *SHMRef(int id, int *length)
{
	void *ptr;
	struct shmid_ds shm_stat;


	if(length != NULL)
	    *length = 0;

	if(id < 0)
	    return(NULL);

	/* Attach shared memory to this process. */
	ptr = shmat(id, NULL, 0);
	if(ptr == (void *)-1)
	    return(NULL);

	/* Get statistics of newly attached shared memory segment. */
	if(!shmctl(id, IPC_STAT, &shm_stat))
	{
	    if(length != NULL)
		*length = shm_stat.shm_segsz;
	}

	return(ptr);
}

/*
 *      Detaches the shared memory segment specified by the pointer.
 *
 *	If the number of references (attachments) reaches 0 then the
 *	shared memory segment will be destroyed.
 */
void SHMUnref(void *ptr)
{
	if((ptr == NULL) || (ptr == (void *)-1))
	    return;

	shmdt(ptr);
}

/*
 *	Returns the length of the shared memory segment specified by id
 *	in bytes.
 */
int SHMGetLength(int id)
{
	struct shmid_ds stat_buf;

	if(id < 0)
	    return(0);

	if(shmctl(id, IPC_STAT, &stat_buf))
	    return(0);
	else
	    return(stat_buf.shm_segsz);
}

/*
 *	Returns the number of references (attachments) of the shared
 *	memory segment specified by id.
 */
int SHMGetRefs(int id)
{
	struct shmid_ds stat_buf;

	if(id < 0)
	    return(0);

	if(shmctl(id, IPC_STAT, &stat_buf))
	    return(0);
	else
	    return((int)stat_buf.shm_nattch);
}
