/***************************************************************************
 *   Copyright (C) 2004 by EVER Sp. z o.o.                                 *
 *                                                                         *
 *   This program 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.             *
 ***************************************************************************/
#include "cshmem.h"

CShMem::CShMem()
{
	shm_key=sem_key=0;
	shm_size=0;
	shm_id=sem_id=0;
	isat=isid=isopened=false;
}

CShMem::~CShMem()
{
}

int CShMem::createkey(char *pathname, int proj_id)
{
	if (pathname==NULL)
		return SHM_FAILURE;
	//error_report("pathname: %s, proj_id: %c\n", pathname, proj_id);
	shm_key=ftok(pathname, proj_id);
	if (shm_key == -1)
		return SHM_FAILURE;
		
	//error_report("shm_key: %x\n", shm_key);
	
	return SHM_SUCCESS;
}

/*
 *	create a new shared memory segment, but return 
 *	error if it already exists or couldn't be created
 */
int CShMem::create(key_t smkey, size_t smsize)
{
	if (smsize==0 || isid) {
		return SHM_FAILURE;
	}
	shm_key=smkey;
	shm_size=smsize;
	
	return create();
}

int CShMem::create(size_t smsize)
{
	if (smsize==0 || isid) {
		return SHM_FAILURE;
	}
	shm_size=smsize;
	
	return create();
}

