/* $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: kern_exec.c 1.49 90/05/03 $"

/*
 * kern_exec.c
 *	Exec system calls.
 */

/* $Log:	kern_exec.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/signal.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/map.h"
#include "sys/cmap.h"
#include "sys/user.h"
#include "sys/fcntl.h"
#include "sys/errno.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/buf.h"
#include "sys/vnode.h"
#include "sys/snode.h"
#include "sys/pathname.h"
#include "sys/vm.h"
#include "sys/seg.h"
#include "sys/file.h"
#include "sys/uio.h"
#include "sys/acct.h"
#include "sys/vfs.h"
#include "sys/filehdr.h"
#include "sys/aouthdr.h"
#include "sys/scnhdr.h"
#include "sys/dio.h"

#include "sys/reg.h"
#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/gate.h"
#include "sys/plocal.h"
#include "sys/cmn_err.h"

#include "sys/mftpr.h"

#include "sys/var.h"

/*
 * Subset of struct vattr for exece() (allows smaller stack frame).
 */

struct	exec_vattr {
	ushort		va_mode;	/* files access mode and type */
	uid_t		va_uid;		/* owner user id */
	gid_t		va_gid;		/* owner group id */
	ulong		va_size;	/* file size in bytes (quad?) */
};

/*
 * Data returned by gethead if valid object - subset of
 * section headers.
 */

struct exec_data {
	long	e_amagic;		/* magic # from aouthdr */
	long	e_entry;		/* entry point */
	ulong	e_tsize;		/* size */
	ulong	e_dsize;
	ulong	e_bsize;
	ulong	e_torg;			/* virtual address */
	ulong	e_dorg;
	ulong	e_toff;			/* offset in file */
	ulong	e_doff;
};

#define	SHSIZE		32
union exdata {
	char	ex_shell[SHSIZE];
	struct	hdr {
		struct filehdr exec_fhdr;
		struct aouthdr exec_aout;
	} ex_exec;
	struct exec_data ex_data;
};

/*
 * exec system call, with and without environments.
 */

struct execa {
	char	*fname;
	char	**argp;
	char	**envp;
};

/*
 * Structure returned by fuexarg after copying arguments to the argv
 * pages. Stackbld uses it to set up u_psargs and initialize the user
 * stack.
 */
struct uargs {
	caddr_t	va;			/* kernel virt addr of arg pages */
	int	npgs;			/* # of above pages */
	char	*argp;			/* ptr to argv in arg pages */
	int	arglen;			/* # of chars in argv */
	int	usp;			/* initial user SP */
};

#ifdef	DEBUG
int exec_debug = 0;
#endif	/* DEBUG */

exec()
{
	((struct execa *)u.u_ap)->envp = NULL;
	exece();
}

