root/bin/sh/eval.c
/*      $NetBSD: eval.c,v 1.200 2026/07/01 12:06:27 kre Exp $   */

/*-
 * Copyright (c) 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Kenneth Almquist.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
#if 0
static char sccsid[] = "@(#)eval.c      8.9 (Berkeley) 6/8/95";
#else
__RCSID("$NetBSD: eval.c,v 1.200 2026/07/01 12:06:27 kre Exp $");
#endif
#endif /* not lint */

#include <stdbool.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sysctl.h>

/*
 * Evaluate a command.
 */

#include "shell.h"
#include "nodes.h"
#include "syntax.h"
#include "expand.h"
#include "parser.h"
#include "jobs.h"
#include "eval.h"
#include "builtins.h"
#include "options.h"
#include "exec.h"
#include "redir.h"
#include "input.h"
#include "output.h"
#include "trap.h"
#include "var.h"
#include "memalloc.h"
#include "error.h"
#include "show.h"
#include "mystring.h"
#include "main.h"
#ifndef SMALL
#include "nodenames.h"
#include "myhistedit.h"
#endif


STATIC struct skipsave s_k_i_p;
#define evalskip        (s_k_i_p.state)
#define skipcount       (s_k_i_p.count)

STATIC int loopnest;            /* current loop nesting level */
STATIC int funcnest;            /* depth of function calls */
STATIC int builtin_flags;       /* evalcommand flags for builtins */
/*
 * Base function nesting level inside a dot command.  Set to 0 initially
 * and to (funcnest + 1) before every dot command to enable
 *   1) detection of being in a file sourced by a dot command and
 *   2) counting of function nesting in that file for the implementation
 *      of the return command.
 * The value is reset to its previous value after the dot command.
 */
STATIC int dot_funcnest;


const char *commandname;
struct strlist *cmdenviron;
int exitstatus;                 /* exit status of last command */
int back_exitstatus;            /* exit status of backquoted command */


STATIC void evalloop(union node *, int);
STATIC void evalfor(union node *, int);
STATIC void evalcase(union node *, int);
STATIC void evalsubshell(union node *, int);
STATIC void expredir(union node *);
STATIC void evalredir(union node *, int);
STATIC void evalpipe(union node *);
STATIC void evalcommand(union node *, int, struct backcmd *);
STATIC void prehash(union node *);

STATIC char *find_dot_file(char *);

/*
 * Called to reset things after an exception.
 */

#ifdef mkinit
INCLUDE "eval.h"

RESET {
        reset_eval();
}

SHELLPROC {
        exitstatus = 0;
}
#endif

void
reset_eval(void)
{
        evalskip = SKIPNONE;
        dot_funcnest = 0;
        loopnest = 0;
        funcnest = 0;
}

static int
sh_pipe(int fds[2])
{
        int nfd;

        if (pipe(fds))
                return -1;

        if (fds[0] < 3) {
                nfd = fcntl(fds[0], F_DUPFD, 3);
                if (nfd != -1) {
                        close(fds[0]);
                        fds[0] = nfd;
                }
        }

        if (fds[1] < 3) {
                nfd = fcntl(fds[1], F_DUPFD, 3);
                if (nfd != -1) {
                        close(fds[1]);
                        fds[1] = nfd;
                }
        }
        return 0;
}


/*
 * The eval command.
 */

int
evalcmd(int argc, char **argv)
{
        char *p;
        char *concat;
        char **ap;

        if (argc > 1) {
                p = argv[1];
                if (argc > 2) {
                        STARTSTACKSTR(concat);
                        ap = argv + 2;
                        for (;;) {
                                while (*p)
                                        STPUTC(*p++, concat);
                                if ((p = *ap++) == NULL)
                                        break;
                                STPUTC(' ', concat);
                        }
                        STPUTC('\0', concat);
                        p = grabstackstr(concat);
                }
                evalstring(p, builtin_flags & EV_TESTED);
        } else
                exitstatus = 0;
        return exitstatus;
}


/*
 * Execute a command or commands contained in a string.
 */

void
evalstring(const char *s, int flag)
{
        union node *n;
        struct stackmark smark;
        int last;
        int any;

        last = flag & EV_EXIT;
        flag &= ~EV_EXIT;

        setstackmark(&smark);
        setinputstring(s, 1, line_number);

        any = 0;        /* to determine if exitstatus will have been set */
        while ((n = parsecmd(0)) != NEOF) {
                XTRACE(DBG_EVAL, ("evalstring: "), showtree(n));
                if (n && nflag == 0) {
                        if (last && at_eof())
                                evaltree(n, flag | EV_EXIT);
                        else
                                evaltree(n, flag);
                        any = 1;
                        if (evalskip)
                                break;
                }
                rststackmark(&smark);
        }
        popfile();
        popstackmark(&smark);
        if (!any)
                exitstatus = 0;
        if (last)
                exraise(EXEXIT);
}



/*
 * Evaluate a parse tree.  The value is left in the global variable
 * exitstatus.
 */

