/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1985-2009 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                  David Korn <dgk@research.att.com>                   *
*                   Phong Vo <kpv@research.att.com>                    *
*                                                                      *
***********************************************************************/
#if defined(_UWIN) && defined(_BLD_ast)

void _STUB_vmmopen(){}

#else

#include	"vmhdr.h"

#if _sys_stat
#include	<sys/stat.h>
#endif
#include	<fcntl.h>

#ifdef S_IRUSR
#define CREAT_MODE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
#else
#define CREAT_MODE	0644
#endif

#if _lib_mmap
#include	<sys/mman.h>
#else
#define mmap(a,b,c,d,e,f)	MAP_FAILED
#define munmap(a,b)		MAP_FAILED
#endif

/* Create a region to allocate based on mmap()
**
** Written by Kiem-Phong Vo (kpv@research.att.com)
*/

#ifndef MAP_FAILED
#define MAP_FAILED	(void*)(-1)
#endif

#define	MM_MAGIC	(('V'<<24) | ('M'<<16) | ('A'<<8) | ('P'))
#define MM_ROUND	(64*1024)
#define MM_START	ROUND(sizeof(Mmvm_t),ALIGN)

typedef struct _user_s
{	struct _user_s*	next;	/* link list		*/
	int		key;	/* identifying key	*/
	Void_t*		data;	/* data to be returned	*/
} User_t;

typedef struct _mmvm_s
{
	Vmulong_t	magic;	/* magic bytes		*/
	Void_t*		base;	/* base of the map	*/
	size_t		size;	/* current size		*/
	size_t		busy;	/* amount in use	*/
	size_t		round;	/* amount to round to	*/
	User_t*		user;	/* some user data	*/
} Mmvm_t;

typedef struct _mmvmdisc_s
{
	Vmdisc_t	disc;	/* Vmalloc discipline	*/
	int		fd;	/* file descriptor	*/
	Mmvm_t*		mm;	/* mmap data		*/
} Mmvmdisc_t;

#if __STD_C
static int mmvminit(char* file, Void_t* addr, size_t round, Mmvm_t* mm)
#else
static int mmvminit(file, addr, round, mm)
char*	file;	/* file to map data from	*/
Void_t*	addr;	/* desired starting address	*/
size_t	round;	/* amount to round requests 	*/
Mmvm_t*	mm;	/* to return some mapped info	*/
#endif
{
	int		fd;
	off_t		size;
	Void_t		*base;
	Mmvm_t		*hdr;

	base = NIL(Void_t*);
	if((fd = open(file, O_RDWR, CREAT_MODE)) >= 0)
	{	if((size = lseek(fd, (off_t)0, 2)) < 0)
			goto done;
		else if(size == 0)
			goto new_f;

		/* read the header */
		if(lseek(fd, (off_t)0, 0) != (off_t)0)
			goto done;
		if(read(fd, mm, sizeof(Mmvm_t)) != sizeof(Mmvm_t))
			goto done;
		if(mm->magic != MM_MAGIC || !mm->base ||
		   (off_t)mm->size != size || mm->busy > mm->size )
			goto done;
		base = (Void_t*)mmap(mm->base, mm->size, PROT_READ|PROT_WRITE,
				     MAP_FIXED|MAP_SHARED, fd, (off_t)0 );
		if(base == (Void_t*)MAP_FAILED)
			base = NIL(Void_t*);
	}
	else
	{	if((fd = open(file, O_RDWR|O_CREAT, CREAT_MODE)) < 0)
			goto done;

	new_f:	/* create an initial set of data */
		size = round;
		if(lseek(fd, size-1, 0) != (size-1) || write(fd, "", 1) != 1 )
			goto done;

		base = (Void_t*)mmap(addr, (size_t)size, PROT_READ|PROT_WRITE,
				     (addr ? MAP_FIXED : 0)|MAP_SHARED, fd, (off_t)0 );
		if(base == (Void_t*)MAP_FAILED)
			base = NIL(Void_t*);
		if(!base)
			goto done;

		/* write magic number */
		hdr = (Mmvm_t*)base;
		hdr->magic = MM_MAGIC;
		hdr->base  = base;
		hdr->size  = size;
		hdr->busy  = MM_START;
		hdr->round = round;
		hdr->user  = NIL(User_t*);
		memcpy(mm, hdr, sizeof(Mmvm_t));
	}

done:
	if(!base)
	{	if(fd >= 0)
			close(fd);
		fd = -1;
	}

	return fd;
}


#if __STD_C
static Void_t* mmvmmemory(Vmalloc_t* vm, Void_t* caddr,
			size_t csize, size_t nsize, Vmdisc_t* disc)
