/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $XConsortium: subshell.c /main/3 1995/11/01 16:51:36 rswiston $ */
/***************************************************************
*                                                              *
*                      AT&T - PROPRIETARY                      *
*                                                              *
*        THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF        *
*                    AT&T BELL LABORATORIES                    *
*         AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN         *
*            ACCORDANCE WITH APPLICABLE AGREEMENTS             *
*                                                              *
*                Copyright (c) 1995 AT&T Corp.                 *
*              Unpublished & Not for Publication               *
*                     All Rights Reserved                      *
*                                                              *
*       The copyright notice above does not evidence any       *
*      actual or intended publication of such source code      *
*                                                              *
*               This software was created by the               *
*           Advanced Software Technology Department            *
*                    AT&T Bell Laboratories                    *
*                                                              *
*               For further information contact                *
*                    {research,attmail}!dgk                    *
*                                                              *
***************************************************************/

/* : : generated by proto : : */

#if !defined(__PROTO__)
#if defined(__STDC__) || defined(__cplusplus) || defined(_proto) || defined(c_plusplus)
#if defined(__cplusplus)
#define __MANGLE__	"C"
#else
#define __MANGLE__
#endif
#define __STDARG__
#define __PROTO__(x)	x
#define __OTORP__(x)
#define __PARAM__(n,o)	n
#if !defined(__STDC__) && !defined(__cplusplus)
#if !defined(c_plusplus)
#define const
#endif
#define signed
#define void		int
#define volatile
#define __V_		char
#else
#define __V_		void
#endif
#else
#define __PROTO__(x)	()
#define __OTORP__(x)	x
#define __PARAM__(n,o)	o
#define __MANGLE__
#define __V_		char
#define const
#define signed
#define void		int
#define volatile
#endif
#if defined(__cplusplus) || defined(c_plusplus)
#define __VARARG__	...
#else
#define __VARARG__
#endif
#if defined(__STDARG__)
#define __VA_START__(p,a)	va_start(p,a)
#else
#define __VA_START__(p,a)	va_start(p)
#endif
#endif
#include	"defs.h"
#include	<ls.h>
#include	"io.h"
#include	"fault.h"
#include	"shnodes.h"
#include	"shlex.h"
#include	"jobs.h"
#include	"variables.h"
#include	"path.h"


/*
 * The following structure is used for command substitution and (...)
 */
static struct subshell
{
	struct subshell	*prev;	/* previous subshell data */
	struct subshell	*pipe;	/* subshell where output goes to pipe on fork */
	Hashtab_t	*var;	/* variable table at time of subshell */
	Hashtab_t	*svar;	/* save shell variable table */
	struct errorcontext *errcontext;
	Shopt_t		options;/* save shell options */
	pid_t		subpid;	/* child process id */
	Sfio_t*	saveout;/*saved standard output */
	char		*pwd;	/* present working directory */
	int		mask;	/* present umask */
	short		tmpfd;	/* saved tmp file descriptor */
	char		jobcontrol;
	char		monitor;
	unsigned char	fdstatus;
} *subshell_data;

static int subenv;

/*
 * This routine will turn the sftmp() file into a real /tmp file
 */
void	sh_subtmpfile __PARAM__((void), ()){
	if(sfset(sfstdout,0,0)&SF_STRING)
	{
		register int fd;
		register struct checkpt	*pp = (struct checkpt*)sh.jmplist;
		register struct subshell *sp = subshell_data->pipe;
		/* save file descriptor 1 if open */
		if((sp->tmpfd = fd = fcntl(1,F_DUPFD,10)) >= 0)
		{
			fcntl(fd,F_SETFD,FD_CLOEXEC);
			sh.fdstatus[fd] = sh.fdstatus[1]|IOCLEX;
			close(1);
		}
		/* popping a discipline forces a /tmp file create */
		sfdisc(sfstdout,SF_POPDISC);
		sh.fdstatus[fd=sffileno(sfstdout)] = IOREAD|IOWRITE;
		sfsync(sfstdout);
		if(fd==1)
			fcntl(1,F_SETFD,0);
		else
		{
			sfsetfd(sfstdout,1);
			sh.fdstatus[1] = sh.fdstatus[fd];
			sh.fdstatus[fd] = IOCLOSE;
		}
		sh_iostream(1);
		sfset(sfstdout,SF_SHARE|SF_PUBLIC,1);
		sfpool(sfstdout,sh.outpool,SF_WRITE);
		if(pp && pp->olist  && pp->olist->strm == sfstdout)
			pp->olist->strm = 0;
	}
}