exece()
{
	register nc;
	register char *cp;
	register struct execa *uap;
	int	indir;
	uid_t   uid;
	gid_t   gid;
	char	*execnamep;
	struct	vnode	*vp;
	struct	ofile_tab *oft = NULL;
	struct	exec_vattr vattr;
	struct	pathname pn;
	char	*sharg;
	union exdata exdata;
	char	cfarg[SHSIZE];
	struct	uargs uargs;
	unsigned int ptype;

	uap = (struct execa *)u.u_ap;
	u.u_error = pn_get(uap->fname, UIOSEG_USER, &pn);
	if (u.u_error)
		return;
	u.u_error = lookuppn(&pn, FOLLOW_LINK, (struct vnode **)0, &vp);
	if (u.u_error) {
		pn_free(&pn);
		return;
	}
	indir = 0;
	uid = u.u_uid;
	gid = u.u_gid;
	if (u.u_error = exec_getattr(vp, &vattr))
		goto bad;
	if ((vp->v_vfsp->vfs_flag & VFS_NOSUID) == 0) {
		if (vattr.va_mode & VSUID)
			uid = vattr.va_uid;
		if (vattr.va_mode & VSGID)
			gid = vattr.va_gid;
	} else if (vattr.va_mode & (VSUID | VSGID)) {
		exec_nosuid(uap->fname);
	}

	/*
	 * Dynix note:
	 * Must have 'x' permission, and 'r' permission if tracing.
	 * 'r' permission on tracing avoids security hole (reading
	 * copy of binary without read permission on file).
	 */
again:
	if (u.u_error = VOP_ACCESS(vp, VEXEC, u.u_cred))
		goto bad;
	if ((u.u_procp->p_flag & SDBG)
	&&  (u.u_error = VOP_ACCESS(vp, VREAD, u.u_cred)))
		goto bad;
	if (vp->v_type != VREG
	||  (vattr.va_mode & (VEXEC|(VEXEC>>3)|(VEXEC>>6))) == 0) {
		u.u_error = EACCES;
		goto bad;
	}

	u.u_error = gethead(vp, &exdata, &ptype);

	/*
	 * If not a valid executable, see if it's a "#!" file. getxfile()
	 * checks format of the head further.
	 *
	 * Also an ASCII line beginning with #! is the file name of a ``shell''
	 * and arguments may be prepended to the argument list if given here.
	 *
	 * SHELL NAMES ARE LIMITED IN LENGTH.
	 *
	 * ONLY ONE ARGUMENT MAY BE PASSED TO THE SHELL FROM
	 * THE ASCII LINE.
	 */

	if (u.u_error == ENOEXEC) {
		if (exdata.ex_shell[0] != '#' || exdata.ex_shell[1] != '!' ||
		    indir) {
			goto bad;
		}
		u.u_error = 0;				/* need to clear */
		cp = &exdata.ex_shell[2];		/* skip "#!" */
		while (cp < &exdata.ex_shell[SHSIZE]) {
			if (*cp == '\t')
				*cp = ' ';
			else if (*cp == '\n') {
				*cp = '\0';
				break;
			}
			cp++;
		}
		if (*cp != '\0') {
			u.u_error = ENOEXEC;
			goto bad;
		}
		cp = &exdata.ex_shell[2];
		while (*cp == ' ')
			cp++;
		execnamep = cp;
		while (*cp && *cp != ' ')
			cp++;
		sharg = NULL;
		if (*cp) {
			*cp++ = '\0';
			while (*cp == ' ')
				cp++;
			if (*cp) {
				bcopy((caddr_t)cp, (caddr_t)cfarg, SHSIZE);
				sharg = cfarg;
			}
		}
		indir = 1;
		VN_PUT(vp);
		u.u_error = lookupname(execnamep, UIOSEG_KERNEL, FOLLOW_LINK,
						(struct vnode **)0, &vp);
		if (u.u_error) {
			vp = (struct vnode *)0;
			goto bad;
		}
		if (u.u_error = exec_getattr(vp, &vattr))
			goto bad;
		uid = u.u_uid;	/* disallow setuid/setgid shell scripts */
		gid = u.u_gid;
		goto again;
	} else if (u.u_error)
		goto bad;

	/* Make sure there is no pending asynchronous I/O for this process */
	if (u.u_procp->p_aio) {
		aio_pend_wait(u.u_procp->p_aio, u.u_procp, 0);
	}

	/*
	 * If shared open file table, back out for new program image.
	 * Note: ok to race with concurrent dereference; worst case is
	 * additional work in creating new copy.
	 */

	if (OFILE_SHARED(u.u_ofile_tab)) {
		int error;

		oft = ofile_clone(u.u_ofile_tab, &error);
		if (oft == NULL) {
			u.u_error = error;
			goto bad;
		}
	}

	/*
	 * Copy user arguments
	 */
	u.u_error = fuexarg(uap, sharg, indir, &uargs);
	if (u.u_error)
		goto bad;

	/*
	 * Note: getxfile() is called with vp locked, returns it unlocked.
	 */
	if (ptype == BCS_PROC) {
		u.u_error = getxfile_bcs(vp, &exdata.ex_data,
						 &uargs, uid, gid, &vattr);
	} else
	u.u_error = getxfile(vp, &exdata.ex_data, &uargs, uid, gid, &vattr);
	VN_RELE(vp);
	vp = (struct vnode *)NULL;
	/*
	 * Free page table entries allocated by fuexarg. If error, 
	 * need to free the pages as well.
	 */
	if (u.u_error) {
		wmemfree(uargs.va, uargs.npgs);
		goto bad;
	} else
		uptfree((long)uargs.npgs, (long)btokmx((struct pte *)uargs.va));
	/* 
	 * Now that we are committed to the new image (i.e., we cannot simply
	 * error return to the process image which called exec()), let's 
	 * set the process type attribute.
	 */
	u.u_ptype = ptype;

	l.cnt.v_cntexec++;

	/*
	 * Reset caught signals.  Held signals remain held through p_sigmask.
	 * No process-state locking, since only process can modify own
	 * p_sigcatch, and races with psignal() exist anyhow.
	 */

	while (u.u_procp->p_sigcatch) {
		/*
		 * If the action was to catch the signal, then
		 * change it to be SIG_DFL.
		 */
		nc = ffs(u.u_procp->p_sigcatch);
		SIGM_DELSET(u.u_procp->p_sigcatch, sigmask(nc));
		u.u_sa[nc].sa_handler = SIG_DFL;
		u.u_sa[nc].sa_flags = 0;
	}
	/*
	 * clear sigsuspend mask since we can't return.
	 */
	SIGM_CLR(u.u_oldmask);

	/*
	 * Allow custom system calls to deal with exec.
	 * Done here in case "close-on-exec" will close some
	 * file-descriptors the custom syscalls might use.
	 */

	cust_sys_exec();

	/*
	 * If allocated a new open file table, install it and loose old one.
	 * Then close all "close-on-exec" file-descriptors.
	 */

	if (oft) {
		ofile_deref(u.u_ofile_tab);
		u.u_ofile_tab = oft;
		oft = NULL;
	}
	ofile_exclose(u.u_ofile_tab);

	setregs((ulong)exdata.ex_data.e_entry);

	/*
	 * Remember file name for accounting.
	 */
	u.u_acflag &= ~AFORK;
	if (pn.pn_pathlen > MAXCOMLEN)
		pn.pn_pathlen = MAXCOMLEN;
	bcopy((caddr_t)pn.pn_buf, (caddr_t)u.u_comm,
			(unsigned)(pn.pn_pathlen + 1));
bad:
	pn_free(&pn);
	if (vp) {
		VN_PUT(vp);
	}
	if (oft) {
		register int error = u.u_error;
		ofile_deref(oft);		/* closef() may alter u_error */
		u.u_error = error;
	}
	/* 
	 * The i386 BCS specification (page 6-21) requires that there
	 * be at least 60 possible open file descriptors per process.
	 * Let's take care of that here.  We use v.v_nofiles assuming
	 * that the kernel has been configured with NOFILES set to
	 * 60 or more.  If not, there's not much we can do about it.
	 */
	if (u.u_ptype == BCS_PROC && v.v_nofiles > NOFILE && !u.u_error) {
		(void) csetdtablesize(v.v_nofiles);
		if (u.u_error) {
#ifdef	DEBUG
		   if (exec_debug)
			printf("exece:csetdtablesize failed,errno=%d,rval=%d\n"
				,u.u_error, u.u_r.r_val1);
#endif	/* DEBUG */
			u.u_error = 0;
		}
	}

	/*
	 * If process traced by a multi-process debugger, stop for it.
	 * Stop here (rather than in getxfile()) since have args/etc on
	 * stack and all resouces released.
	 */

	if ((u.u_procp->p_flag & SMPTRC) && !u.u_error) {
		(void) p_lock(&u.u_procp->p_state, SPLHI);
		if (u.u_procp->p_flag & SMPTRC)			/* can race */
			mpt_stop(PTS_EXEC);
		v_lock(&u.u_procp->p_state, SPL0);
	}
}

/*
 * gethead()
 *	Read the file, a.out and section headers. There must be at
 *	least three sections - text, data and bss. Ignore all others.
 *
 */