void
evaltree(union node *n, int flags)
{
        bool do_etest;
        int sflags = flags & ~EV_EXIT;
        union node *next;
        struct stackmark smark;

        do_etest = false;
        if (n == NULL || nflag) {
                VTRACE(DBG_EVAL, ("evaltree(%s) called\n",
                    n == NULL ? "NULL" : "-n"));
                if (nflag == 0)
                        exitstatus = 0;
                goto out2;
        }

        setstackmark(&smark);
        do {
#ifndef SMALL
                displayhist = 1; /* show history substitutions done with fc */
#endif
                next = NULL;
                CTRACE(DBG_EVAL, ("pid %d, evaltree(%p: %s(%d), %#x) called\n",
                    getpid(), n, NODETYPENAME(n->type), n->type, flags));
        /*
                if (n->type != NCMD && traps_invalid)
                        free_traps();
        */
                switch (n->type) {
                case NSEMI:
                        evaltree(n->nbinary.ch1, sflags);
                        if (nflag || evalskip)
                                goto out1;
                        next = n->nbinary.ch2;
                        break;
                case NAND:
                        evaltree(n->nbinary.ch1, EV_TESTED);
                        if (nflag || evalskip || exitstatus != 0)
                                goto out1;
                        next = n->nbinary.ch2;
                        break;
                case NOR:
                        evaltree(n->nbinary.ch1, EV_TESTED);
                        if (nflag || evalskip || exitstatus == 0)
                                goto out1;
                        next = n->nbinary.ch2;
                        break;
                case NREDIR:
                        if (traps_invalid)
                                free_traps();
                        evalredir(n, flags);
                        break;
                case NSUBSHELL:
                        evalsubshell(n, flags);
                        do_etest = !(flags & EV_TESTED);
                        break;
                case NBACKGND:
                        if (traps_invalid)
                                free_traps();
                        evalsubshell(n, flags);
                        break;
                case NIF: {
                        if (traps_invalid)
                                free_traps();
                        evaltree(n->nif.test, EV_TESTED);
                        if (nflag || evalskip)
                                goto out1;
                        if (exitstatus == 0)
                                next = n->nif.ifpart;
                        else if (n->nif.elsepart)
                                next = n->nif.elsepart;
                        else
                                exitstatus = 0;
                        break;
                }
                case NWHILE:
                case NUNTIL:
                        if (traps_invalid)
                                free_traps();
                        evalloop(n, sflags);
                        break;
                case NFOR:
                        if (traps_invalid)
                                free_traps();
                        evalfor(n, sflags);
                        break;
                case NCASE:
                        if (traps_invalid)
                                free_traps();
                        evalcase(n, sflags);
                        break;
                case NDEFUN:
                        if (traps_invalid)
                                free_traps();
                        CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n",
                            n->narg.text, n->narg.lineno,
                            fnline1 ? " LINENO=1" : ""));
                        defun(n->narg.text, n->narg.next, n->narg.lineno);
                        exitstatus = 0;
                        break;
                case NNOT:
                        evaltree(n->nnot.com, EV_TESTED);
                        exitstatus = !exitstatus;
                        break;
                case NDNOT:
                        evaltree(n->nnot.com, EV_TESTED);
                        if (exitstatus != 0)
                                exitstatus = 1;
                        break;
                case NPIPE:
                        if (traps_invalid)
                                free_traps();
                        evalpipe(n);
                        do_etest = !(flags & EV_TESTED);
                        break;
                case NCMD:
                        evalcommand(n, flags, NULL);
                        do_etest = !(flags & EV_TESTED);
                        break;
                default:
#ifdef NODETYPENAME
                        out1fmt("Node type = %d(%s)\n",
                                n->type, NODETYPENAME(n->type));
#else
                        out1fmt("Node type = %d\n", n->type);
#endif
                        flushout(&output);
                        break;
                }
                n = next;
                rststackmark(&smark);
        } while(n != NULL);
 out1:
        popstackmark(&smark);
 out2:
        if (pendingsigs)
                dotrap();
        if (eflag && exitstatus != 0 && do_etest)
                exitshell(exitstatus);
        if (flags & EV_EXIT)
                exraise(EXEXIT);
}


STATIC void
evalloop(union node *n, int flags)
{
        int status;

        loopnest++;
        status = 0;

        CTRACE(DBG_EVAL,  ("evalloop %s:", NODETYPENAME(n->type)));
        VXTRACE(DBG_EVAL, (" "), showtree(n->nbinary.ch1));
        VXTRACE(DBG_EVAL, ("evalloop    do: "), showtree(n->nbinary.ch2));
        VTRACE(DBG_EVAL,  ("evalloop  done\n"));
        CTRACE(DBG_EVAL,  ("\n"));

        for (;;) {
                evaltree(n->nbinary.ch1, EV_TESTED);
                if (nflag)
                        break;
                if (evalskip) {
 skipping:              if (evalskip == SKIPCONT && --skipcount <= 0) {
                                evalskip = SKIPNONE;
                                continue;
                        }
                        if (evalskip == SKIPBREAK && --skipcount <= 0)
                                evalskip = SKIPNONE;
                        if (evalskip == SKIPFUNC || evalskip == SKIPFILE)
                                status = exitstatus;
                        break;
                }
                if (n->type == NWHILE) {
                        if (exitstatus != 0)
                                break;
                } else {
                        if (exitstatus == 0)
                                break;
                }
                evaltree(n->nbinary.ch2, flags & EV_TESTED);
                status = exitstatus;
                if (evalskip)
                        goto skipping;
        }
        loopnest--;
        exitstatus = status;
}



STATIC void
evalfor(union node *n, int flags)
{
        struct arglist arglist;
        union node *argp;
        struct strlist *sp;
        struct stackmark smark;
        int status;

        status = nflag ? exitstatus : 0;

        setstackmark(&smark);
        arglist.lastp = &arglist.list;
        for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
                expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
                if (evalskip)
                        goto out;
        }
        *arglist.lastp = NULL;

        loopnest++;
        for (sp = arglist.list ; sp ; sp = sp->next) {
                line_number = n->nfor.lineno;
                if (xflag) {
                        outxstr(expandstr(ps4val(), line_number));
                        outxstr("for ");
                        outxstr(n->nfor.var);
                        outxc('=');
                        outxshstr(sp->text);
                        outxc('\n');
                        flushout(outx);
                }

                setvar(n->nfor.var, sp->text, 0);
                evaltree(n->nfor.body, flags & EV_TESTED);
                status = exitstatus;
                if (nflag)
                        break;
                if (evalskip) {
                        if (evalskip == SKIPCONT && --skipcount <= 0) {
                                evalskip = SKIPNONE;
                                continue;
                        }
                        if (evalskip == SKIPBREAK && --skipcount <= 0)
                                evalskip = SKIPNONE;
                        break;
                }
        }
        loopnest--;
        exitstatus = status;
 out:
        popstackmark(&smark);
}



STATIC void
evalcase(union node *n, int flags)
{
        union node *cp, *ncp;
        union node *patp;
        struct arglist arglist;
        struct stackmark smark;
        int status = 0;

        setstackmark(&smark);
        arglist.lastp = &arglist.list;
        line_number = n->ncase.lineno;
        expandarg(n->ncase.expr, &arglist, EXP_TILDE);
        for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
                for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
                        line_number = patp->narg.lineno;
                        if (casematch(patp, arglist.list->text)) {
                                while (cp != NULL && evalskip == 0 &&
                                    nflag == 0) {
                                        if (cp->type == NCLISTCONT)
                                                ncp = cp->nclist.next;
                                        else
                                                ncp = NULL;
                                        line_number = cp->nclist.lineno;
                                        evaltree(cp->nclist.body, flags);
                                        status = exitstatus;
                                        cp = ncp;
                                }
                                goto out;
                        }
                }
        }
 out:
        exitstatus = status;
        popstackmark(&smark);
}



/*
 * Kick off a subshell to evaluate a tree.
 */

