/*
 * Copyright (C) 2015 Daniel-Constantin Mierla (asipto.com)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>

#include "../config.h"
#include "../globals.h"
#include "memdbg.h"
#include "shm.h"

#ifdef  SHM_MMAP

#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h> /*open*/
#include <sys/stat.h>
#include <fcntl.h>

#endif

#include "memcore.h"

#define _ROUND2TYPE(s, type) \
	(((s)+(sizeof(type)-1))&(~(sizeof(type)-1)))
#define _ROUND_LONG(s) _ROUND2TYPE(s, long)


void shm_core_destroy(void);

#ifndef SHM_MMAP
static int _shm_core_shmid = -1; /*shared memory id*/
#endif

gen_lock_t* _shm_lock=0;

static void* _shm_core_pool = (void*)-1;

sr_shm_api_t _shm_root = {0};

/**
 *
 */
int shm_getmem(void)
{

#ifdef SHM_MMAP
#ifndef USE_ANON_MMAP
	int fd;
#endif
#else
	struct shmid_ds shm_info;
#endif

#ifdef SHM_MMAP
	if (_shm_core_pool && (_shm_core_pool!=(void*)-1)){
#else
	if ((_shm_core_shmid!=-1)||(_shm_core_pool!=(void*)-1)){
#endif
		LOG(L_CRIT, "shm already initialized\n");
		return -1;
	}
	
#ifdef SHM_MMAP
#ifdef USE_ANON_MMAP
	_shm_core_pool=mmap(0, shm_mem_size, PROT_READ|PROT_WRITE,
					 MAP_ANON|MAP_SHARED, -1 ,0);
#else
	fd=open("/dev/zero", O_RDWR);
	if (fd==-1){
		LOG(L_CRIT, "could not open /dev/zero: %s\n",
				strerror(errno));
		return -1;
	}
	_shm_core_pool=mmap(0, shm_mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd ,0);
	/* close /dev/zero */
	close(fd);
#endif /* USE_ANON_MMAP */
#else
	
	_shm_core_shmid=shmget(IPC_PRIVATE, shm_mem_size , 0700);
	if (_shm_core_shmid==-1){
		LOG(L_CRIT, "could not allocate shared memory"
				" segment: %s\n", strerror(errno));
		return -1;
	}
	_shm_core_pool=shmat(_shm_core_shmid, 0, 0);
#endif
	if (_shm_core_pool==(void*)-1){
		LOG(L_CRIT, "could not attach shared memory"
				" segment: %s\n", strerror(errno));
		/* destroy segment*/
		shm_core_destroy();
		return -1;
	}
	return 0;
}

/**
 *
 */
void* shm_core_get_pool(void)
{
	int ret;
	long sz;
	long* p;
	long* end;
	
	ret=shm_getmem();
	if (ret<0)
		return NULL;

	if (shm_force_alloc){
		sz=sysconf(_SC_PAGESIZE);
		DBG("%ld bytes/page\n", sz);
		if ((sz<sizeof(*p)) || (_ROUND_LONG(sz)!=sz)){
			LOG(L_WARN, "invalid page size %ld, using 4096\n",
					sz);
			sz=4096; /* invalid page size, use 4096 */
		}
		end=_shm_core_pool+shm_mem_size-sizeof(*p);
		/* touch one word in every page */
		for(p=(long*)_ROUND_LONG((long)_shm_core_pool); p<=end;
										p=(long*)((char*)p+sz))
			*p=0; 
	}
	return _shm_core_pool;
}

/**
 * init the core lock
 */
int shm_core_lock_init(void)
{
	if (_shm_lock) {
		LM_DBG("shared memory lock initialized\n");
		return 0;
	}
	_shm_lock=shm_malloc_unsafe(sizeof(gen_lock_t)); /* skip lock_alloc, 
													   race cond*/
	if (_shm_lock==0){
		LOG(L_CRIT, "could not allocate lock\n");
		shm_core_destroy();
		return -1;
	}
	if (lock_init(_shm_lock)==0){
		LOG(L_CRIT, "could not initialize lock\n");
		shm_core_destroy();
		return -1;
	}
	return 0;
}

/**
 *
 */
void shm_core_lock_destroy(void)
{
	if (_shm_lock){
		DBG("destroying the shared memory lock\n");
		lock_destroy(_shm_lock); /* we don't need to dealloc it*/
	}
}

/**
 *
 */
void shm_core_destroy(void)
{
#ifndef SHM_MMAP
	struct shmid_ds shm_info;
#endif
	
	if (_shm_core_pool && (_shm_core_pool!=(void*)-1)) {
#ifdef SHM_MMAP
		munmap(_shm_core_pool, /* SHM_MEM_SIZE */ shm_mem_size );
#else
		shmdt(_shm_core_pool);
#endif
		_shm_core_pool=(void*)-1;
	}
#ifndef SHM_MMAP
	if (shm_shmid!=-1) {
		shmctl(shm_shmid, IPC_RMID, &shm_info);
		shm_shmid=-1;
	}
#endif
}

/**
 *
 */
int shm_init_api(sr_shm_api_t *ap)
{
	memset(&_shm_root, 0, sizeof(sr_shm_api_t));
	_shm_root.mname          = ap->mname;
	_shm_root.mem_pool       = ap->mem_pool;
	_shm_root.mem_block      = ap->mem_block;
	_shm_root.xmalloc        = ap->xmalloc;
	_shm_root.xmalloc_unsafe = ap->xmalloc_unsafe;
	_shm_root.xfree          = ap->xfree;
	_shm_root.xfree_unsafe   = ap->xfree_unsafe;
	_shm_root.xrealloc       = ap->xrealloc;
	_shm_root.xresize        = ap->xresize;
	_shm_root.xstatus        = ap->xstatus;
	_shm_root.xinfo          = ap->xinfo;
	_shm_root.xavailable     = ap->xavailable;
	_shm_root.xsums          = ap->xsums;
	_shm_root.xdestroy       = ap->xdestroy;
	_shm_root.xmodstats      = ap->xmodstats;
	_shm_root.xfmodstats     = ap->xfmodstats;
	return 0;

}

/**
 *
 */
int shm_init_manager(char *name)
{
	if(strcmp(name, "fm")==0
			|| strcmp(name, "f_malloc")==0
			|| strcmp(name, "fmalloc")==0) {
		/*fast malloc*/
		return fm_malloc_init_shm_manager();
	} else if(strcmp(name, "qm")==0
			|| strcmp(name, "q_malloc")==0
			|| strcmp(name, "qmalloc")==0) {
		/*quick malloc*/
		return qm_malloc_init_shm_manager();
	} else if(strcmp(name, "tlsf")==0
			|| strcmp(name, "tlsf_malloc")==0) {
		/*tlsf malloc*/
		return tlsf_malloc_init_shm_manager();
	} else if(strcmp(name, "sm")==0) {
		/*system malloc*/
	} else {
		/*custom malloc - module*/
	}
	return -1;
}

/**
 *
 */
void shm_destroy_manager(void)
{
	shm_core_lock_destroy();
	if(_shm_root.xdestroy) {
		_shm_root.xdestroy();
		LM_DBG("destroying memory manager: %s\n",
				(_shm_root.mname)?_shm_root.mname:"unknown");
	}
	shm_core_destroy();
}

/**
 *
 */
void shm_print_manager(void)
{
	LM_DBG("shm - using memory manager: %s\n",
			(_pkg_root.mname)?_pkg_root.mname:"unknown");
}