gethead(vp, ex, ptype)
	register struct vnode *vp;
	union exdata *ex;
	unsigned int *ptype;
{
	register struct scnhdr *scnp;
	register struct filehdr *fhdrp;
	struct aouthdr *ahdrp;
	struct exec_data *exdp;
	int error, resid, offset;
#define	SCNSZ	4
	struct scnhdr scn_hdr[SCNSZ];
	int i;
	uint nsec, sections;
	int scns = 0;

	/*
	 * Read in first few bytes of file for segment sizes, magic no:
	 *	I386MAGIC = COFF binary
	 *
	 * Should cache tsize, dsize, bsize, and magic # indication in struct
	 * vnode for already mapped VTEXT vnode, and avoid the vn_rdwr().
	 * When/if do this, may need care to purge bad map (currently done in
	 * mmreg_rw(), since do actually read the mapped file).
	 */

	ex->ex_shell[0] = '\0';	/* for zero length files */
	error = vn_rdwr(UIO_READ, vp, (caddr_t)ex, sizeof (*ex),
						0, UIOSEG_KERNEL, 0, &resid);
	if (resid)
		return(ENOEXEC);
	if (error)
		return(error);

	fhdrp = &ex->ex_exec.exec_fhdr;
	ahdrp = &ex->ex_exec.exec_aout;
	switch ((ushort)fhdrp->f_magic) {
		case I386MAGIC:    /* Sequent binary */
			*ptype = POSIX_PROC;
			break;
		case SVI386MAGIC:  /* BCS binary */
			*ptype = BCS_PROC;
			break;
		default:
			return(ENOEXEC);
	}

	/* We require that the optional a.out header be present */

	if ((fhdrp->f_flags & F_EXEC) == 0 || fhdrp->f_opthdr == 0)
		return(ENOEXEC);

	switch ((short)ahdrp->magic) {
		case 0413:
			break;
		case 0443:
			return(ELIBEXEC);
		case 0410:
		case 0411:
		default:
			return(ENOEXEC);
	}

	/*
	 * Read the section headers. There must be at least
	 * 3 => .text, .data and .bss. Typically there's
	 * also a .comment. We read in 4 sections at a time
	 * to cut down the overhead - this means that for
	 * the typical coff file, one i/o request should
	 * be sufficient to get all the section headers.
	 *
	 * Since ex_data and ex_exec are overlaid on each
	 * other, we cannot use any ex_exec fields once we
	 * start filling out ex_data. There's probably a
	 * better way of doing this.
	 */

	sections = fhdrp->f_nscns;
	exdp = &ex->ex_data;
	exdp->e_entry = ahdrp->entry;
	exdp->e_amagic = ahdrp->magic;
	offset = sizeof(*ex);
	while (sections) {
		nsec = min(SCNSZ, sections);
		sections -= nsec;

		error = vn_rdwr(UIO_READ, vp, (caddr_t)scn_hdr,
				(int)(nsec*sizeof(scn_hdr[0])), offset,
				UIOSEG_KERNEL, 0, &resid);
		if (resid)
			return(ENOEXEC);
		if (error)
			return(error);

		offset += nsec*sizeof(scn_hdr[0]);

		for (i = 0, scnp = scn_hdr; i < nsec; i++, scnp++) {
			switch (scnp->s_flags) {
			case STYP_TEXT:
				scns |= STYP_TEXT;
				exdp->e_torg = scnp->s_vaddr;
				exdp->e_tsize = scnp->s_size;
				exdp->e_toff = scnp->s_scnptr;
				break;
			case STYP_DATA:
				scns |= STYP_DATA;
				exdp->e_dorg = scnp->s_vaddr;
				exdp->e_dsize = scnp->s_size;
				exdp->e_doff = scnp->s_scnptr;
				break;
			case STYP_BSS:
				scns |= STYP_BSS;
				exdp->e_bsize = scnp->s_size;
				break;
			}
		}
	}
	if (scns != (STYP_TEXT|STYP_DATA|STYP_BSS))
		return(ENOEXEC);
	
	return(0);
}


/*
 * getxfile()
 *	Set up memory for executed file.
 *
 * Arranges text to be RO ref to mapped (VTEXT) vnode, data is copy-on-ref
 * to same vnode.
 *
 * Called with LOCKED vnode; vnode is UNLOCKED before return.
 */

static
getxfile(vp, ep, uargs, uid, gid, va)
	register struct vnode *vp;
	register struct exec_data *ep;
	struct	uargs *uargs;
	uid_t	uid;
	gid_t	gid;
	struct	exec_vattr *va;
{
	register struct	proc *p = u.u_procp;
	size_t	ts;				/* text only size (pages) */
	size_t	ds;				/* total of text+init_data */
	size_t	bs;				/* bss size (pages) */
	size_t	ss;				/* initial stack size (pages) */
	size_t	tds;				/* text+data (pages) in file */
	ulong	thandle;			/* handle of mmap'd text+data */
	ulong	dhandle;			/* handle of mmap'd data */
	ulong	dstart;				/* aligned start of data */
	int	error;
	spl_t	s;

	/*
	 * Compute text and data sizes and make sure not too large.
	 * Assumes data follows text in file.
	 * Check both dssize+bsize and individually (wrap-around).
	 *
	 * Also insure non-zero text size and executable file is large
	 * enough to hold the executable.
	 */

	ts = (ep->e_toff & PGOFSET) + ep->e_tsize;
	tds = btoc(ts + ep->e_dsize);
	ds = btoc((ep->e_doff & PGOFSET) + ep->e_dsize);
	dstart = btop(ep->e_doff);
	if (ds) {
		ulong	pad;
		/*
		 * If we have a data section, then the bytes used
		 * to pad the section out to a page boundary are
		 * treated as bss, so subtract it from bss size.
		 */
		pad = ctob(tds) - (ts + ep->e_dsize);
		if (pad < ep->e_bsize)
			ep->e_bsize -= pad;
		else
			ep->e_bsize = 0;
	}
	bs = clrnd(btoc(ep->e_bsize));
	ts = btoc(ts) + btop(ep->e_torg);
	ds += ts;
	ss = uargs->npgs;

	if (ep->e_tsize <= 0 || va->va_size < ctob(tds)
	    || btop(ep->e_dorg) != ts)
		error = ENOEXEC;
	else if (ep->e_tsize > MAXTSIZ*NBPG || ep->e_dsize > MAXDSIZ*NBPG
					   || ep->e_bsize > MAXDSIZ*NBPG)
		error = ENOMEM;
	else
		error = chksize((unsigned)ds+bs, (unsigned)ss);

	if (error) {
		VN_UNLOCKNODE(vp);
		return(error);
	}

	/*
	 * Get handle on mapping.  Do this before release old space to
	 * be sure we can get it, and avoid vnode deadlocks.
	 */

	error = xalloc(vp, tds, ds - ts, dstart, &thandle, &dhandle);
	VN_UNLOCKNODE(vp);
	if (error)
		return(error);

	/*
	 * Make sure enough swap space to start process.
	 */

	u.u_cdmap = zdmap;
	u.u_csmap = zdmap;
	if (!vsalloc(&u.u_cdmap, (size_t)btop(ep->e_dorg), ds+bs-ts) ||
	    !vsalloc(&u.u_csmap, 0, ss)) {
		vsrele(&u.u_cdmap);
		xfree(tds, ds - ts, dstart, thandle, dhandle);
		return(EAGAIN);
	}