/*
 * This routine creates a temp file if necessary and creates a subshell.
 * The parent routine longjmps back to sh_subshell()
 * The child continues possibly with its standard output replaced by temp file
 */
void sh_subfork __PARAM__((void), ()){
	register struct subshell *sp = subshell_data;
	pid_t pid;
	/* see whether inside $(...) */
	if(sp->pipe)
		sh_subtmpfile();
	if(pid = sh_fork(0,NIL(int*)))
	{
		/* this is the parent part of the fork */
		if(sp->subpid==0)
			sp->subpid = pid;
		siglongjmp(*sh.jmplist,SH_JMPSUB);
	}
	else
	{
		/* this is the child part of the fork */
		/* setting subpid to 1 causes subshell to exit when reached */
		sh_onstate(SH_FORKED|SH_NOLOG);
		sh_offstate(SH_MONITOR);
		subshell_data = 0;
		sh.subshell = 0;
		sp->subpid=0;
	}
}

/*
 * This routine will make a copy of the given node in the
 * layer created by the most recent subshell_fork if the
 * node hasn't already been copied
 */
Namval_t *sh_assignok __PARAM__((register Namval_t *np,int add), (np, add)) __OTORP__(register Namval_t *np;int add;){
	register Namval_t *mp;
	if(nv_isnull(np) && !add)	/* don't bother with this */
		return(np);
	/* don't bother to save if in newer scope */
	if(nv_search((char*)np,subshell_data->var,HASH_BUCKET)!=np)
		return(np);
	mp = nv_search((char*)np,subshell_data->svar,NV_ADD|HASH_BUCKET);
	if(mp->nvflag || mp->nvalue.cp)		/* see if already saved */
		return(np);
	if(nv_isnull(np))
	{
		/* mark so that it can be restored */
		nv_onattr(mp,NV_NOFREE);
		return(np);
	}
	nv_setsize(mp,nv_size(np));
	mp->nvenv = np->nvenv;
	mp->nvfun = np->nvfun;
	mp->nvalue.cp = np->nvalue.cp;
	mp->nvflag = np->nvflag;
	nv_onattr(np,NV_NOFREE);
	return(np);
}

/*
 * restore the variables
 */
static void nv_restore __PARAM__((struct subshell *sp), (sp)) __OTORP__(struct subshell *sp;){
	register Namval_t *mp, *np;
	register Hashpos_t *pos = hashscan(sp->svar,0);
	while(np=(Namval_t*)hashnext(pos))
	{
		if(mp = nv_search((char*)np,sp->var,HASH_BUCKET))
		{
			nv_unset(mp);
			nv_setsize(mp,nv_size(np));
			mp->nvenv = np->nvenv;
			mp->nvfun = np->nvfun;
			mp->nvalue.cp = np->nvalue.cp;
			mp->nvflag = np->nvflag;
		}
		np->nvalue.cp = 0;
		np->nvflag = NV_DEFAULT;
	}
	hashdone(pos);
	hashfree(sp->svar);
}


/*
 * Run command tree <t> in a virtual sub-shell
 * If comsub is not null, then output will be placed in temp file (or buffer)
 * If comsub is not null, the return value will be a stream consisting of
 * output of command <t>.  Otherwise, NULL will be returned.
 */