STATIC void
evalsubshell(union node *n, int flags)
{
        struct job *jp= NULL;
        int backgnd = (n->type == NBACKGND);

        expredir(n->nredir.redirect);
        if (xflag && n->nredir.redirect) {
                union node *rn;

                outxstr(expandstr(ps4val(), line_number));
                outxstr("using redirections:");
                for (rn = n->nredir.redirect; rn; rn = rn->nfile.next)
                        (void) outredir(outx, rn, ' ');
                outxstr(" do subshell ("/*)*/);
                if (backgnd)
                        outxstr(/*(*/") &");
                outxc('\n');
                flushout(outx);
        }
        INTOFF;
        if ((!backgnd && flags & EV_EXIT && !have_traps() && !anyjobs()) ||
            forkshell(jp = makejob(n, 1), n, backgnd?FORK_BG:FORK_FG) == 0) {
                if (backgnd)
                        flags &=~ EV_TESTED;
                INTON;
                redirect(n->nredir.redirect, REDIR_KEEP);
                evaltree(n->nredir.n, flags | EV_EXIT);   /* never returns */
        } else if (backgnd) {
                jobstarted(jp);
                exitstatus = 0;
        } else
                exitstatus = waitforjob(jp);
        INTON;

        if (!backgnd && xflag && n->nredir.redirect) {
                outxstr(expandstr(ps4val(), line_number));
                outxstr(/*(*/") done subshell\n");
                flushout(outx);
        }
}



/*
 * Compute the names of the files in a redirection list.
 */

STATIC void
expredir(union node *n)
{
        union node *redir;

        for (redir = n ; redir ; redir = redir->nfile.next) {
                struct arglist fn;

                fn.lastp = &fn.list;
                switch (redir->type) {
                case NFROMTO:
                case NFROM:
                case NTO:
                case NCLOBBER:
                case NAPPEND:
                        expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
                        redir->nfile.expfname = fn.list->text;
                        break;
                case NFROMFD:
                case NTOFD:
                        if (redir->ndup.vname) {
                                expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
                                fixredir(redir, fn.list->text, 1);
                        }
                        break;
                case NHERE:
                        redir->nhere.text = redir->nhere.doc->narg.text;
                        break;
                case NXHERE:
                        redir->nhere.text = expandhere(redir->nhere.doc);
                        break;
                }
        }
}

/*
 * Perform redirections for a compound command, and then do it (and restore)
 */
STATIC void
evalredir(union node *n, int flags)
{
        struct jmploc jmploc;
        struct jmploc * const savehandler = handler;
        volatile int in_redirect = 1;
        const char * volatile PS4 = NULL;

        expredir(n->nredir.redirect);

        if (xflag && n->nredir.redirect) {
                union node *rn;

                outxstr(PS4 = expandstr(ps4val(), line_number));
                outxstr("using redirections:");
                for (rn = n->nredir.redirect; rn != NULL; rn = rn->nfile.next)
                        (void) outredir(outx, rn, ' ');
                outxstr(" do {\n");     /* } */
                flushout(outx);
        }

        if (setjmp(jmploc.loc)) {
                int e;

                handler = savehandler;
                e = exception;
                popredir(POPREDIR_UNDO);
                if (PS4 != NULL) {
                        outxstr(PS4);
                        /* { */ outxstr("} failed\n");
                        flushout(outx);
                }
                if (e == EXERROR || e == EXEXEC) {
                        if (in_redirect) {
                                exitstatus = 2;
                                return;
                        }
                }
                longjmp(handler->loc, 1);
        } else {
                INTOFF;
                handler = &jmploc;
                redirect(n->nredir.redirect, REDIR_PUSH | REDIR_KEEP);
                in_redirect = 0;
                INTON;
                evaltree(n->nredir.n, flags);
        }
        INTOFF;
        handler = savehandler;
        popredir(POPREDIR_UNDO);
        INTON;

        if (PS4 != NULL) {
                outxstr(PS4);
                /* { */ outxstr("} done\n");
                flushout(outx);
        }
}


/*
 * Evaluate a pipeline.  All the processes in the pipeline are children
 * of the process creating the pipeline.  (This differs from some versions
 * of the shell, which make the last process in a pipeline the parent
 * of all the rest.)
 */

STATIC void
evalpipe(union node *n)
{
        struct job *jp;
        struct nodelist *lp;
        int pipelen;
        int prevfd;
        int pip[2];

        CTRACE(DBG_EVAL, ("evalpipe(%p) called\n", n));
        pipelen = 0;
        for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
                pipelen++;
        INTOFF;
        jp = makejob(n, pipelen);
        prevfd = -1;
        for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
                prehash(lp->n);
                pip[1] = -1;
                if (lp->next) {
                        if (sh_pipe(pip) < 0) {
                                if (prevfd >= 0)
                                        close(prevfd);
                                error("Pipe call failed: %s", strerror(errno));
                        }
                }
                if (forkshell(jp, lp->n,
                    n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) {
                        INTON;
                        if (prevfd > 0)
                                movefd(prevfd, 0);
                        if (pip[1] >= 0) {
                                close(pip[0]);
                                movefd(pip[1], 1);
                        }
                        evaltree(lp->n, EV_EXIT);
                }
                if (prevfd >= 0)
                        close(prevfd);
                prevfd = pip[0];
                close(pip[1]);
        }
        if (n->npipe.backgnd == 0) {
                exitstatus = waitforjob(jp);
                CTRACE(DBG_EVAL, ("evalpipe:  job done exit status %d\n",
                    exitstatus));
        } else {
                jobstarted(jp);
                exitstatus = 0;
        }
        INTON;
}



/*
 * Execute a command inside back quotes.  If it's a builtin command, we
 * want to save its output in a block obtained from malloc.  Otherwise
 * we fork off a subprocess and get the output of the command via a pipe.
 * Should be called with interrupts off.
 */