	/*
	 * At this point, committed to the new image!
	 * Release virtual memory resources of old process, and
	 * initialize the virtual memory of the new process.
	 * If we resulted from vfork(), instead wakeup our
	 * parent who will wake us up when he has taken back
	 * our resources.  Note that p_pptr is stable since
	 * parent is sleeping on its own p_vfork sema and cannot
	 * have exited.
	 *
	 * Avoid swap while page-table is in state of transition.
	 */

	++p->p_noswap;

	if ((p->p_flag & SVFORK) == 0) {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		vrelvm();
	} else {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag &= ~SVFORK;
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		/*
		 * Ok to race with parent switching VM resources here,
		 * since we're not swappable and p_sema doesn't need
		 * those resources.
		 */
		v_sema(&p->p_pptr->p_vfork);
		(void)p_sema(&p->p_vfork, PZERO-1);
		u.u_pmapcnt = 0;
	}
	u.u_mmapmax = u.u_mmap;			/* no mmap's in effect now */

	/*
	 * Get mem for data/bss. Stack is alloced later by stackbld.
	 */
	u.u_dmap = u.u_cdmap;
	u.u_smap = u.u_csmap;
	if (error = vgetvm(ds+bs, 0)) {
		/*
		 * Failed to expand -- swap-space.  Process has been
		 * swkill()'d, so need to unwind and die quietly.
		 */
		xfree(tds, ds - ts, dstart, thandle, dhandle);
		return(error);
	}

	/*
	 * Reattach argv pages as user stack pages
	 */
	if (stackbld(uargs)) {
		swkill(p, "exec: error building stack");
		return(ENOMEM);
	}

	/*
	 * Set up initial Rset and text+data part of page-table.
	 * Then set up bss part of page-table.
	 */

	vinitRS(ds+bs, ss);
	xinitpt(tds, ts, ds - ts, dstart, (ulong)btop(ep->e_torg),
		(ulong)btop(ep->e_dorg), thandle, dhandle);
	ptefill(dptopte(p, ds), PG_ZFOD, (int)bs);
	l.cnt.v_nzfod += bs;

	--p->p_noswap;

	/*
	 * Traced ==> post SIGTRAP if traced under old ptrace.
	 * Set up SUID/SGID protections for untraced process.
	 */

	if (p->p_flag & SDBG) {
		s = p_lock(&p->p_state, SPLHI);
		/*
		 * if just a STRC send SIGTRAP (SMPTRC will call mtp_stop)
		 */
		if ((p->p_flag & (STRC|SMPTRC)) == STRC) {	/* old-flavor debugger */
			/*
			 * issig() will see this and call pt_stop().
			 */
			lpsignal(p, SIGTRAP);
		}
		v_lock(&p->p_state, s);
	} else {
		if (uid != u.u_uid || gid != u.u_gid)
			u.u_cred = crcopy(u.u_cred);
		u.u_uid = uid;
		p->p_suid = uid;
		u.u_gid = gid;
		p->p_sgid = gid;
	}

	u.u_tsize = ts;
	u.u_dsize = ds+bs;
	u.u_ssize = ss;
	u.u_dsegsz = u.u_dsize;
	u.u_prof.pr_scale = 0;

	return(0);				/* success! */
}


/*
 * getxfile_bcs()
 *	Set up memory for executed file.
 *
 * Arranges text to be RO ref to mapped (VTEXT) vnode, data is copy-on-ref
 * to same vnode.  Handles the case where the size of the file is less than
 * the page-alinged end-of-data (the last portion of the data segment is read
 * into memory to avoid mapping a partial page).  Also permits the placement
 * of the data segment at any "reasonalbe" virtual address (e.g., BCS procs
 * like to place the data segment at virtual 4MBytes).
 *
 * Called with LOCKED vnode; vnode is UNLOCKED before return.
 */

static
getxfile_bcs(vp, ep, uargs, uid, gid, va)
	register struct vnode *vp;
	register struct exec_data *ep;
	struct	uargs *uargs;
	uid_t	uid;
	gid_t	gid;
	struct	exec_vattr *va;
{
	register struct	proc *p = u.u_procp;
	size_t	ts;		/* text only size (pages) */
	size_t	ds;		/* total of text+init_data */
	size_t	bs;		/* bss size (pages) */
	size_t	ss;		/* initial stack size (pages)*/
	size_t	dv;		/* total of virtual text+data*/
	ulong	thandle;	/* handle of mmap'd text+data*/
	ulong	dhandle;	/* handle of mmap'd data */
	ulong	dstart;		/* aligned start of data */
	ulong	edata;		/* end of data in file */
	ulong   etext;          /* end of text in file */
	ulong	pda=0;		/* Poor Data Alignment. Indicates the end of */
				/* 	the data segment is not page aligned */
	ulong	pad=0;		/* # bytes from edata to the next page...    */
				/* 	boundary, zero if edata is already...*/
				/* 	aligned.			     */
	int	error;
	int 	resid;
	spl_t	s;

	/*
	 * Compute text and data sizes and make sure not too large.
	 * Assumes data follows text in file.
	 * Check both dssize+bsize and individually (wrap-around).
	 *
	 * Also insure non-zero text size and executable file is large
	 * enough to hold the executable.
	 */

	/*
	 * The following variables represent sizes relative to
	 * the executable file and not the virtual memory image:
	 * 	dstart 	represents the offset of data in the file.
	 * 	edata	represents the end of the data segment in the file.
	 *      etext   represents the end of the text segment in the file.
	 */
	dstart = btop(ep->e_doff);
	edata = ep->e_doff + ep->e_dsize;
	etext = ep->e_toff + ep->e_tsize;

	if (va->va_size < (ulong)NBPG || va->va_size < ctob(btoc(etext))) {
		/*
		 * If we got here, it must mean that the size of the file
		 * is smaller than one hardwar page or the end of the text
		 * segment rounded to the next page boudary.  We cannot 
		 * directly map the entire text segment without modifying the
		 * the executable file (e.g., extending the size of the file
		 * to the next page boundary).  Therefore, we will call
		 * getxfile_bcache() to setup the address space and read the
		 * file through the buffer cache.
		 */
		error = getxfile_bcache(vp, ep, uargs, uid, gid, va);
		return(error);
	}