Sfio_t *sh_subshell __PARAM__((union anynode *t, int flags, int comsub), (t, flags, comsub)) __OTORP__(union anynode *t; int flags; int comsub;){
	struct subshell sub_data;
	register struct subshell *sp = &sub_data;
	int jmpval,nsig;
	int savecurenv = sh.curenv;
	char *savstak;
	Sfio_t *iop=0;
	struct checkpt buff;
	struct sh_scoped savst;
	struct dolnod   *argsav=0;
	sfsync(sh.outpool);
	argsav = sh_arguse();
	sh.curenv = ++subenv;
	savst = sh.st;
	sh_pushcontext(&buff,SH_JMPSUB);
	sh.subshell++;
	sp->prev = subshell_data;
	sp->subpid = 0;
	sp->errcontext = &buff.err;
	sp->var = sh.var_tree;
	sp->options = sh.options;
	sp->pipe = 0;
	if(!sh.pwd)
		path_pwd(0);
	sp->pwd = (sh.pwd?strdup(sh.pwd):0);
	umask(sp->mask=umask(0));
	subshell_data = sp;
	/* save trap table */
	sh.st.otrapcom = 0;
	if((nsig=sh.st.trapmax*sizeof(char*))>0 || sh.st.trapcom[0])
	{
		nsig += sizeof(char*);
		memcpy(savstak=stakalloc(nsig),(char*)&sh.st.trapcom[0],nsig);
		/* this nonsense needed for $(trap) */
		sh.st.otrapcom = (char**)savstak;
	}
	sh_sigreset(0);
	sp->svar = hashalloc(sh.var_tree,HASH_set,HASH_ALLOCATE,0);
	jmpval = sigsetjmp(buff.buff,0);
	if(jmpval==0)
	{
		if(comsub)
		{
			/* disable job control */
			sp->jobcontrol = job.jobcontrol;
			sp->monitor = (sh_isstate(SH_MONITOR)!=0);
			job.jobcontrol=0;
			sh_offstate(SH_MONITOR);
			sp->pipe = sp;
			/* save sfstdout and status */
			sp->saveout = sfswap(sfstdout,NIL(Sfio_t*));
			sp->fdstatus = sh.fdstatus[1];
			sp->tmpfd = -1;
			/* use sftmp() file for standard output */
			iop = sftmp(IOBSIZE+1);
			sfswap(iop,sfstdout);
			sh.fdstatus[1] = IOWRITE;
		}
		else if(sp->prev)
		{
			sp->pipe = sp->prev->pipe;
			flags &= ~SH_NOFORK;
		}
		sh_exec(t,flags);
	}
	if(jmpval!=SH_JMPSUB && sh.st.trapcom[0] && sh.subshell)
	{
		/* trap on EXIT not handled by child */
		char *trap=sh.st.trapcom[0];
		sh.st.trapcom[0] = 0;	/* prevent recursion */
		sh.oldexit = sh.exitval;
		sh_trap(trap,0);
		free(trap);
	}
	sh_popcontext(&buff);
	if(sh.subshell==0)	/* must be child process */
	{
		if(jmpval==SH_JMPSCRIPT)
			siglongjmp(*sh.jmplist,jmpval);
		sh_done(0);
	}
	if(comsub)
	{
		/* re-enable job control */
		job.jobcontrol = sp->jobcontrol;
		if(sp->monitor)
			sh_onstate(SH_MONITOR);
		/* move tmp file to iop and restore sfstdout */
		iop = sfswap(sfstdout,NIL(Sfio_t*));
		if(sffileno(iop)==1)
		{
			int fd=sfsetfd(iop,3);
			if(fd<0)
				error(ERROR_system(1),e_toomany);
			sh.sftable[fd] = iop;
			fcntl(fd,F_SETFD,FD_CLOEXEC);
			sh.fdstatus[fd] = (sh.fdstatus[1]|IOCLEX);
			sh.fdstatus[1] = IOCLOSE;
		}
		sfswap(sp->saveout,sfstdout);
		/*  check if standard output was preserved */
		if(sp->tmpfd>=0)
		{
			close(1);
			fcntl(sp->tmpfd,F_DUPFD,1);
			sh_close(sp->tmpfd);
		}
		sh.fdstatus[1] = sp->fdstatus;
	}
	if(sp->subpid)
		job_wait(sp->subpid);
	if(comsub && iop)
		sfseek(iop,(off_t)0,SEEK_SET);
	if(sh.subshell)
		sh.subshell--;
	sh.options = sp->options;
	nv_restore(sp);
	sh_argfree(argsav,0);
	sh_sigreset(1);
	sh.st = savst;
	sh.jobenv = sh.curenv = savecurenv;
	if(nsig)
		memcpy((char*)&sh.st.trapcom[0],savstak,nsig);
	sh.trapnote = 0;
	if(nsig)
		stakset(savstak,0);
	sh.options = sp->options;
	subshell_data = sp->prev;
	if(!sh.pwd || strcmp(sp->pwd,sh.pwd))
	{
		/* restore PWDNOD */
		Namval_t *pwdnod = nv_scoped(PWDNOD);
		if(sh.pwd)
			chdir(sh.pwd=sp->pwd);
		if(nv_isattr(pwdnod,NV_NOFREE))
			pwdnod->nvalue.cp = (const char*)sp->pwd;
	}
	else
		free((__V_*)sp->pwd);
	umask(sp->mask);
	if(sh.topfd != buff.topfd)
		sh_iorestore(buff.topfd);
	if(sh.exitval > SH_EXITSIG)
		sh_fault(sh.exitval&SH_EXITMASK);
	return(iop);
}