void
evalbackcmd(union node *n, struct backcmd *result)
{
        int pip[2];
        struct job *jp;
        struct stackmark smark;         /* unnecessary (because we fork) */

        result->fd = -1;
        result->buf = NULL;
        result->nleft = 0;
        result->jp = NULL;

        if (nflag || n == NULL)
                goto out;

        setstackmark(&smark);

#ifdef notyet
        /*
         * For now we disable executing builtins in the same
         * context as the shell, because we are not keeping
         * enough state to recover from changes that are
         * supposed only to affect subshells. eg. echo "`cd /`"
         */
        if (n->type == NCMD) {
                exitstatus = oexitstatus;       /* XXX o... no longer exists */
                evalcommand(n, EV_BACKCMD, result);
        } else
#endif
        {
                INTOFF;
                if (sh_pipe(pip) < 0)
                        error("Pipe call failed");
                jp = makejob(n, 0);   /* 0 --> leave jobs_invalid, no jobctl */
                if (forkshell(jp, n, FORK_NOJOB) == 0) {
                        FORCEINTON;
                        close(pip[0]);
                        movefd(pip[1], 1);
                        evaltree(n, EV_EXIT);
                        /* NOTREACHED */
                }
                close(pip[1]);
                result->fd = pip[0];
                result->jp = jp;
                INTON;
        }
        popstackmark(&smark);
 out:
        CTRACE(DBG_EVAL, ("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
                result->fd, result->buf, result->nleft, result->jp));
}

const char *
syspath(void)
{
        static char *sys_path = NULL;
        static int mib[] = {CTL_USER, USER_CS_PATH};
        static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
        size_t len;

        if (sys_path == NULL) {
                if (sysctl(mib, 2, 0, &len, 0, 0) != -1 &&
                    (sys_path = ckmalloc(len + 5)) != NULL &&
                    sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) {
                        memcpy(sys_path, "PATH=", 5);
                } else {
                        ckfree(sys_path);
                        /* something to keep things happy */
                        sys_path = def_path;
                }
        }
        return sys_path;
}

static int
parse_command_args(int argc, char **argv, int *use_syspath)
{
        int sv_argc = argc;
        char *cp, c;

        *use_syspath = 0;

        for (;;) {
                argv++;
                if (--argc == 0)
                        break;
                cp = *argv;
                if (*cp++ != '-')
                        break;
                if (*cp == '-' && cp[1] == 0) {
                        argv++;
                        argc--;
                        break;
                }
                while ((c = *cp++)) {
                        switch (c) {
                        case 'p':
                                *use_syspath = 1;
                                break;
                        default:
                                /* run 'typecmd' for other options */
                                return 0;
                        }
                }
        }
        return sv_argc - argc;
}

volatile int vforking = 0;

/*
 * Execute a simple command.
 */

STATIC void
evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
{
        struct stackmark smark;
        union node *argp;
        struct arglist arglist;
        struct arglist varlist;
        volatile int flags = flgs;
        char ** volatile argv;
        volatile int argc;
        char **envp;
        int varflag;
        struct strlist *sp;
        volatile int mode;
        int pip[2];
        struct cmdentry cmdentry;
        struct job * volatile jp;
        struct jmploc jmploc;
        struct jmploc *volatile savehandler = NULL;
        const char *volatile savecmdname;
        volatile struct shparam saveparam;
        struct localvar *volatile savelocalvars;
        struct parsefile *volatile savetopfile;
        volatile int e;
        char * volatile lastarg;
        const char * volatile path = pathval();
        volatile int temp_path;
        const int savefuncline = funclinebase;
        const int savefuncabs = funclineabs;
        volatile int cmd_flags = 0;

        vforking = 0;
        /* First expand the arguments. */
        CTRACE(DBG_EVAL, ("evalcommand(%p, %d) called [%s]\n", cmd, flags,
            cmd->ncmd.args ? cmd->ncmd.args->narg.text : ""));
        setstackmark(&smark);
        back_exitstatus = 0;

        line_number = cmd->ncmd.lineno;

        arglist.lastp = &arglist.list;
        varflag = 1;
        /* Expand arguments, ignoring the initial 'name=value' ones */
        for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
                if (varflag && isassignment(argp->narg.text))
                        continue;
                varflag = 0;
                line_number = argp->narg.lineno;
                expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
        }
        *arglist.lastp = NULL;

        expredir(cmd->ncmd.redirect);

        /* Now do the initial 'name=value' ones we skipped above */
        varlist.lastp = &varlist.list;
        for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
                line_number = argp->narg.lineno;
                if (!isassignment(argp->narg.text))
                        break;
                /* EXP_CASE handles CTL* chars in expansions properly */
                expandarg(argp, &varlist, EXP_VARTILDE | EXP_CASE);
        }
        *varlist.lastp = NULL;

        argc = 0;
        for (sp = arglist.list ; sp ; sp = sp->next)
                argc++;
        argv = stalloc(sizeof (char *) * (argc + 1));

        for (sp = arglist.list ; sp ; sp = sp->next) {
                VTRACE(DBG_EVAL, ("evalcommand arg: %s\n", sp->text));
                *argv++ = sp->text;
        }
        *argv = NULL;
        lastarg = NULL;
        if (iflag && funcnest == 0 && argc > 0)
                lastarg = argv[-1];
        argv -= argc;

        /* Print the command if xflag is set. */
        if (xflag) {
                char sep = 0;
                union node *rn;

                outxstr(expandstr(ps4val(), line_number));
                for (sp = varlist.list ; sp ; sp = sp->next) {
                        char *p;

                        if (sep != 0)
                                outxc(sep);

                        /*
                         * The "var=" part should not be quoted, regardless
                         * of the value, or it would not represent an
                         * assignment, but rather a command
                         */
                        p = strchr(sp->text, '=');
                        if (p != NULL) {
                                *p = '\0';      /*XXX*/
                                outxshstr(sp->text);
                                outxc('=');
                                *p++ = '=';     /*XXX*/
                        } else
                                p = sp->text;
                        outxshstr(p);
                        sep = ' ';
                }
                for (sp = arglist.list ; sp ; sp = sp->next) {
                        if (sep != 0)
                                outxc(sep);
                        outxshstr(sp->text);
                        sep = ' ';
                }
                for (rn = cmd->ncmd.redirect; rn; rn = rn->nfile.next)
                        if (outredir(outx, rn, sep))
                                sep = ' ';
                outxc('\n');
                flushout(outx);
        }

        /* Now locate the command. */
        if (argc == 0) {
                /*
                 * the empty command begins as a normal builtin, and
                 * remains that way while redirects are processed, then
                 * will become special before we get to doing the
                 * var assigns.
                 */
                cmdentry.cmdtype = CMDBUILTIN;
                cmdentry.u.bltin = bltincmd;
                VTRACE(DBG_CMDS, ("No command name, assume \"command\"\n"));
        } else {
                static const char PATH[] = "PATH=";

                /*
                 * Modify the command lookup path, if a PATH= assignment
                 * is present
                 */
                for (sp = varlist.list; sp; sp = sp->next)
                        if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0)
                                path = sp->text + sizeof(PATH) - 1;

                do {
                        int argsused, use_syspath;

                        find_command(argv[0], &cmdentry, cmd_flags, path);
                        VTRACE(DBG_CMDS, ("Command %s type %d\n", argv[0],
                            cmdentry.cmdtype));
#if 0
                        /*
                         * This short circuits all of the processing that
                         * should be done (including processing the
                         * redirects), so just don't ...
                         *
                         * (eventually this whole #if'd block will vanish)
                         */
                        if (cmdentry.cmdtype == CMDUNKNOWN) {
                                exitstatus = 127;
                                flushout(&errout);
                                goto out;
                        }
#endif

                        /* implement the 'command' builtin here */
                        if (cmdentry.cmdtype != CMDBUILTIN ||
                            cmdentry.u.bltin != bltincmd)
                                break;
                        VTRACE(DBG_CMDS, ("Command \"command\"\n"));
                        cmd_flags |= DO_NOFUNC;
                        argsused = parse_command_args(argc, argv, &use_syspath);
                        if (argsused == 0) {
                                /* use 'type' builtin to display info */
                                VTRACE(DBG_CMDS,
                                    ("Command \"command\" -> \"type\"\n"));
                                cmdentry.u.bltin = typecmd;
                                break;
                        }
                        argc -= argsused;
                        argv += argsused;
                        if (use_syspath)
                                path = syspath() + 5;
                } while (argc != 0);
                if (cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC)
                        /* posix mandates that 'command <splbltin>' act as if
                           <splbltin> was a normal builtin */
                        cmdentry.cmdtype = CMDBUILTIN;
        }

        /*
         * When traps are invalid, we permit the following:
         *      trap
         *      command trap
         *      eval trap
         *      command eval trap
         *      eval command trap
         * without zapping the traps completely, in all other cases we do.
         * Function calls also do not zap the traps (but commands they execute
         * probably will) - this allows a function like
         *      trapstate() { trap -p; }
         * called as save_traps=$(trapstate).
         *
         * The test here permits eval "anything" but when evalstring() comes
         * back here again, the "anything" will be validated.
         * This means we can actually do:
         *      eval eval eval command eval eval command trap
         * as long as we end up with just "trap"
         *
         * We permit "command" by allowing CMDBUILTIN as well as CMDSPLBLTIN
         *
         * trapcmd() takes care of doing free_traps() if it is needed there.
         */
        if (traps_invalid &&
            cmdentry.cmdtype != CMDFUNCTION &&
            ((cmdentry.cmdtype!=CMDSPLBLTIN && cmdentry.cmdtype!=CMDBUILTIN) ||
             (cmdentry.u.bltin != trapcmd && cmdentry.u.bltin != evalcmd)))
                free_traps();

        /* Fork off a child process if necessary. */
        if (cmd->ncmd.backgnd
          || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
             && (have_traps() || (flags & EV_EXIT) == 0))