	ds = (ep->e_doff & PGOFSET) + ep->e_dsize;
	if (ds) {
		pad = ctob(btoc(ds)) - ds;
		if (pad < ep->e_bsize)
			ep->e_bsize -= pad;
		else
			ep->e_bsize = 0;
	}
	bs = clrnd(btoc(ep->e_bsize));
	ts = btoc(ep->e_torg + ep->e_tsize);
	ds = btoc(ds);
	dv = btoc(ep->e_dorg + ep->e_dsize);
	ss = uargs->npgs;

	if (pad != 0) {
		/*
		 * If we got here, it must mean that the size of the file
		 * is smaller than the end of the data segment rounded to
		 * the next page boudary or that the end of the data segment
		 * is not aligned to a page boundary.  Therefore, let's 
		 * subtract the last page from "ds" so that we do 
		 * not attempt to mmap (xalloc_bcs(), xinitpt_bcs()) the 
		 * last partial page.  The last page of data will be taken 
		 * care of later in this routine (via setting up the 
		 * corresponding pte and reading in the last partial 
		 * page of the data segment).
		 */
		pda = 1; /* indicate Poor Data Alignment */
		ds--;
	}

#ifdef	DEBUG
	if (exec_debug) {
		printf("toff=0x%x, torg=0x%x, tsize=0x%x\n", ep->e_toff,
		        ep->e_torg, ep->e_tsize);
		printf("doff=0x%x, dorg=0x%x, dsize=0x%x\n", ep->e_doff,
		        ep->e_dorg, ep->e_dsize);
		printf("bsize=0x%x\n", ep->e_bsize);
		printf("ts=0x%x, ds=0x%x, bs=0x%x, dv=0x%x, ss=0x%x\n",
		        ts, ds, bs, dv,	ss);
		printf("edata=0x%x, pda=0x%x\n", edata, pda);
	}
#endif	/* DEBUG */

	if ((int)ep->e_tsize <= 0 ||
	    (int)ep->e_dorg < 0 ||
	    (int)ep->e_doff != (int)(ep->e_toff + ep->e_tsize) || 
	    (size_t)btop(ep->e_dorg) < ts ||
	    (int)va->va_size < (int)(ep->e_doff + ep->e_dsize) ||
	    (int)ep->e_torg < 0 || 
	    (int)ep->e_torg >= (int)ep->e_dorg)
		error = ENOEXEC;
	else if (ep->e_tsize > MAXTSIZ*NBPG || ep->e_dsize > MAXDSIZ*NBPG
					   || ep->e_bsize > MAXDSIZ*NBPG)
		error = ENOMEM;
	else
		error = chksize((unsigned)(dv+bs), (unsigned)ss);

	if (error) {
		VN_UNLOCKNODE(vp);
		return(error);
	}

	/*
	 * Get handle on mapping.  Do this before release old space to
	 * be sure we can get it, and avoid vnode deadlocks.
	 */

	error = xalloc_bcs(vp, ts, ds, dstart, &thandle, &dhandle);
	VN_UNLOCKNODE(vp);
	if (error)
		return(error);

	/*
	 * Make sure enough swap space to start process.
	 */

	u.u_cdmap = zdmap;
	u.u_csmap = zdmap;
	if (!vsalloc(&u.u_cdmap,(size_t)btop(ep->e_dorg),(size_t)(ds+bs+pda))||
	    !vsalloc(&u.u_csmap, (size_t)0, ss)) {
		vsrele(&u.u_cdmap);
		xfree_bcs(ts, ds, dstart, thandle, dhandle);
		return(EAGAIN);
	}

	/*
	 * At this point, committed to the new image!
	 * Release virtual memory resources of old process, and
	 * initialize the virtual memory of the new process.
	 * If we resulted from vfork(), instead wakeup our
	 * parent who will wake us up when he has taken back
	 * our resources.  Note that p_pptr is stable since
	 * parent is sleeping on its own p_vfork sema and cannot
	 * have exited.
	 *
	 * Avoid swap while page-table is in state of transition.
	 */

	++p->p_noswap;

	if ((p->p_flag & SVFORK) == 0) {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		vrelvm();
	} else {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag &= ~SVFORK;
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		/*
		 * Ok to race with parent switching VM resources here,
		 * since we're not swappable and p_sema doesn't need
		 * those resources.
		 */
		v_sema(&p->p_pptr->p_vfork);
		(void)p_sema(&p->p_vfork, PZERO-1);
		u.u_pmapcnt = 0;
	}
	u.u_mmapmax = u.u_mmap;			/* no mmap's in effect now */

	/*
	 * Get mem for data/bss. Stack is alloced later by stackbld.
	 */
	u.u_dmap = u.u_cdmap;
	u.u_smap = u.u_csmap;
	if (error = vgetvm(dv+bs, 0)) {
		/*
		 * Failed to expand -- swap-space.  Process has been
		 * swkill()'d, so need to unwind and die quietly.
		 */
		xfree_bcs(ts, ds, dstart, thandle, dhandle);
		return(error);
	}

	/*
	 * Reattach argv pages as user stack pages
	 */
	if (stackbld(uargs)) {
		swkill(p, "exec: error building stack");
		return(ENOMEM);
	}

	/*
	 * Set up initial Rset and text+data part of page-table.
	 * Then set up bss part of page-table.
	 */

	vinitRS(dv+bs, ss);
	xinitpt_bcs(ts, ds, dstart, (ulong)btop(ep->e_torg),
		(ulong)btop(ep->e_dorg), thandle, dhandle);
	ptefill(dptopte(p, dv), PG_ZFOD, (int)bs);
	l.cnt.v_nzfod += (bs + pda);

	if (pda) {
		/*
		 * If pda is non-zero, then the executable file is smaller
		 * than the page-aligned end of data, or the end of the data
		 * segment does not lie on a page boundary.  In other words,
	   	 * we can't map the last page of data without extending
		 * the size of the file to the next page boundary (which we
		 * would rather not do, due to permission hassles).  Therefore,
		 * since we have already mapped all but the last page of the
		 * file, let's finish the job by setting up the corresponding
		 * pte and reading in the last portion of data.  Since the
		 * the pte is initialized to ZFOD, we should not have any
		 * trouble with garbage data at the end of the page.
		 */
		ptefill(dptopte(p, dv-1), PG_ZFOD, 1);
		VN_LOCKNODE(vp);
		error = vn_rdwr(UIO_READ, vp, (caddr_t)ctob(dv-1),
			(int)(edata-(ulong)ptob(btop(edata))), 
			(int)ptob(btop(edata)),
			UIOSEG_USER, 0, &resid);
		if (error || resid) {
			VN_UNLOCKNODE(vp);
			/* Since the time the vnode was last locked, the file
			 * could have changed.  We cannot always detect 
			 * changes to the file, but if there is a resid or
			 * an error let's kill the process before we go any
			 * further.
			 */
			swkill(p, ":  The executable changed during exec");
			return(ENOEXEC);
		}
		VN_UNLOCKNODE(vp);
	}