#else
static Void_t* mmvmmemory(vm, caddr, csize, nsize, disc)
Vmalloc_t*	vm;
Void_t*		caddr;
size_t		csize;
size_t		nsize;
Vmdisc_t*	disc;
#endif
{
	Mmvmdisc_t	*mmdc = (Mmvmdisc_t*)disc;

	if(mmdc->fd < 0 || !mmdc->mm)
		return NIL(Void_t*);

#define MMADDR(b)	((Void_t*)(((Vmuchar_t*)b) + MM_START) )
	if(caddr && caddr != MMADDR(mmdc->mm->base) )
		return NIL(Void_t*);
	if(nsize < csize)
		return NIL(Void_t*);

	if(nsize > mmdc->mm->size-MM_START)
	{	/* base and size of new map */
		caddr = mmdc->mm->base;
		csize = MM_START + nsize +
			((nsize % disc->round) < (disc->round/2) ? disc->round/2 : 0);
		csize = ROUND(csize, disc->round);

		/* make room for new space */
		if(lseek(mmdc->fd, (off_t)(csize-1), 0) != (off_t)(csize-1) ||
		   write(mmdc->fd, "", 1) != 1 )
			return NIL(Void_t*);
		
		/* remap the space */
		(void)munmap(caddr, mmdc->mm->size);
		caddr = (Void_t*)mmap(caddr, csize, PROT_READ|PROT_WRITE,
				     MAP_FIXED|MAP_SHARED, mmdc->fd, (off_t)0 );
		if(caddr == (Void_t*)MAP_FAILED)
			caddr = NIL(Void_t*);
		if(caddr)
			mmdc->mm->size = csize;
		else	/* bad problem */
		{	close(mmdc->fd);
			mmdc->fd = -1;
			mmdc->mm = NIL(Mmvm_t*);
			return NIL(Void_t*);
		}
	}

	mmdc->mm->busy = nsize+MM_START;
	return (Void_t*)(((Vmuchar_t*)mmdc->mm->base) + MM_START);
}


#if __STD_C
static int mmvmexcept(Vmalloc_t* vm, int type, Void_t* data, Vmdisc_t* disc)
#else
static int mmvmexcept(vm, type, data, disc)
Vmalloc_t*	vm;
int		type;
Void_t*		data;
Vmdisc_t*	disc;
#endif
{
	Mmvmdisc_t	*mmdc = (Mmvmdisc_t*)disc;
	Vmuchar_t	*base;

	if(type == VM_OPEN)
	{	if(mmdc->mm->busy > MM_START)
		{	base = ((Vmuchar_t*)mmdc->mm->base) + MM_START;
			*((Void_t**)data) = (Void_t*)base;
			return 1;
		}
		else	return 0;
	}
	else if(type == VM_CLOSE)
	{	(void)munmap(mmdc->mm->base, mmdc->mm->size);
		(void)close(mmdc->fd);
		vmfree(Vmheap, mmdc);
		return 1; /* freeing of mapped data is already done */
	}
	else	return 0;
}


#if __STD_C
Vmalloc_t* vmmopen(char* file, Void_t* base, size_t round)
#else
Vmalloc_t* vmmopen(file, base, round)
char*		file;	/* file mapping data from	*/
Void_t* 	base;	/* desired starting address	*/
size_t		round;	/* amount to round requests	*/
#endif
{
	Vmalloc_t	*vm;
	Mmvmdisc_t	*mmdc;
	Mmvm_t		mm;
	int		fd;

	if(!file)
		return NIL(Vmalloc_t*);

	/* set the amount to round up to on each memory request */
	GETPAGESIZE(_Vmpagesize);
	if(round < MM_ROUND)
		round = MM_ROUND;
	round = ROUND(round, _Vmpagesize);

	if((fd = mmvminit(file, base, round, &mm)) < 0)
		return NIL(Vmalloc_t*);

	if(!(mmdc = (Mmvmdisc_t*)vmalloc(Vmheap, sizeof(Mmvmdisc_t))) )
	{	close(fd);
		return NIL(Vmalloc_t*);
	}

	mmdc->disc.memoryf = mmvmmemory;
	mmdc->disc.exceptf = mmvmexcept;
	mmdc->disc.round   = mm.round;
	mmdc->fd = fd;
	mmdc->mm = (Mmvm_t*)mm.base;

	if(!(vm = vmopen(&mmdc->disc, Vmbest, VM_TRUST)) )
	{	mmvmexcept(NIL(Vmalloc_t*), VM_CLOSE, NIL(Void_t*), &mmdc->disc);	
		return NIL(Vmalloc_t*);
	}

	return vm;
}


#if __STD_C
Void_t* vmmset(Vmalloc_t* vm, int key, Void_t* data, int set)
#else
Void_t* vmmset(vm, data, key, set)
Vmalloc_t*	vm;	/* a region based on vmmmopen	*/
int		key;	/* key of data to be set	*/
Void_t*		data;	/* data to be set		*/
int		set;	/* 1 for setting, 0 for getting	*/
#endif
{
	User_t	*u;
	Mmvm_t	*mmvm = ((Mmvmdisc_t*)vm->disc)->mm;

	for(u = mmvm->user; u; u = u->next)
		if(u->key == key)
			break;
	if(!set)
		return u ? u->data : NIL(Void_t*);
	else if(u)
	{	Void_t* old = u->data;
		u->data = data;
		return old;
	}
	else if(!(u = (User_t*)vmalloc(vm, sizeof(User_t))) )
		return NIL(Void_t*);
	else
	{	u->data = data;
		u->key  = key;
		u->next = mmvm->user;
		mmvm->user = u;
		return data;
	}
}

#endif
