/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: prbrk.c 1.2 89/06/23 $"

/*
 * prbrk.c
 *	Parallel run-time basic private memory allocator.
 *
 * Replaces libc's sbrk(), brk(), and others necessary for the emulation.
 *
 * TODO:
 *	When mmap() handles "extended" u_mmap[]'s, can remove the EMFILE code.
 *	When mmap() supports private-ZFOD, much of this simplifies.
 */

#include <sys/types.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/mman.h>

#include "parallel.h"
#include "parc.h"

/*
 * sbrk.c
 *	A real C-procedure to do sbrk.
 *
 * This does the same function as the VAX interface; in particular,
 * there is no test for negative incr.
 *
 * Uses static versions of _curbrk to avoid collision with
 * libc version, and since mmap() modifies _curbrk.  Keeps
 * _minbrk public, since profiling startoff ref's it.
 *
 * Derived from DYNIX libc sbrk.c (1.4 85/02/22)
 */

extern	char	end;

#ifndef lint
static	char	*_curbrk = (char *)&end;	/* used by brk() */
#else
extern char 	*_curbrk;
#endif
	char	*_minbrk = (char *)&end;	/* used by brk() */

char *
sbrk(incr)
	int	incr;
{
	char	*val;

	val = _curbrk;
	if (incr) {			 	/* optimization for sbrk(0) */
		if (brk(_curbrk + incr) < 0)
			return((char *) -1);
	}
	return(val);
}

/*
 * brk()
 *	Add private address space.
 */

brk(newbrk)
	register char	*newbrk;
{
	register char	*cur;			/* actual brk (rounded) */
	register char	*new;			/* requested brk (rounded) */
	extern	shared char sshdata;		/* start of shared stuff */

	if (newbrk < _minbrk)			/* keep original data */
		newbrk = _minbrk;

	if (newbrk > &sshdata) {		/* run into shared space */
		errno = ENOMEM;
		return(-1);
	}

	cur = PGRND(_curbrk);
	new = PGRND(newbrk);

	/*
	 * Three cases: grow, shrink, or no actual change.
	 */

	if (new > cur) {					/* grow */
		if (new_space(cur, new-cur) < 0)
			return(-1);
	} else if (new < cur) {					/* shrink */
		if (loose_space(new, cur-new) < 0)
			return(-1);
	}

	_curbrk = newbrk;
	return(0);
}

/* 
 * The remainder of this file implements necessary primitives to
 * support brk().  If MMAPZFOD, these are easy (and should be folded
 * into the above).  Until then, more fuss is necesary.
 */

#ifdef	MAP_ZEROFILL

/* 
 * new_space()
 *	Create new private space.  Simply ask mmap for it.
 *
 * Arguments are system page-size aligned.
 */

static
new_space(vaddr, size)
	char	*vaddr;
	int	size;
{
	if (ZFOD_MMAP(vaddr, size) < 0) {
		errno = ENOMEM;
		return(-1);
	}
	return(0);
}

/*
 * loose_space()
 *	Loose some address space.  Munmap() does wonders.
 *
 * Arguments are system page-size aligned.
 */

static
loose_space(vaddr, size)
	char	*vaddr;
	int	size;
{
	return(munmap(vaddr, size));
}

#else	/* must emulate private ZFOD */

/* 
 * new_space()
 *	Create new private space.
 *
 * Must emulate ZFOD space creation using mmap() on a "private" file,
 * until mmap() directly supports private ZFOD space creation.
 *
 * Arguments are system page-size aligned.
 */

static	char	priv_file[] = " private junk XXXXXX";
static	int	priv_fd = -1;			/* mapping file */

static
new_space(cur, size)
	register char	*cur;
	int	size;
{
	register char	*first = PGRND(&end);

	if (priv_fd < 0) {
		priv_fd = _mkuntmp(priv_file);
		if (priv_fd < 0) {
			errno = ENOMEM;
			return(-1);
		}
	}

	/*
	 * Map more space.  This is cloned from lshbrk().
	 */

	if (MMAP(priv_fd, cur, size, cur - first) < 0) {
#ifdef	CONCAT
		errno = ENOMEM;
		return(-1);
#else
		if (errno == EMFILE) {
			/*
			 * Make room by deleting all "private" mapped
			 * stuff.  Could be more efficient, ignore
			 * for now (just an emulation).
			 */
			(void) munmap(first, cur - first);
			if (MMAP(priv_fd, first, (cur - first) + size, 0) < 0) {
				if (MMAP(priv_fd, first, (cur - first), 0) < 0){
					perror("brk fail");
					abort();
				}
				errno = ENOMEM;
				return(-1);
			}
		} else {
			errno = ENOMEM;
			return(-1);
		}
#endif
	}

	/*
	 * Successfully mapped.
	 */

	return(0);
}