	--p->p_noswap;

	/*
	 * Traced ==> post SIGTRAP if traced under old ptrace.
	 * Set up SUID/SGID protections for untraced process.
	 */

	if (p->p_flag & SDBG) {
		s = p_lock(&p->p_state, SPLHI);
		/*
		 * if just a STRC send SIGTRAP (SMPTRC will call mtp_stop)
		 */
		if ((p->p_flag & (STRC|SMPTRC)) == STRC) {	/* old-flavor debugger */
			/*
			 * issig() will see this and call pt_stop().
			 */
			lpsignal(p, SIGTRAP);
		}
		v_lock(&p->p_state, s);
	} else {
		if (uid != u.u_uid || gid != u.u_gid)
			u.u_cred = crcopy(u.u_cred);
		u.u_uid = uid;
		p->p_suid = uid;
		u.u_gid = gid;
		p->p_sgid = gid;
	}

	u.u_tsize = ts;
	u.u_dsize = dv+bs;
	u.u_ssize = ss;
	u.u_dsegsz = u.u_dsize;
	u.u_prof.pr_scale = 0;

	return(0);				/* success! */
}

/*
 * getxfile_bcache()
 *	Set up memory for executed file, reading the executable image 
 * 	into memory through the buffer cache rather than mmap'ing the file.
 *	This is usefull when the size of the executable file is less than
 *	one hardware page (i.e., when it is not possible to mmap() the file
 *	without extending the length of the file to the size of a page).
 *
 *	Also note that this routine is cognizant of the starting virtual
 *	address of the data segment.  Thus, the data segment need
 *	not start at the next virtual page following the text segment.
 *	This is usefull for BCS executables whose data virtual address
 *	is set at 4MBytes (plus the data offset).
 *
 * Called with LOCKED vnode; vnode is UNLOCKED before return.
 */

static
getxfile_bcache(vp, ep, uargs, uid, gid, va)
	register struct vnode *vp;
	register struct exec_data *ep;
	struct	uargs *uargs;
	uid_t	uid;
	gid_t	gid;
	struct	exec_vattr *va;
{
	register struct	proc *p = u.u_procp;
	register struct pte *pte;
	size_t	ts;				/* text only size (pages) */
	size_t	ds;				/* total of text+init_data */
	size_t	bs;				/* bss size (pages) */
	size_t	ss;				/* initial stack size (pages)*/
	ulong	tvstart;			/* aligned virt start of text*/
	ulong	dvstart;			/* aligned virt start of data*/
	ulong   vimage_size;			/* # virt. pages of t,d,&b */
	int 	i;
	int	error;
	int 	resid;
	spl_t	s;


	/*
	 * Compute text and data sizes and make sure not too large.
	 * Assumes data follows text in file.
	 * Check both dssize+bsize and individually (wrap-around).
	 *
	 * Also insure non-zero text size and executable file is large
	 * enough to hold the executable.
	 */

	ds = ep->e_dorg + ep->e_dsize;
	if (ds) {
		ulong	pad;
		/*
		 * If we have a data section, then the bytes used
		 * to pad the section out to a page boundary are
		 * treated as bss, so subtract it from bss size.
		 */
		pad = ctob(btoc(ds)) - ds;
		if (pad < ep->e_bsize)
			ep->e_bsize -= pad;
		else
			ep->e_bsize = 0;
	}
	bs = btoc(ep->e_bsize);
	ts = btoc(ep->e_torg + ep->e_tsize);
	ds = btoc(ds);
	ss = uargs->npgs;
	tvstart = (ulong)btop(ep->e_torg);
	dvstart = (ulong)btop(ep->e_dorg);
	vimage_size = (ulong)(ds + bs);

#ifdef	DEBUG
	if (exec_debug) {
		printf("toff=0x%x, torg=0x%x, tsize=0x%x\n", ep->e_toff,
		        ep->e_torg, ep->e_tsize);
		printf("doff=0x%x, dorg=0x%x, dsize=0x%x\n", ep->e_doff,
		        ep->e_dorg, ep->e_dsize);
		printf("bsize=0x%x\n", ep->e_bsize);
		printf("ts=0x%x, ds=0x%x, bs=0x%x, ss=0x%x,vimage_size=0x%x\n",
			ts, ds, bs, ss, vimage_size);	
		printf("tvstart=0x%X, dvstart=0x%X\n", tvstart, dvstart);
	}
#endif	/* DEBUG */

	if ((int)ep->e_tsize <= 0 ||
	    (int)ep->e_dorg < 0 ||
	    (int)ep->e_doff != (int)(ep->e_toff + ep->e_tsize) ||
	    (int)va->va_size < (int)(ep->e_doff + ep->e_dsize) ||
	    (size_t)dvstart < ts ||
	    (int)ep->e_torg < 0 ||
	    (int)ep->e_torg >= (int)ep->e_dorg)
		error = ENOEXEC;
	else if (ts > MAXTSIZ || ds > MAXDSIZ || bs > MAXDSIZ)
		error = ENOMEM;
	else
		error = chksize((unsigned)vimage_size, (unsigned)ss);

	/*
	 * Must unlock vnode here before releasing address space in 
	 * case exec'ing same vnode (i.e., deadlock avoidance).
	 */
	VN_UNLOCKNODE(vp);
	if (error)
		return(error);

	/*
	 * Make sure enough swap space to start process.
	 */

	u.u_cdmap = zdmap;
	u.u_csmap = zdmap;
	if (!vsalloc(&u.u_cdmap, (size_t)0, (size_t)vimage_size) ||
	    !vsalloc(&u.u_csmap, (size_t)0, ss)) {
		vsrele(&u.u_cdmap);
		return(EAGAIN);
	}