#ifdef notyet                   /* EV_BACKCMD is never set currently */
                        /* this will need more work if/when it gets used */
          || ((flags & EV_BACKCMD) != 0
             && (cmdentry.cmdtype != CMDBUILTIN
                 && cmdentry.cmdtype != CMDSPLBLTIN)
               || cmdentry.u.bltin == dotcmd
               || cmdentry.u.bltin == evalcmd)
#endif
         ) {
                INTOFF;
                jp = makejob(cmd, 1);
                mode = cmd->ncmd.backgnd;
                if (flags & EV_BACKCMD) {
                        mode = FORK_NOJOB;
                        if (sh_pipe(pip) < 0)
                                error("Pipe call failed");
                }
#ifdef DO_SHAREDVFORK
                /* It is essential that if DO_SHAREDVFORK is defined that the
                 * child's address space is actually shared with the parent as
                 * we rely on this.
                 */
                if (usefork == 0 && cmdentry.cmdtype == CMDNORMAL &&
                    (!cmd->ncmd.backgnd || cmd->ncmd.redirect == NULL)) {
                        pid_t   pid;
                        int serrno;
                        sigset_t block_all, olds;

                        (void)sigfillset(&block_all);
                        savelocalvars = localvars;
                        localvars = NULL;

                        /* see jobs.c:forkshell() for explanation */
                        (void)sigprocmask(SIG_BLOCK, &block_all, &olds);
                        vforking = 1;
        VFORK_BLOCK
                        switch (pid = vfork()) {
                        case -1:
                                serrno = errno;
                                vforking = 0;
                                (void)sigprocmask(SIG_SETMASK, &olds, NULL);
                                VTRACE(DBG_EVAL, ("vfork() failed, errno=%d\n",
                                    serrno));
                                INTON;
                                error("Cannot vfork (%s)", strerror(serrno));
                                break;
                        case 0:
                                /*
                                 * Make sure that exceptions only unwind to
                                 * after the vfork(2)
                                 */
                                SHELL_FORKED();
                                if (setjmp(jmploc.loc)) {
                                        VTRACE(DBG_EVAL|DBG_ERRS|
                                          DBG_PROCS|DBG_CMDS|DBG_TRAP,
                                          ("vfork child exit exception:%d "
                                           "exitstatus:%d exerrno:%d\n",
                                           exception, exitstatus, exerrno));

                                        if (exception == EXSHELLPROC) {
                                                /*
                                                 * We can't progress with the
                                                 * vfork, so, set vforking = 2
                                                 * so the parent knows,
                                                 * and _exit();
                                                 */
                                                vforking = 2;
                                                _exit(0);
                                        } else {
                                                _exit(exception == EXEXIT ?
                                                    exitstatus : exerrno);
                                        }
                                }
                                savehandler = handler;
                                handler = &jmploc;
                                listmklocal(varlist.list,
                                    VDOEXPORT | VEXPORT | VNOFUNC);
                                forkchild(jp, cmd, mode, vforking, &olds);
                                break;
                        default:
                                VFORK_UNDO();
                                                /* restore from vfork(2) */
                                /*
                                 * We need to know what happened to the
                                 * vfork() (currently in vforking) but
                                 * we need to restore signals asap, and
                                 * vforking cannot remain set after that.
                                 * so ...
                                 */
                                {
                                    int vforkresult = vforking;
                                
                                    vforking = 0;
                                    (void)sigprocmask(SIG_SETMASK, &olds, NULL);
                                
                                    CTRACE(DBG_PROCS|DBG_CMDS,
                                        ("parent after vfork - vforking=%d\n",
                                          vforkresult));

                                    handler = savehandler;
                                    poplocalvars();
                                    localvars = savelocalvars;
                                    if (vforkresult == 2) {
                                        /*
                                         * obliterate the deceased zombie
                                         * (known to have exited already)
                                         */
                                        (void)waitpid(pid, NULL, 0);

                                        /*
                                         * We need to progress in a
                                         * normal fork fashion
                                         */
                                        goto normal_fork;
                                    }
                                }

                                /*
                                 * Here the child has left home,
                                 * getting on with its life, so
                                 * so must we...
                                 */
                                forkparent(jp, cmd, mode, pid);
                                goto parent;
                        }
        VFORK_END
                } else {
 normal_fork:
#endif
                        if (forkshell(jp, cmd, mode) != 0)
                                goto parent;    /* at end of routine */
                        CTRACE(DBG_PROCS|DBG_CMDS, ("Child sets EV_EXIT\n"));
                        flags |= EV_EXIT;
                        FORCEINTON;
#ifdef DO_SHAREDVFORK
                }
#endif
                if (flags & EV_BACKCMD) {
                        if (!vforking) {
                                FORCEINTON;
                        }
                        close(pip[0]);
                        movefd(pip[1], 1);
                }
                flags |= EV_EXIT;
        }

        /*
         * This is the child process if a fork occurred.
         * (or "the" process if there was no fork at all)
         *
         * Execute the command.
         */

        switch (cmdentry.cmdtype) {
                volatile int saved;
                struct funcdef * volatile savefunc;
                const char * volatile savectx;

        case CMDFUNCTION:
                VXTRACE(DBG_EVAL, ("Shell function%s:  ",vforking?" VF":""),
                    trargs(argv));
                redirect(cmd->ncmd.redirect, REDIR_KEEP | (saved =
                        !(flags & EV_EXIT) || have_traps() ? REDIR_PUSH : 0));
                saveparam = shellparam;
                shellparam.malloc = 0;
                shellparam.reset = 1;
                shellparam.nparam = argc - 1;
                shellparam.p = argv + 1;
                shellparam.optnext = NULL;
                INTOFF;
                savelocalvars = localvars;
                savectx = currentcontext;
                localvars = NULL;
                reffunc(savefunc = cmdentry.u.func);
                INTON;
                if (setjmp(jmploc.loc)) {
                        if (exception == EXSHELLPROC) {
                                freeparam((volatile struct shparam *)
                                    &saveparam);
                        } else {
                                freeparam(&shellparam);
                                shellparam = saveparam;
                        }
                        if (saved)
                                popredir(POPREDIR_UNDO);
                        unreffunc(savefunc);
                        poplocalvars();
                        localvars = savelocalvars;
                        funclinebase = savefuncline;
                        funclineabs = savefuncabs;
                        currentcontext = savectx;
                        handler = savehandler;
                        longjmp(handler->loc, 1);
                }
                savehandler = handler;
                handler = &jmploc;
                if (cmdentry.u.func) {
                        if (cmdentry.lno_frel)
                                funclinebase = cmdentry.lineno - 1;
                        else
                                funclinebase = 0;
                        funclineabs = cmdentry.lineno;
                        currentcontext = argv[0];

                        VTRACE(DBG_EVAL,
                          ("function: node: %d '%s' # %d%s; funclinebase=%d\n",
                            getfuncnode(cmdentry.u.func)->type,
                            NODETYPENAME(getfuncnode(cmdentry.u.func)->type),
                            cmdentry.lineno, cmdentry.lno_frel?" (=1)":"",
                            funclinebase));
                }
                listmklocal(varlist.list, VDOEXPORT | VEXPORT);
                /* stop shell blowing its stack */
                if (++funcnest > 1000)
                        error("too many nested function calls");
                evaltree(getfuncnode(cmdentry.u.func),
                    flags & (EV_TESTED|EV_EXIT));
                funcnest--;
                INTOFF;
                unreffunc(cmdentry.u.func);
                poplocalvars();
                localvars = savelocalvars;
                funclinebase = savefuncline;
                funclineabs = savefuncabs;
                currentcontext = savectx;
                freeparam(&shellparam);
                shellparam = saveparam;
                handler = savehandler;
                if (saved)
                        popredir(POPREDIR_UNDO);
                INTON;
                if (evalskip == SKIPFUNC) {
                        evalskip = SKIPNONE;
                        skipcount = 0;
                }
                if (flags & EV_EXIT)
                        exitshell(exitstatus);
                break;

        case CMDSPLBLTIN:
                VTRACE(DBG_EVAL, ("special "));
        case CMDBUILTIN:
                VXTRACE(DBG_EVAL, ("builtin command [%d]%s:  ", argc,
                    vforking ? " VF" : ""), trargs(argv));

                if (cmdentry.u.bltin == execcmd) {
                        char **ap;

                        /*
                         * Work out how we should process redirections
                         * on the "exec" command.   We need REDIR_KEEP
                         * if we must not set close-on-exec, and REDIR_PUSH
                         * if we need to be able to undo them (in the
                         * exec command, only on  some kind of error).
                         *
                         * Skip "exec" (argv[0]) then examine args.
                         *
                         * This must be done manually, as nextopt()
                         * hasn't been init'd for this command yet.
                         * And it won't be until after redirections are done.
                         *
                         * "exec" currently takes no options (except "--"),
                         * but might one day, and this needs to keep working,
                         * so do it, kind of, properly.
                         *
                         * Note in the common cases argv[1] will be NULL
                         * (for exec just setting up redirectons) or will
                         * not start with a '-' ("exec cmd") so normally
                         * this loop will either never start or will break
                         * at the first test of the first iteration.
                         */
                        for (ap = argv + 1; *ap != NULL; ap++) {

                                if (ap[0][0] != '-')
                                        break;

                                if (ap[0][1] == '\0')   /* "exec -" */
                                        break;          /* or continue ?? */

                                if (ap[0][1] == '-' && ap[0][2] == '\0') {
                                        ap++;
                                        break;
                                }

#if defined(DUMMY_EXAMPLE_CODE) && 0
                                /*
                                 * if options are added to "exec" then
                                 * any which take an arg (like the common
                                 * in other shells "-a cmdname") need to
                                 * be recognised here, lest "cmdname" be
                                 * thought to be the cmd to exec
                                 */

                                for (char *op = ap[0] + 1; *op; op++) {
                                        switch (*op) {
                                        case 'a':
                                        case any others similar:
                                                /* options needing an optarg */
                                                if (op[1] == '\0' && ap[1])
                                                        ap++;
                                                break;

                                        default:
                                                /* options with no optarg */
                                                continue;
                                        }
                                        break;
                                }
#endif /* DUMMY EXAMPLE CODE */
                        }

                        if (*ap != NULL)
                                mode = REDIR_KEEP;      /* exec cmd <... */
                        else
                                mode = 0;               /* exec < .... */

                        /*
                         * always save old fd setup in case of error()
                         * execcmd() will undo this if no error occurs
                         * (that is, in the case the shell has not vanished)
                         */
                        mode |= REDIR_PUSH;
                } else                  /* any builtin execpt "exec" */
                        mode = REDIR_PUSH | REDIR_KEEP;

                if (flags == EV_BACKCMD) {
                        memout.nleft = 0;
                        memout.nextc = memout.buf;
                        memout.bufsize = 64;
                        mode |= REDIR_BACKQ;
                }

                e = -1;
                savecmdname = commandname;
                savetopfile = getcurrentfile();
                savehandler = handler;
                temp_path = 0;

                if (!setjmp(jmploc.loc)) {
                        handler = &jmploc;

                        /*
                         * We need to ensure the command hash table isn't
                         * corrupted by temporary PATH assignments.
                         * However we must ensure the 'local' command works!
                         */
                        if (path != pathval() && (cmdentry.u.bltin == hashcmd ||
                            cmdentry.u.bltin == typecmd)) {
                                savelocalvars = localvars;
                                localvars = 0;
                                temp_path = 1;
                                mklocal(path - 5 /* PATH= */, 0);
                        }
                        redirect(cmd->ncmd.redirect, mode);

                        /*
                         * the empty command is regarded as a normal
                         * builtin for the purposes of redirects, but
                         * is a special builtin for var assigns.
                         * (unless we are the "command" command.)
                         */
                        if (argc == 0 && !(cmd_flags & DO_NOFUNC))
                                cmdentry.cmdtype = CMDSPLBLTIN;

                        /* exec is a special builtin, but needs this list... */
                        cmdenviron = varlist.list;
                        /* we must check 'readonly' flag for all builtins */
                        listsetvar(varlist.list,
                                cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET);
                        commandname = argv[0];
                        /* initialize nextopt */
                        argptr = argv + 1;
                        optptr = NULL;
                        /* and getopt */
                        optreset = 1;
                        optind = 1;
                        builtin_flags = flags;
                        clr_err(out1);  /* discard previous I/O errors */
                        exitstatus = cmdentry.u.bltin(argc, argv);
                } else {
                        e = exception;
                        if (e == EXINT)
                                exitstatus = SIGINT + 128;
                        else if (e == EXEXEC)
                                exitstatus = exerrno;
                        else if (e != EXEXIT)
                                exitstatus = 2;
                }
                handler = savehandler;
                flushall();
                out1 = &output;
                out2 = &errout;
                freestdout();
                if (temp_path) {
                        poplocalvars();
                        localvars = savelocalvars;
                }
                cmdenviron = NULL;
                if (e != EXSHELLPROC) {
                        commandname = savecmdname;
                        if (flags & EV_EXIT)
                                exitshell(exitstatus);
                }
                if (e != -1) {
                        if ((e != EXERROR && e != EXEXEC)
                            || cmdentry.cmdtype == CMDSPLBLTIN)
                                exraise(e);
                        popfilesupto(savetopfile);
                        FORCEINTON;
                }

                if (cmdentry.u.bltin != execcmd)
                        popredir(POPREDIR_UNDO);

                if (flags == EV_BACKCMD) {
                        backcmd->buf = memout.buf;
                        backcmd->nleft = memout.nextc - memout.buf;
                        memout.buf = NULL;
                }
                break;

        default:
                VXTRACE(DBG_EVAL, ("normal command%s:  ", vforking?" VF":""),
                    trargs(argv));
                redirect(cmd->ncmd.redirect,
                    (vforking ? REDIR_VFORK : 0) | REDIR_KEEP);
                if (!vforking)
                        for (sp = varlist.list ; sp ; sp = sp->next)
                                setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK);
                envp = environment();
                shellexec(argv, envp, path, cmdentry.u.index, vforking);
                break;
        }
        goto out;

 parent:                        /* parent process gets here (if we forked) */

        exitstatus = 0;         /* if not altered just below */
        if (mode == FORK_FG) {  /* argument to fork */
                exitstatus = waitforjob(jp);
        } else if (mode == FORK_NOJOB) {
                backcmd->fd = pip[0];
                close(pip[1]);
                backcmd->jp = jp;
        } else
                jobstarted(jp);

        FORCEINTON;

 out:
        if (lastarg)
                /* implement $_ for whatever use that really is */
                (void) setvarsafe("_", lastarg, VNOERROR);
        popstackmark(&smark);
}