int CShMem::create()
{
	if (isid) {
		return SHM_FAILURE;
	}
	
	shm_id=shmget(shm_key, shm_size, IPC_CREAT|IPC_EXCL|0644);
	if (shm_id<0) {
		//error_report("Shared memory segment couldn't be created/opened! (%s)\n",strerror(errno));
		return SHM_FAILURE;
	} else {
		isid=true;
		segptr=(char*)shmat(shm_id, NULL, 0);
		if((int)segptr==-1) {
			//error_report("Shared memory segment couldn't be attached! (%s)\n",strerror(errno));
			return SHM_FAILURE;
		} else
			isat=true;
	}
	if (createsem(shm_key)==SHM_FAILURE) {
		return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

/*
 * open existing segment - return error if it doesn't exists 
 */
int CShMem::open(key_t smkey, size_t smsize)
{
	if (smsize==0 || isid) {
		return SHM_FAILURE;
	}
	shm_key=smkey;
	shm_size=smsize;
	
	return open(shm_size);
}

int CShMem::open(size_t smsize)
{
	if (smsize==0 || isid) {
		return SHM_FAILURE;
	}
	shm_size=smsize;
	
	shm_id=shmget(shm_key, shm_size, 0644);
	if (shm_id<0) {
		error_report("Shared memory segment couldn't be opened! (%s)\n",strerror(errno));
		return SHM_FAILURE;
	} else {
		isid=isopened=true;
		segptr=(char*)shmat(shm_id, NULL, 0);
		if((int)segptr==-1) {
			error_report("Shared memory segment couldn't be attached! (%s)\n",strerror(errno));
			return SHM_FAILURE;
		} else
			isat=true;
	}
	if (opensem(shm_key)==SHM_FAILURE) {
		return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

int CShMem::read(void *smbuf, size_t offset, size_t len)
{
	if (!isid || !isat)
		return SHM_FAILURE;
	if (!issemlocked()) {
		if (locksem()) {
			memcpy(smbuf, segptr+offset, len);
			unlocksem();
		} else {
			return SHM_FAILURE;
		}
	}
	return SHM_SUCCESS;
}

int CShMem::read(void *smbuf, size_t offset, size_t len, bool wait)
{
	if (!isid || !isat)
		return SHM_FAILURE;
	if (!issemlocked()) {
		if (locksem(wait)) {
			memcpy(smbuf, segptr+offset, len);
			unlocksem();
		} else {
			return SHM_FAILURE;
		}
	}
	return SHM_SUCCESS;
}

void *CShMem::read(size_t offset)
{
	if (!isid || !isat)
		return NULL;
	if (!issemlocked()) {
		return (void *)(segptr+offset);
	}
	return NULL;
}

void *CShMem::read(size_t offset, bool wait)
{
#define MAX_LOCKWAIT	5
	int icnt=0;
	if (!isid || !isat)
		return NULL;
	if (wait) {
		while(issemlocked()) {
			sleep(1);
			icnt+=1;
			if (icnt>MAX_LOCKWAIT)
				return NULL;
		}
		return (void *)(segptr+offset);
	} else {
		if (!issemlocked())
			return (void *)(segptr+offset);
	}
	return NULL;
}

int CShMem::write(void *smbuf, size_t offset, size_t len)
{
	if (!isid || !isat)
		return SHM_FAILURE;
	if (!issemlocked()) {
		if (locksem()) {
			memcpy((void*)(segptr+offset), (void*)smbuf, len);
			unlocksem();
		} else
			return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

int CShMem::write(void *smbuf, size_t offset, size_t len, bool wait)
{
	if (!isid || !isat)
		return SHM_FAILURE;
	if (!issemlocked()) {
		if (locksem(wait)) {
			memcpy((void*)(segptr+offset), (void*)smbuf, len);
			unlocksem();
		} else
			return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

int CShMem::clear()
{
	if (!isid || !isat)
		return SHM_FAILURE;
	
	return SHM_SUCCESS;
}

int CShMem::close()
{
	if (isat) {
		if (shmdt((char *)segptr)==-1) {
			//error_report("Shared memory segment couldn't be detached! (%s)\n",strerror(errno));
			return SHM_FAILURE;
		}
	}
	if (isid && !isopened) {
		if (shmctl(shm_id, IPC_RMID, 0)==-1) {
			//error_report("Shared memory segment couldn't be removed! (%s)\n",strerror(errno));
			return SHM_FAILURE;
		}
		closesem();
	}
	return SHM_SUCCESS;
}

int CShMem::createsem(key_t skey)
{
	if (sem_id!=0 && sem_id!=-1)
		return SHM_FAILURE;	// already opened or created

	sem_key=skey;
	sem_id=semget(sem_key, 1, IPC_CREAT|IPC_EXCL|0644);
	if(sem_id==-1) {
		//error_report("Semaphore couldn't be created! (%s)\n",strerror(errno));
		return SHM_FAILURE;
	}
	union semun semopts;
	semopts.val=1;
	semctl(sem_id, 0, SETVAL, semopts);
	
	return SHM_SUCCESS;
}

int CShMem::opensem(key_t skey)
{
	if (sem_id!=0 && sem_id!=-1)
		return SHM_FAILURE;	// already opened or created

	sem_key=skey;
	sem_id=semget(sem_key, 0, 0644);
	if (sem_id==-1) {
		//error_report("Semaphore couldn't be opened! (%s)\n",strerror(errno));
		return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

int CShMem::closesem()
{
	if (sem_id==0 || sem_id==-1) {
		errno=EINVAL;
		return SHM_FAILURE;	// not opened nor created
	}
	union semun arg;
	if (semctl(sem_id, 1, IPC_RMID, arg)==-1) {
		return SHM_FAILURE;
	}
	return SHM_SUCCESS;
}

bool CShMem::issemlocked()
{
	if (sem_id==0 || sem_id==-1) {
		errno=EINVAL;
		return SHM_FAILURE;	// not opened nor created
	}
		
	int semval=(bool)semctl(sem_id, 0, GETVAL, 0);
	if (semval==-1) {
		//error_report("Semaphore value couldn't be read! (%s)\n",strerror(errno));
		return 0;
	}
	return (bool)(semval>0?false:true);
}

bool CShMem::locksem()
{
	struct sembuf sem_lock={
		0, -1/*decrease*/, IPC_NOWAIT
	};

	/* check if it's not locked already */
	if((bool)issemlocked()==true) {
		return false;
	}
	if((semop(sem_id, &sem_lock, 1))==-1) {
		//error_report("Semaphore couldn't be locked! (%s)\n",strerror(errno));
		return false;
	} else {
		return true;
	}
}

bool CShMem::locksem(bool wait)
{
	struct sembuf sem_lock={
		0, -1/*decrease*/, (wait?0:IPC_NOWAIT)
	};

	/* check if it's not locked already */
	if (!wait)
		if ((bool)issemlocked()==true)
			return false;

	int retval=0;
	if((semop(sem_id, &sem_lock, 1))==-1) {
		while(errno==EAGAIN) {
			usleep(250000);
			retval=semop(sem_id, &sem_lock, 1);
		}
		if (retval==-1) {
			//error_report("Semaphore couldn't be locked! (%s)\n",strerror(errno));
			return false;
		} else
			return true;
	} else {
		return true;
	}
}

bool CShMem::unlocksem()
{
	struct sembuf sem_unlock={
		0, 1/*increase*/, IPC_NOWAIT
	};

	/* check if it's not unlocked already */
	if((bool)issemlocked()==false) {
		return false;
	}
	if((semop(sem_id, &sem_unlock, 1))==-1) {
		//error_report("Semaphore couldn't be unlocked! (%s)\n",strerror(errno));
		return false;
	} else {
		return true;
	}
}