	/*
	 * At this point, committed to the new image!
	 * Release virtual memory resources of old process, and
	 * initialize the virtual memory of the new process.
	 * If we resulted from vfork(), instead wakeup our
	 * parent who will wake us up when he has taken back
	 * our resources.  Note that p_pptr is stable since
	 * parent is sleeping on its own p_vfork sema and cannot
	 * have exited.
	 *
	 * Avoid swap while page-table is in state of transition.
	 */

	++p->p_noswap;

	if ((p->p_flag & SVFORK) == 0) {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		vrelvm();
	} else {
		s = p_lock(&p->p_state, SPLHI);
		p->p_flag &= ~SVFORK;
		p->p_flag |= SEXEC;
		v_lock(&p->p_state, s);
		/*
		 * Ok to race with parent switching VM resources here,
		 * since we're not swappable and p_sema doesn't need
		 * those resources.
		 */
		v_sema(&p->p_pptr->p_vfork);
		(void)p_sema(&p->p_vfork, PZERO-1);
		u.u_pmapcnt = 0;
	}
	u.u_mmapmax = u.u_mmap;			/* no mmap's in effect now */

	/*
	 * Get mem for data/bss. Stack is alloced later by stackbld.
	 */
	u.u_dmap = u.u_cdmap;
	u.u_smap = u.u_csmap;
	if (error = vgetvm((size_t)vimage_size, (size_t)0)) {
		/*
		 * Failed to expand -- swap-space.  Process has been
		 * swkill()'d, so die quietly.
		 */
		return(error);
	}

	/*
	 * Reattach argv pages as user stack pages
	 */
	if (stackbld(uargs)) {
		swkill(p, "exec: error building stack");
		return(ENOMEM);
	}

	/*
	 * Set up initial Rset and initialize text+data+bss part of page-table
	 * in the following order:
	 * 	unused pte's between 0 and start of text
	 * 	pte's mapping text
	 * 	unused pte's between the end of text and the start of data
	 * 	pte's mapping data and bss
	 */

	vinitRS((size_t)vimage_size, ss);
	ptefill(dvtopte(p, 0), PG_INVAL, (int)tvstart);
	ptefill(dvtopte(p, tvstart), PG_ZFOD, (int)(ts - tvstart));
	ptefill(dvtopte(p,ts), PG_INVAL, (int)(dvstart - ts));
	ptefill(dvtopte(p, dvstart), PG_ZFOD, (int)(ds-dvstart+bs));

	l.cnt.v_nzfod += ((ts-tvstart) + (ds-dvstart+bs));

	--p->p_noswap;

	/*
	 * Address space is set up.  Now re-lock the vnode and read 
	 * text and data.  However, since the vnode was last locked,
	 * it or the represented file could have changed.  This could
	 * be disasterous.  Therefore, let's call it illegal and kill
	 * the process if this is detected.
	 */

	VN_LOCKNODE(vp);

	/*
	 * Read text segment (coff header too) and place it at virtual 0.
	 */
	error = vn_rdwr(UIO_READ, vp, (caddr_t)(ep->e_torg), (int)ep->e_tsize,
				(int)ep->e_toff, UIOSEG_USER, 0, &resid);

	if (error || resid != 0) {
#ifdef	DEBUG
		printf("getxfile_bcache: vn_rdwr 1 failed, error=%d, resid=%d\n",
			error, resid);
#endif	/* DEBUG */
		VN_UNLOCKNODE(vp);
		swkill(p, "The executable file was modified while being exec'ed");
		return(ENOEXEC);
	}

	/*
	 * Now read data segment and place it at the header-supplied
	 * data virtual address.
	 */
	error = vn_rdwr(UIO_READ, vp, (caddr_t)ep->e_dorg, (int)ep->e_dsize,
				 (int)ep->e_doff, UIOSEG_USER, 0, &resid);

	if (error || resid != 0) {
#ifdef	DEBUG
		printf("getxfile_bcache: vn_rdwr 2 failed, error=%d, resid=%d\n",
			error, resid);
#endif	/* DEBUG */
		VN_UNLOCKNODE(vp);
		swkill(p, "The executable file was modified while being exec'ed");
		return(ENOEXEC);
	}

	VN_UNLOCKNODE(vp);


	/*
	 * Be sure that text-mapping pte's are marked read-only since
	 * the paging code preserves protection bits.
	 */

	pte = dvtopte(p, 0);
	for(i = tvstart; i < (ts-tvstart); i++) {
		*(int *)pte &= ~PG_PROT;
		*(int *)pte |= RO;
		pte++;
	}

	/*
	 * Traced ==> post SIGTRAP if traced under old ptrace.
	 * Set up SUID/SGID protections for untraced process.
	 */

	if (p->p_flag & SDBG) {
		s = p_lock(&p->p_state, SPLHI);
		/*
		 * if just a STRC send SIGTRAP (SMPTRC will call mtp_stop)
		 */
		if ((p->p_flag & (STRC|SMPTRC)) == STRC) {	/* old-flavor debugger */
			/*
			 * issig() will see this and call pt_stop().
			 */
			lpsignal(p, SIGTRAP);
		}
		v_lock(&p->p_state, s);
	} else {
		if (uid != u.u_uid || gid != u.u_gid)
			u.u_cred = crcopy(u.u_cred);
		u.u_uid = uid;
		p->p_suid = uid;
		u.u_gid = gid;
		p->p_sgid = gid;
	}

	u.u_tsize = ts;
	u.u_dsize = ds+bs;
	u.u_ssize = ss;
	u.u_dsegsz = u.u_dsize;
	u.u_prof.pr_scale = 0;

	return(0);				/* success! */
}

/*
 * exec_getattr()
 *	Get a subset of struct vattr fields.  This keeps exece()'s stack
 *	smaller.
 */

static
exec_getattr(vp, va)
	struct	vnode	*vp;
	register struct	exec_vattr *va;
{
	struct	vattr	vattr;
	int	error;

	if (error = VOP_GETATTR(vp, &vattr, u.u_cred))
		return(error);

	va->va_mode = vattr.va_mode;
	va->va_uid = vattr.va_uid;
	va->va_gid = vattr.va_gid;
	va->va_size = vattr.va_size;
	return(0);
}

/*
 * exec_nosuid()
 *	Complain about not allowing SUID/SGID bits on this file-system.
 *
 * Seperate procedure to keep exece() stack small.
 */

static
exec_nosuid(name)
	caddr_t	name;
{
	struct pathname pn;

	if (pn_get(name, UIOSEG_USER, &pn) == 0) {
		cmn_err(CE_WARN, "%s: Setuid execution not allowed\n", pn.pn_buf);
		/*
		 *+ setuid programs cannot be executed off this filesystem.
		 *+ This is a result of the way the filesystem was mounted.
		 */
		pn_free(&pn);
	}
}