/*
 * Search for a command.  This is called before we fork so that the
 * location of the command will be available in the parent as well as
 * the child.  The check for "goodname" is an overly conservative
 * check that the name will not be subject to expansion.
 */

STATIC void
prehash(union node *n)
{
        struct cmdentry entry;

        if (n && n->type == NCMD && n->ncmd.args)
                if (goodname(n->ncmd.args->narg.text))
                        find_command(n->ncmd.args->narg.text, &entry, 0,
                                     pathval());
}

int
in_function(void)
{
        return funcnest;
}

enum skipstate
current_skipstate(void)
{
        return evalskip;
}

void
save_skipstate(struct skipsave *p)
{
        *p = s_k_i_p;
}

void
restore_skipstate(const struct skipsave *p)
{
        s_k_i_p = *p;
}

void
stop_skipping(void)
{
        evalskip = SKIPNONE;
        skipcount = 0;
}

/*
 * Builtin commands.  Builtin commands whose functions are closely
 * tied to evaluation are implemented here.
 */

/*
 * No command given.
 */

int
bltincmd(int argc, char **argv)
{
        /*
         * Preserve exitstatus of a previous possible redirection
         * as POSIX mandates
         */
        return back_exitstatus;
}


/*
 * Handle break and continue commands.  Break, continue, and return are
 * all handled by setting the evalskip flag.  The evaluation routines
 * above all check this flag, and if it is set they start skipping
 * commands rather than executing them.  The variable skipcount is
 * the number of loops to break/continue, or the number of function
 * levels to return.  (The latter is always 1.)  It should probably
 * be an error to break out of more loops than exist, but it isn't
 * in the standard shell so we don't make it one here.
 */