/*
 * loose_space()
 *	Loose some address space.  Munmap() does wonders.
 *
 * Caller guarantees won't try to delete below &end.
 *
 * Arguments are system page-size aligned.
 */

/*ARGSUSED*/
static
loose_space(vaddr, size)
	char	*vaddr;
	int	size;
{
	register char	*first = PGRND(&end);
	register char	*cur = PGRND(_curbrk);

	/*
	 * If all private space going away, reset state.
	 */

	if (vaddr == first) {
		(void) close(priv_fd);		/* ==> unmap */
		priv_fd = -1;
		return(0);
	}

	/*
	 * In order to guarantee subsequent grow gets ZFOD space, need
	 * to unmap file, truncate, map appropriate part.
	 *
	 * Would be more convenient if system allowed truncate of
	 * mapped file...
	 */

	(void) munmap(first, cur - first);	/* unmap all */
	if (ftruncate(priv_fd, vaddr - first) < 0) {
		perror("ftruncate -- runtime error");
		abort();			/* shouldn't happen */
	}

	if (MMAP(priv_fd, first, vaddr - first, 0) < 0) {
		perror("mmap -- runtime error");
		abort();			/* shouldn't happen */
	}

	return(0);
}

/*
 * fork(), vfork()
 *	Handle duplication of private space in emulation.
 *
 * Write any private expanded space into new file to be used by child.
 * Can't just fork, since would share parent's "private" memory!
 * (gee, never thought too much sharing would happen!  ;-)
 *
 * Only need this until MMAPZFOD.
 */

#define	_MMAP(fd,va,sz,pos)	\
		_mmap(va, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, pos)

fork()
{
	register char	*first = PGRND(&end);
	register char	*cur = PGRND(_curbrk);
	register int	child_priv_fd = -1;
	int	pid;

#ifdef MCOUNT
	moncontrol(0);
#endif
	if (priv_fd >= 0) {
		child_priv_fd = _mkuntmp(priv_file);
		if (child_priv_fd < 0) {
			errno = ENOMEM;
			return(-1);
		}
		if (write(child_priv_fd, first, cur-first) != cur-first) {
			(void) close(child_priv_fd);
			errno = ENOMEM;
			return(-1);
		}
	}

	pid = sysfork();

	if (pid > 0) {					/* parent */
		(void) close(child_priv_fd);
#ifdef MCOUNT
		moncontrol(1);
#endif
		return(pid);
	}

	if (pid < 0) {					/* error */
		if (child_priv_fd >= 0)
			(void) close(child_priv_fd);	/* no errors possible */
		return(-1);
	}

	/*
	 * Child.  Must now mmap child's copy of stuff to parent.
	 */

	(void) close(priv_fd);				/* ==> unmap */
	if ((priv_fd = child_priv_fd) != -1) {
		if (_MMAP(priv_fd, first, cur - first, 0) < 0) {
			perror("fork mmap -- runtime error");
			abort();			/* shouldn't happen */
		}
	}
#ifdef MCOUNT
	moncontrol(1);
#endif

	return(0);
}

vfork() { return(fork()); }

/*
 * sysfork()
 *	Internal kludge interface to real fork syscall.
 *
 * Code cloned from src/lib/libc/topaz/sys/fork.c
 */

static
sysfork()
{
	asm(" 	movzbd	2,r0");
	asm("	svc");
	asm("	bfc	0f");
	asm("	bsr	cerror");
	asm("	br	9f");
	asm("0: cmpqd	0,r1");			/* child has ppid in r1 */
	asm("	beq	9f");
	asm("	movqd	0,r0");
	asm("9:");
#ifdef	lint
	return(0);
#endif
}

#endif