/*
 * fuexarg
 *	Copy arguments from user space into kernel
 *
 * Scan the arguments to figure out the various sizes. Allocate wmemalled
 * pages and copy args into them. Return useful information to caller in
 * uargs.
 *
 * There is a (small) probability of memory deadlock here since even
 * though the process is swappable at this point, the arg pages are
 * marked as system pages and aren't swapped. If the rate of execs
 * starting is greater than the completion rate, and we're memory starved,
 * it's possible for execing processes to be swapped out with not enough
 * memory to come back in. If this ever becomes important, need to throttle
 * the number of simultaneous execs.
 */

fuexarg(uap, sharg, indir, uargs)
	struct execa *uap;			/* user args */
	char *sharg;				/* shell script args */
	int indir;				/* is shell script ? */
	struct uargs *uargs;			/* return info */
{
	int na, ne, nc, nac, np, len, ap, ucp;
	char *cp, **argp, **envp;
	int *ep;
	int i, error;
	int pages;
	caddr_t va;

	na = 0;
	ne = 0;
	nc = 0;
	nac = 0;
	argp = uap->argp;
	envp = uap->envp;
	if (argp) for (;;) {
		ap = NULL;
		if (indir && (na == 1 || na == 2 && sharg))
			ap = (int)uap->fname;
		else if (argp) {
			ap = fuword((caddr_t)argp);
			argp++;
		}
		if (ap==NULL && envp) {
			argp = NULL;
			if ((ap = fuword((caddr_t)envp)) == NULL)
				break;
			envp++;
			ne++;
		}
		if (ap == NULL)
			break;
		else if (ap == -1) {
			return(EFAULT);
		}
		na++;
		if (indir && na == 2 && sharg != NULL) {
			len = strlen(sharg) + 1;
		} else {
			error = ustrlen((caddr_t)ap, &len);
			if (error) {
				return(error);
			}
		}
		nc += len;
		if (argp)
			nac += len;
		if (nc > NCARGS)
			return(E2BIG);
	}

	nc = (nc + NBPW-1) & ~(NBPW-1);
	np = (na + 3)*NBPW;

	pages = clrnd(btoc(nc + np + NBPW + USRSTACKADJ));
	va = wmemall(ctob(pages), 1);


	ucp = USRSTACK - USRSTACKADJ - nc - NBPW;
	cp = va + ctob(pages) - nc - NBPW;
	ep = (int *)((int)cp - np);

	/* clear unused bytes */
	bzero(va, (unsigned)((caddr_t)ep - va));

	uargs->va = va;
	uargs->npgs = pages;
	uargs->argp = (char *)cp;
	uargs->arglen = nac;
	uargs->usp = (int)ucp - np;

	/*
	 * Since we did a successful ustrlen earlier, we know
	 * the pages are not bogus. Since user space is mapped
	 * in the kernel on the 386, use copystr to copy the args
	 * in - much quicker and it's ok to page fault in the kernel.
	 */
	*ep++ = na - ne;		/* argc */
	for (i = 0; i < (na - ne); i++) {
		*ep++ = ucp;		/* argv */
		if (indir && i == 1 && sharg != NULL) {
			(void) copystr(sharg, cp, nc, &len);
		} else if (indir && (i == 1 || i == 2 && sharg != NULL)) {
			ap = (int)uap->fname;
#ifdef	i386
			error = copystr((char *)VA_USER+ap, cp, nc, &len);
#else
			error = copyinstr((char *)ap, cp, nc, &len);
#endif
			ASSERT_DEBUG(error == 0, "fuexarg 1");
		} else {
			ap = fuword((caddr_t)uap->argp++);
#ifdef	i386
			error = copystr((char *)VA_USER+ap, cp, nc, &len);
#else
			error = copyinstr((char *)ap, cp, nc, &len);
#endif
			ASSERT_DEBUG(error == 0, "fuexarg 2");
		}
		ucp += len;
		cp += len;
		nc -= len;
	}
	*ep++ = 0;
	for (i = 0; i < ne; i++) {
		*ep++ = ucp;		/* envp */
		ap = fuword((caddr_t)uap->envp++);
#ifdef	i386
		error = copystr((char *)VA_USER+ap, cp, nc, &len);
#else
		error = copyinstr((char *)ap, cp, nc, &len);
#endif
		ASSERT_DEBUG(error == 0, "fuexarg 3");
		ucp += len;
		cp += len;
		nc -= len;
	}
	*ep = 0;
	return(0);
}

/*
 * stackbld
 *	Set up user stack
 *
 * Reattach previously wmemalled memory as stack pages, initialize u_psargs,
 * clear unused pte's (moved here from expand()).
 */

stackbld(uargs)
	register struct uargs *uargs;
{
	register int ptab, i;
	struct proc *p;
	struct pte *pte, *first;
	size_t oss;
	char *cp, *sp;

	p = u.u_procp;
	oss = u.u_ssize;
	u.u_ssize += uargs->npgs;
	p->p_ssize += uargs->npgs;

	if (!ptexpand(u.u_dsize, oss, 1)) {
		p->p_ssize = u.u_ssize = oss;
		return(1);
	}

	/* do psargs */
	u.u_ar0[SP] = uargs->usp;
	i = MIN(uargs->arglen, PSARGSZ - 1);
	cp = uargs->argp;
	sp = u.u_psargs;
	while (i--) {
		if ((*sp = *cp++) == '\0')
			*sp = ' ';
		sp++;
	}
	*sp = '\0';

	/*
	 * Reattach argv pages as user stack pages
	 */
	pte = svtopte(p, clbase(btop(USRSTACK - ctob(uargs->npgs))));
	ptab = btokmx((struct pte *)uargs->va);
	LOCK_MEM;
	for (i = 0; i < uargs->npgs; i++) {
		vmreattach(p, &Usrptmap[ptab+i], pte);
		pte++;
	}
	UNLOCK_MEM;
	/*
	 * Clear pte's between last data pte and last stack pte, since
	 * they may indicate fod or R/W permissions.
	 */
	first = dptopte(p, p->p_dsize);
	ptefill(first, PG_INVAL, sptopte(p, p->p_ssize-1) - first);

	FLUSH_USER_TLB(p->p_ptb1);
	return(0);
}