int
breakcmd(int argc, char **argv)
{
        int n = argc > 1 ? number(argv[1]) : 1;

        if (n <= 0)
                error("invalid count: %d", n);
        if (n > loopnest)
                n = loopnest;
        if (n > 0) {
                evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
                skipcount = n;
        }
        return 0;
}

int
dotcmd(int argc, char **argv)
{
        exitstatus = 0;

        (void) nextopt(NULL);           /* ignore a leading "--" */

        if (*argptr != NULL) {          /* That's what SVR2 does */
                char *fullname;
                /*
                 * dot_funcnest needs to be 0 when not in a dotcmd, so it
                 * cannot be restored with (funcnest + 1).
                 */
                int dot_funcnest_old;
                struct stackmark smark;

                setstackmark(&smark);
                fullname = find_dot_file(*argptr);
                setinputfile(fullname, 1);
                commandname = fullname;
                dot_funcnest_old = dot_funcnest;
                dot_funcnest = funcnest + 1;
                cmdloop(0);
                dot_funcnest = dot_funcnest_old;
                popfile();
                popstackmark(&smark);
        }
        return exitstatus;
}

/*
 * allow dotfile function nesting to be manipulated
 * (for read_profile).  This allows profile files to
 * be treated as if they were used as '.' commands,
 * (approximately) and in particular, for "return" to work.
 */
