1 /* $NetBSD: eval.c,v 1.97 2009/10/06 20:05:10 christos Exp $ */
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 #include <sys/cdefs.h>
38 static char sccsid
[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95";
40 __RCSID("$NetBSD: eval.c,v 1.97 2009/10/06 20:05:10 christos Exp $");
49 #include <sys/fcntl.h>
50 #include <sys/times.h>
51 #include <sys/param.h>
52 #include <sys/types.h>
54 #include <sys/sysctl.h>
81 #include "myhistedit.h"
85 /* flags in argument to evaltree */
86 #define EV_EXIT 01 /* exit after evaluating tree */
87 #define EV_TESTED 02 /* exit status is checked; ignore -e flag */
88 #define EV_BACKCMD 04 /* command executing within back quotes */
90 int evalskip
; /* set if we are skipping commands */
91 STATIC
int skipcount
; /* number of levels to skip */
92 MKINIT
int loopnest
; /* current loop nesting level */
93 int funcnest
; /* depth of function calls */
96 const char *commandname
;
97 struct strlist
*cmdenviron
;
98 int exitstatus
; /* exit status of last command */
99 int back_exitstatus
; /* exit status of backquoted command */
102 STATIC
void evalloop(union node
*, int);
103 STATIC
void evalfor(union node
*, int);
104 STATIC
void evalcase(union node
*, int);
105 STATIC
void evalsubshell(union node
*, int);
106 STATIC
void expredir(union node
*);
107 STATIC
void evalpipe(union node
*);
108 STATIC
void evalcommand(union node
*, int, struct backcmd
*);
109 STATIC
void prehash(union node
*);
113 * Called to reset things after an exception.
139 nfd
= fcntl(fds
[0], F_DUPFD
, 3);
147 nfd
= fcntl(fds
[1], F_DUPFD
, 3);
162 evalcmd(int argc
, char **argv
)
171 STARTSTACKSTR(concat
);
175 STPUTC(*p
++, concat
);
176 if ((p
= *ap
++) == NULL
)
180 STPUTC('\0', concat
);
181 p
= grabstackstr(concat
);
183 evalstring(p
, EV_TESTED
);
190 * Execute a command or commands contained in a string.
194 evalstring(char *s
, int flag
)
197 struct stackmark smark
;
199 setstackmark(&smark
);
200 setinputstring(s
, 1);
202 while ((n
= parsecmd(0)) != NEOF
) {
204 popstackmark(&smark
);
207 popstackmark(&smark
);
213 * Evaluate a parse tree. The value is left in the global variable
218 evaltree(union node
*n
, int flags
)
224 TRACE(("evaltree(NULL) called\n"));
229 displayhist
= 1; /* show history substitutions done with fc */
231 TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
232 getpid(), n
, n
->type
, flags
));
235 evaltree(n
->nbinary
.ch1
, flags
& EV_TESTED
);
238 evaltree(n
->nbinary
.ch2
, flags
);
241 evaltree(n
->nbinary
.ch1
, EV_TESTED
);
242 if (evalskip
|| exitstatus
!= 0)
244 evaltree(n
->nbinary
.ch2
, flags
);
247 evaltree(n
->nbinary
.ch1
, EV_TESTED
);
248 if (evalskip
|| exitstatus
== 0)
250 evaltree(n
->nbinary
.ch2
, flags
);
253 expredir(n
->nredir
.redirect
);
254 redirect(n
->nredir
.redirect
, REDIR_PUSH
);
255 evaltree(n
->nredir
.n
, flags
);
259 evalsubshell(n
, flags
);
260 do_etest
= !(flags
& EV_TESTED
);
263 evalsubshell(n
, flags
);
266 evaltree(n
->nif
.test
, EV_TESTED
);
270 evaltree(n
->nif
.ifpart
, flags
);
271 else if (n
->nif
.elsepart
)
272 evaltree(n
->nif
.elsepart
, flags
);
288 defun(n
->narg
.text
, n
->narg
.next
);
292 evaltree(n
->nnot
.com
, EV_TESTED
);
293 exitstatus
= !exitstatus
;
297 do_etest
= !(flags
& EV_TESTED
);
300 evalcommand(n
, flags
, (struct backcmd
*)NULL
);
301 do_etest
= !(flags
& EV_TESTED
);
304 out1fmt("Node type = %d\n", n
->type
);
311 if ((flags
& EV_EXIT
) != 0 || (eflag
&& exitstatus
!= 0 && do_etest
))
312 exitshell(exitstatus
);
317 evalloop(union node
*n
, int flags
)
324 evaltree(n
->nbinary
.ch1
, EV_TESTED
);
326 skipping
: if (evalskip
== SKIPCONT
&& --skipcount
<= 0) {
330 if (evalskip
== SKIPBREAK
&& --skipcount
<= 0)
334 if (n
->type
== NWHILE
) {
341 evaltree(n
->nbinary
.ch2
, flags
& EV_TESTED
);
353 evalfor(union node
*n
, int flags
)
355 struct arglist arglist
;
358 struct stackmark smark
;
361 setstackmark(&smark
);
362 arglist
.lastp
= &arglist
.list
;
363 for (argp
= n
->nfor
.args
; argp
; argp
= argp
->narg
.next
) {
364 expandarg(argp
, &arglist
, EXP_FULL
| EXP_TILDE
);
368 *arglist
.lastp
= NULL
;
371 for (sp
= arglist
.list
; sp
; sp
= sp
->next
) {
372 setvar(n
->nfor
.var
, sp
->text
, 0);
373 evaltree(n
->nfor
.body
, flags
& EV_TESTED
);
376 if (evalskip
== SKIPCONT
&& --skipcount
<= 0) {
380 if (evalskip
== SKIPBREAK
&& --skipcount
<= 0)
388 popstackmark(&smark
);
394 evalcase(union node
*n
, int flags
)
398 struct arglist arglist
;
399 struct stackmark smark
;
402 setstackmark(&smark
);
403 arglist
.lastp
= &arglist
.list
;
404 expandarg(n
->ncase
.expr
, &arglist
, EXP_TILDE
);
405 for (cp
= n
->ncase
.cases
; cp
&& evalskip
== 0 ; cp
= cp
->nclist
.next
) {
406 for (patp
= cp
->nclist
.pattern
; patp
; patp
= patp
->narg
.next
) {
407 if (casematch(patp
, arglist
.list
->text
)) {
409 evaltree(cp
->nclist
.body
, flags
);
418 popstackmark(&smark
);
424 * Kick off a subshell to evaluate a tree.
428 evalsubshell(union node
*n
, int flags
)
431 int backgnd
= (n
->type
== NBACKGND
);
433 expredir(n
->nredir
.redirect
);
436 if (forkshell(jp
, n
, backgnd
? FORK_BG
: FORK_FG
) == 0) {
440 redirect(n
->nredir
.redirect
, 0);
442 evaltree(n
->nredir
.n
, flags
| EV_EXIT
);
445 exitstatus
= waitforjob(jp
);
452 * Compute the names of the files in a redirection list.
456 expredir(union node
*n
)
460 for (redir
= n
; redir
; redir
= redir
->nfile
.next
) {
463 switch (redir
->type
) {
469 expandarg(redir
->nfile
.fname
, &fn
, EXP_TILDE
| EXP_REDIR
);
470 redir
->nfile
.expfname
= fn
.list
->text
;
474 if (redir
->ndup
.vname
) {
475 expandarg(redir
->ndup
.vname
, &fn
, EXP_FULL
| EXP_TILDE
);
476 fixredir(redir
, fn
.list
->text
, 1);
486 * Evaluate a pipeline. All the processes in the pipeline are children
487 * of the process creating the pipeline. (This differs from some versions
488 * of the shell, which make the last process in a pipeline the parent
493 evalpipe(union node
*n
)
501 TRACE(("evalpipe(0x%lx) called\n", (long)n
));
503 for (lp
= n
->npipe
.cmdlist
; lp
; lp
= lp
->next
)
506 jp
= makejob(n
, pipelen
);
508 for (lp
= n
->npipe
.cmdlist
; lp
; lp
= lp
->next
) {
512 if (sh_pipe(pip
) < 0) {
515 error("Pipe call failed");
518 if (forkshell(jp
, lp
->n
, n
->npipe
.backgnd
? FORK_BG
: FORK_FG
) == 0) {
533 evaltree(lp
->n
, EV_EXIT
);
540 if (n
->npipe
.backgnd
== 0) {
541 exitstatus
= waitforjob(jp
);
542 TRACE(("evalpipe: job done exit status %d\n", exitstatus
));
550 * Execute a command inside back quotes. If it's a builtin command, we
551 * want to save its output in a block obtained from malloc. Otherwise
552 * we fork off a subprocess and get the output of the command via a pipe.
553 * Should be called with interrupts off.
557 evalbackcmd(union node
*n
, struct backcmd
*result
)
561 struct stackmark smark
; /* unnecessary */
563 setstackmark(&smark
);
573 * For now we disable executing builtins in the same
574 * context as the shell, because we are not keeping
575 * enough state to recover from changes that are
576 * supposed only to affect subshells. eg. echo "`cd /`"
578 if (n
->type
== NCMD
) {
579 exitstatus
= oexitstatus
;
580 evalcommand(n
, EV_BACKCMD
, result
);
585 if (sh_pipe(pip
) < 0)
586 error("Pipe call failed");
588 if (forkshell(jp
, n
, FORK_NOJOB
) == 0) {
597 evaltree(n
, EV_EXIT
);
606 popstackmark(&smark
);
607 TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
608 result
->fd
, result
->buf
, result
->nleft
, result
->jp
));
614 static char *sys_path
= NULL
;
615 static int mib
[] = {CTL_USER
, USER_CS_PATH
};
616 static char def_path
[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
619 if (sys_path
== NULL
) {
620 if (sysctl(mib
, 2, 0, &len
, 0, 0) != -1 &&
621 (sys_path
= ckmalloc(len
+ 5)) != NULL
&&
622 sysctl(mib
, 2, sys_path
+ 5, &len
, 0, 0) != -1) {
623 memcpy(sys_path
, "PATH=", 5);
626 /* something to keep things happy */
634 parse_command_args(int argc
, char **argv
, int *use_syspath
)
648 if (*cp
== '-' && cp
[1] == 0) {
653 while ((c
= *cp
++)) {
659 /* run 'typecmd' for other options */
664 return sv_argc
- argc
;
671 * Execute a simple command.
675 evalcommand(union node
*cmd
, int flgs
, struct backcmd
*backcmd
)
677 struct stackmark smark
;
679 struct arglist arglist
;
680 struct arglist varlist
;
681 volatile int flags
= flgs
;
682 char ** volatile argv
;
689 struct cmdentry cmdentry
;
690 struct job
* volatile jp
;
691 struct jmploc jmploc
;
692 struct jmploc
*volatile savehandler
= NULL
;
693 const char *volatile savecmdname
;
694 volatile struct shparam saveparam
;
695 struct localvar
*volatile savelocalvars
;
697 char * volatile lastarg
;
698 const char * volatile path
= pathval();
699 volatile int temp_path
;
702 /* First expand the arguments. */
703 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd
, flags
));
704 setstackmark(&smark
);
707 arglist
.lastp
= &arglist
.list
;
709 /* Expand arguments, ignoring the initial 'name=value' ones */
710 for (argp
= cmd
->ncmd
.args
; argp
; argp
= argp
->narg
.next
) {
711 char *p
= argp
->narg
.text
;
712 if (varflag
&& is_name(*p
)) {
715 } while (is_in_name(*p
));
719 expandarg(argp
, &arglist
, EXP_FULL
| EXP_TILDE
);
722 *arglist
.lastp
= NULL
;
724 expredir(cmd
->ncmd
.redirect
);
726 /* Now do the initial 'name=value' ones we skipped above */
727 varlist
.lastp
= &varlist
.list
;
728 for (argp
= cmd
->ncmd
.args
; argp
; argp
= argp
->narg
.next
) {
729 char *p
= argp
->narg
.text
;
734 while (is_in_name(*p
));
737 expandarg(argp
, &varlist
, EXP_VARTILDE
);
739 *varlist
.lastp
= NULL
;
742 for (sp
= arglist
.list
; sp
; sp
= sp
->next
)
744 argv
= stalloc(sizeof (char *) * (argc
+ 1));
746 for (sp
= arglist
.list
; sp
; sp
= sp
->next
) {
747 TRACE(("evalcommand arg: %s\n", sp
->text
));
752 if (iflag
&& funcnest
== 0 && argc
> 0)
756 /* Print the command if xflag is set. */
760 for (sp
= varlist
.list
; sp
; sp
= sp
->next
) {
766 for (sp
= arglist
.list
; sp
; sp
= sp
->next
) {
776 /* Now locate the command. */
778 cmdentry
.cmdtype
= CMDSPLBLTIN
;
779 cmdentry
.u
.bltin
= bltincmd
;
781 static const char PATH
[] = "PATH=";
782 int cmd_flags
= DO_ERR
;
785 * Modify the command lookup path, if a PATH= assignment
788 for (sp
= varlist
.list
; sp
; sp
= sp
->next
)
789 if (strncmp(sp
->text
, PATH
, sizeof(PATH
) - 1) == 0)
790 path
= sp
->text
+ sizeof(PATH
) - 1;
793 int argsused
, use_syspath
;
794 find_command(argv
[0], &cmdentry
, cmd_flags
, path
);
795 if (cmdentry
.cmdtype
== CMDUNKNOWN
) {
801 /* implement the 'command' builtin here */
802 if (cmdentry
.cmdtype
!= CMDBUILTIN
||
803 cmdentry
.u
.bltin
!= bltincmd
)
805 cmd_flags
|= DO_NOFUNC
;
806 argsused
= parse_command_args(argc
, argv
, &use_syspath
);
808 /* use 'type' builting to display info */
809 cmdentry
.u
.bltin
= typecmd
;
815 path
= syspath() + 5;
817 if (cmdentry
.cmdtype
== CMDSPLBLTIN
&& cmd_flags
& DO_NOFUNC
)
818 /* posix mandates that 'command <splbltin>' act as if
819 <splbltin> was a normal builtin */
820 cmdentry
.cmdtype
= CMDBUILTIN
;
823 /* Fork off a child process if necessary. */
824 if (cmd
->ncmd
.backgnd
|| (trap
[0] && (flags
& EV_EXIT
) != 0)
825 || (cmdentry
.cmdtype
== CMDNORMAL
&& (flags
& EV_EXIT
) == 0)
826 || ((flags
& EV_BACKCMD
) != 0
827 && ((cmdentry
.cmdtype
!= CMDBUILTIN
&& cmdentry
.cmdtype
!= CMDSPLBLTIN
)
828 || cmdentry
.u
.bltin
== dotcmd
829 || cmdentry
.u
.bltin
== evalcmd
))) {
831 jp
= makejob(cmd
, 1);
832 mode
= cmd
->ncmd
.backgnd
;
833 if (flags
& EV_BACKCMD
) {
835 if (sh_pipe(pip
) < 0)
836 error("Pipe call failed");
838 #ifdef DO_SHAREDVFORK
839 /* It is essential that if DO_SHAREDVFORK is defined that the
840 * child's address space is actually shared with the parent as
843 if (cmdentry
.cmdtype
== CMDNORMAL
) {
846 savelocalvars
= localvars
;
849 switch (pid
= vfork()) {
851 TRACE(("Vfork failed, errno=%d\n", errno
));
853 error("Cannot vfork");
856 /* Make sure that exceptions only unwind to
859 if (setjmp(jmploc
.loc
)) {
860 if (exception
== EXSHELLPROC
) {
861 /* We can't progress with the vfork,
862 * so, set vforked = 2 so the parent
863 * knows, and _exit();
871 savehandler
= handler
;
873 listmklocal(varlist
.list
, VEXPORT
| VNOFUNC
);
874 forkchild(jp
, cmd
, mode
, vforked
);
877 handler
= savehandler
; /* restore from vfork(2) */
879 localvars
= savelocalvars
;
883 (void)waitpid(pid
, NULL
, 0);
884 /* We need to progress in a normal fork fashion */
888 forkparent(jp
, cmd
, mode
, pid
);
894 if (forkshell(jp
, cmd
, mode
) != 0)
895 goto parent
; /* at end of routine */
897 #ifdef DO_SHAREDVFORK
900 if (flags
& EV_BACKCMD
) {
914 /* This is the child process if a fork occurred. */
915 /* Execute the command. */
916 switch (cmdentry
.cmdtype
) {
919 trputs("Shell function: "); trargs(argv
);
921 redirect(cmd
->ncmd
.redirect
, REDIR_PUSH
);
922 saveparam
= shellparam
;
923 shellparam
.malloc
= 0;
924 shellparam
.reset
= 1;
925 shellparam
.nparam
= argc
- 1;
926 shellparam
.p
= argv
+ 1;
927 shellparam
.optnext
= NULL
;
929 savelocalvars
= localvars
;
932 if (setjmp(jmploc
.loc
)) {
933 if (exception
== EXSHELLPROC
) {
934 freeparam((volatile struct shparam
*)
937 freeparam(&shellparam
);
938 shellparam
= saveparam
;
941 localvars
= savelocalvars
;
942 handler
= savehandler
;
943 longjmp(handler
->loc
, 1);
945 savehandler
= handler
;
947 listmklocal(varlist
.list
, 0);
948 /* stop shell blowing its stack */
949 if (++funcnest
> 1000)
950 error("too many nested function calls");
951 evaltree(cmdentry
.u
.func
, flags
& EV_TESTED
);
955 localvars
= savelocalvars
;
956 freeparam(&shellparam
);
957 shellparam
= saveparam
;
958 handler
= savehandler
;
961 if (evalskip
== SKIPFUNC
) {
966 exitshell(exitstatus
);
972 trputs("builtin command: "); trargs(argv
);
974 mode
= (cmdentry
.u
.bltin
== execcmd
) ? 0 : REDIR_PUSH
;
975 if (flags
== EV_BACKCMD
) {
977 memout
.nextc
= memout
.buf
;
982 savehandler
= handler
;
983 savecmdname
= commandname
;
985 if (!setjmp(jmploc
.loc
)) {
986 /* We need to ensure the command hash table isn't
987 * corruped by temporary PATH assignments.
988 * However we must ensure the 'local' command works!
990 if (path
!= pathval() && (cmdentry
.u
.bltin
== hashcmd
||
991 cmdentry
.u
.bltin
== typecmd
)) {
992 savelocalvars
= localvars
;
994 mklocal(path
- 5 /* PATH= */, 0);
998 redirect(cmd
->ncmd
.redirect
, mode
);
1000 /* exec is a special builtin, but needs this list... */
1001 cmdenviron
= varlist
.list
;
1002 /* we must check 'readonly' flag for all builtins */
1003 listsetvar(varlist
.list
,
1004 cmdentry
.cmdtype
== CMDSPLBLTIN
? 0 : VNOSET
);
1005 commandname
= argv
[0];
1006 /* initialize nextopt */
1012 exitstatus
= cmdentry
.u
.bltin(argc
, argv
);
1015 exitstatus
= e
== EXINT
? SIGINT
+ 128 :
1016 e
== EXEXEC
? exerrno
: 2;
1018 handler
= savehandler
;
1025 localvars
= savelocalvars
;
1028 if (e
!= EXSHELLPROC
) {
1029 commandname
= savecmdname
;
1030 if (flags
& EV_EXIT
)
1031 exitshell(exitstatus
);
1034 if ((e
!= EXERROR
&& e
!= EXEXEC
)
1035 || cmdentry
.cmdtype
== CMDSPLBLTIN
)
1039 if (cmdentry
.u
.bltin
!= execcmd
)
1041 if (flags
== EV_BACKCMD
) {
1042 backcmd
->buf
= memout
.buf
;
1043 backcmd
->nleft
= memout
.nextc
- memout
.buf
;
1050 trputs("normal command: "); trargs(argv
);
1052 clearredir(vforked
);
1053 redirect(cmd
->ncmd
.redirect
, vforked
? REDIR_VFORK
: 0);
1055 for (sp
= varlist
.list
; sp
; sp
= sp
->next
)
1056 setvareq(sp
->text
, VEXPORT
|VSTACK
);
1057 envp
= environment();
1058 shellexec(argv
, envp
, path
, cmdentry
.u
.index
, vforked
);
1063 parent
: /* parent process gets here (if we forked) */
1064 if (mode
== FORK_FG
) { /* argument to fork */
1065 exitstatus
= waitforjob(jp
);
1066 } else if (mode
== FORK_NOJOB
) {
1067 backcmd
->fd
= pip
[0];
1075 /* dsl: I think this is intended to be used to support
1076 * '_' in 'vi' command mode during line editing...
1077 * However I implemented that within libedit itself.
1079 setvar("_", lastarg
, 0);
1080 popstackmark(&smark
);
1085 * Search for a command. This is called before we fork so that the
1086 * location of the command will be available in the parent as well as
1087 * the child. The check for "goodname" is an overly conservative
1088 * check that the name will not be subject to expansion.
1092 prehash(union node
*n
)
1094 struct cmdentry entry
;
1096 if (n
&& n
->type
== NCMD
&& n
->ncmd
.args
)
1097 if (goodname(n
->ncmd
.args
->narg
.text
))
1098 find_command(n
->ncmd
.args
->narg
.text
, &entry
, 0,
1105 * Builtin commands. Builtin commands whose functions are closely
1106 * tied to evaluation are implemented here.
1114 bltincmd(int argc
, char **argv
)
1117 * Preserve exitstatus of a previous possible redirection
1120 return back_exitstatus
;
1125 * Handle break and continue commands. Break, continue, and return are
1126 * all handled by setting the evalskip flag. The evaluation routines
1127 * above all check this flag, and if it is set they start skipping
1128 * commands rather than executing them. The variable skipcount is
1129 * the number of loops to break/continue, or the number of function
1130 * levels to return. (The latter is always 1.) It should probably
1131 * be an error to break out of more loops than exist, but it isn't
1132 * in the standard shell so we don't make it one here.
1136 breakcmd(int argc
, char **argv
)
1138 int n
= argc
> 1 ? number(argv
[1]) : 1;
1143 evalskip
= (**argv
== 'c')? SKIPCONT
: SKIPBREAK
;
1151 * The return command.
1155 returncmd(int argc
, char **argv
)
1157 int ret
= argc
> 1 ? number(argv
[1]) : exitstatus
;
1160 evalskip
= SKIPFUNC
;
1165 /* Do what ksh does; skip the rest of the file */
1166 evalskip
= SKIPFILE
;
1174 falsecmd(int argc
, char **argv
)
1181 truecmd(int argc
, char **argv
)
1188 execcmd(int argc
, char **argv
)
1193 iflag
= 0; /* exit on error */
1196 for (sp
= cmdenviron
; sp
; sp
= sp
->next
)
1197 setvareq(sp
->text
, VEXPORT
|VSTACK
);
1198 shellexec(argv
+ 1, environment(), pathval(), 0, 0);
1204 conv_time(clock_t ticks
, char *seconds
, size_t l
)
1206 static clock_t tpm
= 0;
1211 tpm
= sysconf(_SC_CLK_TCK
) * 60;
1214 snprintf(seconds
, l
, "%.4f", (ticks
- mins
* tpm
) * 60.0 / tpm
);
1216 if (seconds
[0] == '6' && seconds
[1] == '0') {
1217 /* 59.99995 got rounded up... */
1219 strlcpy(seconds
, "0.0", l
);
1223 /* suppress trailing zeros */
1224 i
= strlen(seconds
) - 1;
1225 for (; seconds
[i
] == '0' && seconds
[i
- 1] != '.'; i
--)
1231 timescmd(int argc
, char **argv
)
1235 char us
[8], ss
[8], cus
[8], css
[8];
1241 u
= conv_time(tms
.tms_utime
, us
, sizeof(us
));
1242 s
= conv_time(tms
.tms_stime
, ss
, sizeof(ss
));
1243 cu
= conv_time(tms
.tms_cutime
, cus
, sizeof(cus
));
1244 cs
= conv_time(tms
.tms_cstime
, css
, sizeof(css
));
1246 outfmt(out1
, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n",
1247 u
, us
, s
, ss
, cu
, cus
, cs
, css
);