int
set_dot_funcnest(int new)
{
        int rv = dot_funcnest;

        if (new >= 0)
                dot_funcnest = new;

        return rv;
}

/*
 * Take commands from a file.  To be compatible we should do a path
 * search for the file, which is necessary to find sub-commands.
 */

STATIC char *
find_dot_file(char *basename)
{
        char *fullname;
        const char *path = pathval();
        struct stat statb;

        /* don't try this for absolute or relative paths */
        if (strchr(basename, '/')) {
                if (stat(basename, &statb) == 0) {
                        if (S_ISDIR(statb.st_mode))
                                error("%s: is a directory", basename);
                        if (S_ISBLK(statb.st_mode))
                                error("%s: is a block device", basename);
                        return basename;
                }
        } else while ((fullname = padvance(&path, basename, 1)) != NULL) {
                if ((stat(fullname, &statb) == 0)) {
                        /* weird format is to ease future code... */
                        if (S_ISDIR(statb.st_mode) || S_ISBLK(statb.st_mode))
                                ;
#if notyet
                        else if (unreadable()) {
                                /*
                                 * testing this via st_mode is ugly to get
                                 * correct (and would ignore ACLs).
                                 * better way is just to open the file.
                                 * But doing that here would (currently)
                                 * mean opening the file twice, which
                                 * might not be safe.  So, defer this
                                 * test until code is restructures so
                                 * we can return a fd.   Then we also
                                 * get to fix the mem leak just below...
                                 */
                        }
#endif
                        else {
                                /*
                                 * Don't bother freeing here, since
                                 * it will be freed by the caller.
                                 * XXX no it won't - a bug for later.
                                 */
                                return fullname;
                        }
                }
                stunalloc(fullname);
        }

        /* not found in the PATH */
        error("%s: not found", basename);
        /* NOTREACHED */
}



/*
 * The return command.
 *
 * Quoth the POSIX standard:
 *   The return utility shall cause the shell to stop executing the current
 *   function or dot script. If the shell is not currently executing
 *   a function or dot script, the results are unspecified.
 *
 * As for the unspecified part, there seems to be no de-facto standard: bash
 * ignores the return with a warning, zsh ignores the return in interactive
 * mode but seems to liken it to exit in a script.  (checked May 2014)
 *
 * We choose to silently ignore the return.  Older versions of this shell
 * set evalskip to SKIPFILE causing the shell to (indirectly) exit.  This
 * had at least the problem of circumventing the check for stopped jobs,
 * which would occur for exit or ^D.
 */

int
returncmd(int argc, char **argv)
{
        int ret = argc > 1 ? number(argv[1]) : exitstatus;

        if ((dot_funcnest == 0 && funcnest)
            || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) {
                evalskip = SKIPFUNC;
                skipcount = 1;
        } else if (dot_funcnest > 0) {
                evalskip = SKIPFILE;
                skipcount = 1;
        } else {
                /* XXX: should a warning be issued? */
                ret = 0;
        }

        return ret;
}


int
falsecmd(int argc, char **argv)
{
        return 1;
}


int
truecmd(int argc, char **argv)
{
        return 0;
}


int
execcmd(int argc, char **argv)
{
        /*
         * BEWARE: if any options are added here, they must
         * also be added in evalcommand(), look for "DUMMY_EXAMPLE_CODE"
         * for example code for there.   Here the options would be
         * processed completely normally.
         */
        (void) nextopt("");             /* ignore a leading "--" */

        if (*argptr) {
                struct strlist *sp;

                iflag = 0;              /* exit on error */
                mflag = 0;
                optschanged();
                for (sp = cmdenviron; sp; sp = sp->next)
                        setvareq(sp->text, VDOEXPORT|VEXPORT|VSTACK);
                shellexec(argptr, environment(), pathval(), 0, 0);
                /* NOTREACHED */
        }
        popredir(POPREDIR_PERMANENT);   /* make any redirections permanent */
        return 0;
}

static int
conv_time(clock_t ticks, char *seconds, size_t l)
{
        static clock_t tpm = 0;
        clock_t mins;
        int i;

        if (!tpm)
                tpm = sysconf(_SC_CLK_TCK) * 60;

        mins = ticks / tpm;
        snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm );

        if (seconds[0] == '6' && seconds[1] == '0') {
                /* 59.99995 got rounded up... */
                mins++;
                strlcpy(seconds, "0.0", l);
                return mins;
        }

        /* suppress trailing zeros */
        i = strlen(seconds) - 1;
        for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--)
                seconds[i] = 0;
        return mins;
}

int
timescmd(int argc, char **argv)
{
        struct tms tms;
        int u, s, cu, cs;
        char us[8], ss[8], cus[8], css[8];

        nextopt("");

        times(&tms);

        u = conv_time(tms.tms_utime, us, sizeof(us));
        s = conv_time(tms.tms_stime, ss, sizeof(ss));
        cu = conv_time(tms.tms_cutime, cus, sizeof(cus));
        cs = conv_time(tms.tms_cstime, css, sizeof(css));

        outfmt(out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n",
                u, us, s, ss, cu, cus, cs, css);

        return 0;
}