eval.c revision 1.153.2.4 1 /* $NetBSD: eval.c,v 1.153.2.4 2018/10/20 06:58:15 pgoyette Exp $ */
2
3 /*-
4 * Copyright (c) 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Kenneth Almquist.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
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.
21 *
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
32 * SUCH DAMAGE.
33 */
34
35 #include <sys/cdefs.h>
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95";
39 #else
40 __RCSID("$NetBSD: eval.c,v 1.153.2.4 2018/10/20 06:58:15 pgoyette Exp $");
41 #endif
42 #endif /* not lint */
43
44 #include <stdbool.h>
45 #include <stdlib.h>
46 #include <signal.h>
47 #include <stdio.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <unistd.h>
52 #include <sys/fcntl.h>
53 #include <sys/stat.h>
54 #include <sys/times.h>
55 #include <sys/param.h>
56 #include <sys/types.h>
57 #include <sys/wait.h>
58 #include <sys/sysctl.h>
59
60 /*
61 * Evaluate a command.
62 */
63
64 #include "shell.h"
65 #include "nodes.h"
66 #include "syntax.h"
67 #include "expand.h"
68 #include "parser.h"
69 #include "jobs.h"
70 #include "eval.h"
71 #include "builtins.h"
72 #include "options.h"
73 #include "exec.h"
74 #include "redir.h"
75 #include "input.h"
76 #include "output.h"
77 #include "trap.h"
78 #include "var.h"
79 #include "memalloc.h"
80 #include "error.h"
81 #include "show.h"
82 #include "mystring.h"
83 #include "main.h"
84 #ifndef SMALL
85 #include "nodenames.h"
86 #include "myhistedit.h"
87 #endif
88
89
90 STATIC enum skipstate evalskip; /* != SKIPNONE if we are skipping commands */
91 STATIC int skipcount; /* number of levels to skip */
92 STATIC int loopnest; /* current loop nesting level */
93 STATIC int funcnest; /* depth of function calls */
94 STATIC int builtin_flags; /* evalcommand flags for builtins */
95 /*
96 * Base function nesting level inside a dot command. Set to 0 initially
97 * and to (funcnest + 1) before every dot command to enable
98 * 1) detection of being in a file sourced by a dot command and
99 * 2) counting of function nesting in that file for the implementation
100 * of the return command.
101 * The value is reset to its previous value after the dot command.
102 */
103 STATIC int dot_funcnest;
104
105
106 const char *commandname;
107 struct strlist *cmdenviron;
108 int exitstatus; /* exit status of last command */
109 int back_exitstatus; /* exit status of backquoted command */
110
111
112 STATIC void evalloop(union node *, int);
113 STATIC void evalfor(union node *, int);
114 STATIC void evalcase(union node *, int);
115 STATIC void evalsubshell(union node *, int);
116 STATIC void expredir(union node *);
117 STATIC void evalredir(union node *, int);
118 STATIC void evalpipe(union node *);
119 STATIC void evalcommand(union node *, int, struct backcmd *);
120 STATIC void prehash(union node *);
121
122 STATIC char *find_dot_file(char *);
123
124 /*
125 * Called to reset things after an exception.
126 */
127
128 #ifdef mkinit
129 INCLUDE "eval.h"
130
131 RESET {
132 reset_eval();
133 }
134
135 SHELLPROC {
136 exitstatus = 0;
137 }
138 #endif
139
140 void
141 reset_eval(void)
142 {
143 evalskip = SKIPNONE;
144 dot_funcnest = 0;
145 loopnest = 0;
146 funcnest = 0;
147 }
148
149 static int
150 sh_pipe(int fds[2])
151 {
152 int nfd;
153
154 if (pipe(fds))
155 return -1;
156
157 if (fds[0] < 3) {
158 nfd = fcntl(fds[0], F_DUPFD, 3);
159 if (nfd != -1) {
160 close(fds[0]);
161 fds[0] = nfd;
162 }
163 }
164
165 if (fds[1] < 3) {
166 nfd = fcntl(fds[1], F_DUPFD, 3);
167 if (nfd != -1) {
168 close(fds[1]);
169 fds[1] = nfd;
170 }
171 }
172 return 0;
173 }
174
175
176 /*
177 * The eval commmand.
178 */
179
180 int
181 evalcmd(int argc, char **argv)
182 {
183 char *p;
184 char *concat;
185 char **ap;
186
187 if (argc > 1) {
188 p = argv[1];
189 if (argc > 2) {
190 STARTSTACKSTR(concat);
191 ap = argv + 2;
192 for (;;) {
193 while (*p)
194 STPUTC(*p++, concat);
195 if ((p = *ap++) == NULL)
196 break;
197 STPUTC(' ', concat);
198 }
199 STPUTC('\0', concat);
200 p = grabstackstr(concat);
201 }
202 evalstring(p, builtin_flags & EV_TESTED);
203 } else
204 exitstatus = 0;
205 return exitstatus;
206 }
207
208
209 /*
210 * Execute a command or commands contained in a string.
211 */
212
213 void
214 evalstring(char *s, int flag)
215 {
216 union node *n;
217 struct stackmark smark;
218 int last;
219 int any;
220
221 last = flag & EV_EXIT;
222 flag &= ~EV_EXIT;
223
224 setstackmark(&smark);
225 setinputstring(s, 1, line_number);
226
227 any = 0; /* to determine if exitstatus will have been set */
228 while ((n = parsecmd(0)) != NEOF) {
229 XTRACE(DBG_EVAL, ("evalstring: "), showtree(n));
230 if (n && nflag == 0) {
231 if (last && at_eof())
232 evaltree(n, flag | EV_EXIT);
233 else
234 evaltree(n, flag);
235 any = 1;
236 }
237 rststackmark(&smark);
238 }
239 popfile();
240 popstackmark(&smark);
241 if (!any)
242 exitstatus = 0;
243 if (last)
244 exraise(EXEXIT);
245 }
246
247
248
249 /*
250 * Evaluate a parse tree. The value is left in the global variable
251 * exitstatus.
252 */
253
254 void
255 evaltree(union node *n, int flags)
256 {
257 bool do_etest;
258 int sflags = flags & ~EV_EXIT;
259 union node *next;
260 struct stackmark smark;
261
262 do_etest = false;
263 if (n == NULL || nflag) {
264 VTRACE(DBG_EVAL, ("evaltree(%s) called\n",
265 n == NULL ? "NULL" : "-n"));
266 if (nflag == 0)
267 exitstatus = 0;
268 goto out2;
269 }
270
271 setstackmark(&smark);
272 do {
273 #ifndef SMALL
274 displayhist = 1; /* show history substitutions done with fc */
275 #endif
276 next = NULL;
277 CTRACE(DBG_EVAL, ("pid %d, evaltree(%p: %s(%d), %#x) called\n",
278 getpid(), n, NODETYPENAME(n->type), n->type, flags));
279 switch (n->type) {
280 case NSEMI:
281 evaltree(n->nbinary.ch1, sflags);
282 if (nflag || evalskip)
283 goto out1;
284 next = n->nbinary.ch2;
285 break;
286 case NAND:
287 evaltree(n->nbinary.ch1, EV_TESTED);
288 if (nflag || evalskip || exitstatus != 0)
289 goto out1;
290 next = n->nbinary.ch2;
291 break;
292 case NOR:
293 evaltree(n->nbinary.ch1, EV_TESTED);
294 if (nflag || evalskip || exitstatus == 0)
295 goto out1;
296 next = n->nbinary.ch2;
297 break;
298 case NREDIR:
299 evalredir(n, flags);
300 break;
301 case NSUBSHELL:
302 evalsubshell(n, flags);
303 do_etest = !(flags & EV_TESTED);
304 break;
305 case NBACKGND:
306 evalsubshell(n, flags);
307 break;
308 case NIF: {
309 evaltree(n->nif.test, EV_TESTED);
310 if (nflag || evalskip)
311 goto out1;
312 if (exitstatus == 0)
313 next = n->nif.ifpart;
314 else if (n->nif.elsepart)
315 next = n->nif.elsepart;
316 else
317 exitstatus = 0;
318 break;
319 }
320 case NWHILE:
321 case NUNTIL:
322 evalloop(n, sflags);
323 break;
324 case NFOR:
325 evalfor(n, sflags);
326 break;
327 case NCASE:
328 evalcase(n, sflags);
329 break;
330 case NDEFUN:
331 CTRACE(DBG_EVAL, ("Defining fn %s @%d%s\n",
332 n->narg.text, n->narg.lineno,
333 fnline1 ? " LINENO=1" : ""));
334 defun(n->narg.text, n->narg.next, n->narg.lineno);
335 exitstatus = 0;
336 break;
337 case NNOT:
338 evaltree(n->nnot.com, EV_TESTED);
339 exitstatus = !exitstatus;
340 break;
341 case NDNOT:
342 evaltree(n->nnot.com, EV_TESTED);
343 if (exitstatus != 0)
344 exitstatus = 1;
345 break;
346 case NPIPE:
347 evalpipe(n);
348 do_etest = !(flags & EV_TESTED);
349 break;
350 case NCMD:
351 evalcommand(n, flags, NULL);
352 do_etest = !(flags & EV_TESTED);
353 break;
354 default:
355 #ifdef NODETYPENAME
356 out1fmt("Node type = %d(%s)\n",
357 n->type, NODETYPENAME(n->type));
358 #else
359 out1fmt("Node type = %d\n", n->type);
360 #endif
361 flushout(&output);
362 break;
363 }
364 n = next;
365 rststackmark(&smark);
366 } while(n != NULL);
367 out1:
368 popstackmark(&smark);
369 out2:
370 if (pendingsigs)
371 dotrap();
372 if (eflag && exitstatus != 0 && do_etest)
373 exitshell(exitstatus);
374 if (flags & EV_EXIT)
375 exraise(EXEXIT);
376 }
377
378
379 STATIC void
380 evalloop(union node *n, int flags)
381 {
382 int status;
383
384 loopnest++;
385 status = 0;
386
387 CTRACE(DBG_EVAL, ("evalloop %s:", NODETYPENAME(n->type)));
388 VXTRACE(DBG_EVAL, (" "), showtree(n->nbinary.ch1));
389 VXTRACE(DBG_EVAL, ("evalloop do: "), showtree(n->nbinary.ch2));
390 VTRACE(DBG_EVAL, ("evalloop done\n"));
391 CTRACE(DBG_EVAL, ("\n"));
392
393 for (;;) {
394 evaltree(n->nbinary.ch1, EV_TESTED);
395 if (nflag)
396 break;
397 if (evalskip) {
398 skipping: if (evalskip == SKIPCONT && --skipcount <= 0) {
399 evalskip = SKIPNONE;
400 continue;
401 }
402 if (evalskip == SKIPBREAK && --skipcount <= 0)
403 evalskip = SKIPNONE;
404 break;
405 }
406 if (n->type == NWHILE) {
407 if (exitstatus != 0)
408 break;
409 } else {
410 if (exitstatus == 0)
411 break;
412 }
413 evaltree(n->nbinary.ch2, flags & EV_TESTED);
414 status = exitstatus;
415 if (evalskip)
416 goto skipping;
417 }
418 loopnest--;
419 exitstatus = status;
420 }
421
422
423
424 STATIC void
425 evalfor(union node *n, int flags)
426 {
427 struct arglist arglist;
428 union node *argp;
429 struct strlist *sp;
430 struct stackmark smark;
431 int status;
432
433 status = nflag ? exitstatus : 0;
434
435 setstackmark(&smark);
436 arglist.lastp = &arglist.list;
437 for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
438 expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
439 if (evalskip)
440 goto out;
441 }
442 *arglist.lastp = NULL;
443
444 loopnest++;
445 for (sp = arglist.list ; sp ; sp = sp->next) {
446 if (xflag) {
447 outxstr(expandstr(ps4val(), line_number));
448 outxstr("for ");
449 outxstr(n->nfor.var);
450 outxc('=');
451 outxshstr(sp->text);
452 outxc('\n');
453 flushout(outx);
454 }
455
456 setvar(n->nfor.var, sp->text, 0);
457 evaltree(n->nfor.body, flags & EV_TESTED);
458 status = exitstatus;
459 if (nflag)
460 break;
461 if (evalskip) {
462 if (evalskip == SKIPCONT && --skipcount <= 0) {
463 evalskip = SKIPNONE;
464 continue;
465 }
466 if (evalskip == SKIPBREAK && --skipcount <= 0)
467 evalskip = SKIPNONE;
468 break;
469 }
470 }
471 loopnest--;
472 exitstatus = status;
473 out:
474 popstackmark(&smark);
475 }
476
477
478
479 STATIC void
480 evalcase(union node *n, int flags)
481 {
482 union node *cp, *ncp;
483 union node *patp;
484 struct arglist arglist;
485 struct stackmark smark;
486 int status = 0;
487
488 setstackmark(&smark);
489 arglist.lastp = &arglist.list;
490 line_number = n->ncase.lineno;
491 expandarg(n->ncase.expr, &arglist, EXP_TILDE);
492 for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
493 for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
494 line_number = patp->narg.lineno;
495 if (casematch(patp, arglist.list->text)) {
496 while (cp != NULL && evalskip == 0 &&
497 nflag == 0) {
498 if (cp->type == NCLISTCONT)
499 ncp = cp->nclist.next;
500 else
501 ncp = NULL;
502 line_number = cp->nclist.lineno;
503 evaltree(cp->nclist.body, flags);
504 status = exitstatus;
505 cp = ncp;
506 }
507 goto out;
508 }
509 }
510 }
511 out:
512 exitstatus = status;
513 popstackmark(&smark);
514 }
515
516
517
518 /*
519 * Kick off a subshell to evaluate a tree.
520 */
521
522 STATIC void
523 evalsubshell(union node *n, int flags)
524 {
525 struct job *jp= NULL;
526 int backgnd = (n->type == NBACKGND);
527
528 expredir(n->nredir.redirect);
529 if (xflag && n->nredir.redirect) {
530 union node *rn;
531
532 outxstr(expandstr(ps4val(), line_number));
533 outxstr("using redirections:");
534 for (rn = n->nredir.redirect; rn; rn = rn->nfile.next)
535 (void) outredir(outx, rn, ' ');
536 outxstr(" do subshell ("/*)*/);
537 if (backgnd)
538 outxstr(/*(*/") &");
539 outxc('\n');
540 flushout(outx);
541 }
542 if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
543 forkshell(jp = makejob(n, 1), n, backgnd?FORK_BG:FORK_FG) == 0) {
544 INTON;
545 if (backgnd)
546 flags &=~ EV_TESTED;
547 redirect(n->nredir.redirect, REDIR_KEEP);
548 evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
549 } else if (!backgnd) {
550 INTOFF;
551 exitstatus = waitforjob(jp);
552 INTON;
553 } else
554 exitstatus = 0;
555
556 if (!backgnd && xflag && n->nredir.redirect) {
557 outxstr(expandstr(ps4val(), line_number));
558 outxstr(/*(*/") done subshell\n");
559 flushout(outx);
560 }
561 }
562
563
564
565 /*
566 * Compute the names of the files in a redirection list.
567 */
568
569 STATIC void
570 expredir(union node *n)
571 {
572 union node *redir;
573
574 for (redir = n ; redir ; redir = redir->nfile.next) {
575 struct arglist fn;
576
577 fn.lastp = &fn.list;
578 switch (redir->type) {
579 case NFROMTO:
580 case NFROM:
581 case NTO:
582 case NCLOBBER:
583 case NAPPEND:
584 expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
585 redir->nfile.expfname = fn.list->text;
586 break;
587 case NFROMFD:
588 case NTOFD:
589 if (redir->ndup.vname) {
590 expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
591 fixredir(redir, fn.list->text, 1);
592 }
593 break;
594 }
595 }
596 }
597
598 /*
599 * Perform redirections for a compound command, and then do it (and restore)
600 */
601 STATIC void
602 evalredir(union node *n, int flags)
603 {
604 struct jmploc jmploc;
605 struct jmploc * const savehandler = handler;
606 volatile int in_redirect = 1;
607 const char * volatile PS4 = NULL;
608
609 expredir(n->nredir.redirect);
610
611 if (xflag && n->nredir.redirect) {
612 union node *rn;
613
614 outxstr(PS4 = expandstr(ps4val(), line_number));
615 outxstr("using redirections:");
616 for (rn = n->nredir.redirect; rn != NULL; rn = rn->nfile.next)
617 (void) outredir(outx, rn, ' ');
618 outxstr(" do {\n"); /* } */
619 flushout(outx);
620 }
621
622 if (setjmp(jmploc.loc)) {
623 int e;
624
625 handler = savehandler;
626 e = exception;
627 popredir();
628 if (PS4 != NULL) {
629 outxstr(PS4);
630 /* { */ outxstr("} failed\n");
631 flushout(outx);
632 }
633 if (e == EXERROR || e == EXEXEC) {
634 if (in_redirect) {
635 exitstatus = 2;
636 return;
637 }
638 }
639 longjmp(handler->loc, 1);
640 } else {
641 INTOFF;
642 handler = &jmploc;
643 redirect(n->nredir.redirect, REDIR_PUSH | REDIR_KEEP);
644 in_redirect = 0;
645 INTON;
646 evaltree(n->nredir.n, flags);
647 }
648 INTOFF;
649 handler = savehandler;
650 popredir();
651 INTON;
652
653 if (PS4 != NULL) {
654 outxstr(PS4);
655 /* { */ outxstr("} done\n");
656 flushout(outx);
657 }
658 }
659
660
661 /*
662 * Evaluate a pipeline. All the processes in the pipeline are children
663 * of the process creating the pipeline. (This differs from some versions
664 * of the shell, which make the last process in a pipeline the parent
665 * of all the rest.)
666 */
667
668 STATIC void
669 evalpipe(union node *n)
670 {
671 struct job *jp;
672 struct nodelist *lp;
673 int pipelen;
674 int prevfd;
675 int pip[2];
676
677 CTRACE(DBG_EVAL, ("evalpipe(%p) called\n", n));
678 pipelen = 0;
679 for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
680 pipelen++;
681 INTOFF;
682 jp = makejob(n, pipelen);
683 prevfd = -1;
684 for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
685 prehash(lp->n);
686 pip[1] = -1;
687 if (lp->next) {
688 if (sh_pipe(pip) < 0) {
689 if (prevfd >= 0)
690 close(prevfd);
691 error("Pipe call failed: %s", strerror(errno));
692 }
693 }
694 if (forkshell(jp, lp->n,
695 n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) {
696 INTON;
697 if (prevfd > 0)
698 movefd(prevfd, 0);
699 if (pip[1] >= 0) {
700 close(pip[0]);
701 movefd(pip[1], 1);
702 }
703 evaltree(lp->n, EV_EXIT);
704 }
705 if (prevfd >= 0)
706 close(prevfd);
707 prevfd = pip[0];
708 close(pip[1]);
709 }
710 if (n->npipe.backgnd == 0) {
711 INTOFF;
712 exitstatus = waitforjob(jp);
713 CTRACE(DBG_EVAL, ("evalpipe: job done exit status %d\n",
714 exitstatus));
715 INTON;
716 } else
717 exitstatus = 0;
718 INTON;
719 }
720
721
722
723 /*
724 * Execute a command inside back quotes. If it's a builtin command, we
725 * want to save its output in a block obtained from malloc. Otherwise
726 * we fork off a subprocess and get the output of the command via a pipe.
727 * Should be called with interrupts off.
728 */
729
730 void
731 evalbackcmd(union node *n, struct backcmd *result)
732 {
733 int pip[2];
734 struct job *jp;
735 struct stackmark smark; /* unnecessary (because we fork) */
736
737 result->fd = -1;
738 result->buf = NULL;
739 result->nleft = 0;
740 result->jp = NULL;
741
742 if (nflag || n == NULL)
743 goto out;
744
745 setstackmark(&smark);
746
747 #ifdef notyet
748 /*
749 * For now we disable executing builtins in the same
750 * context as the shell, because we are not keeping
751 * enough state to recover from changes that are
752 * supposed only to affect subshells. eg. echo "`cd /`"
753 */
754 if (n->type == NCMD) {
755 exitstatus = oexitstatus; /* XXX o... no longer exists */
756 evalcommand(n, EV_BACKCMD, result);
757 } else
758 #endif
759 {
760 INTOFF;
761 if (sh_pipe(pip) < 0)
762 error("Pipe call failed");
763 jp = makejob(n, 1);
764 if (forkshell(jp, n, FORK_NOJOB) == 0) {
765 FORCEINTON;
766 close(pip[0]);
767 movefd(pip[1], 1);
768 eflag = 0;
769 evaltree(n, EV_EXIT);
770 /* NOTREACHED */
771 }
772 close(pip[1]);
773 result->fd = pip[0];
774 result->jp = jp;
775 INTON;
776 }
777 popstackmark(&smark);
778 out:
779 CTRACE(DBG_EVAL, ("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
780 result->fd, result->buf, result->nleft, result->jp));
781 }
782
783 const char *
784 syspath(void)
785 {
786 static char *sys_path = NULL;
787 static int mib[] = {CTL_USER, USER_CS_PATH};
788 static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
789 size_t len;
790
791 if (sys_path == NULL) {
792 if (sysctl(mib, 2, 0, &len, 0, 0) != -1 &&
793 (sys_path = ckmalloc(len + 5)) != NULL &&
794 sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) {
795 memcpy(sys_path, "PATH=", 5);
796 } else {
797 ckfree(sys_path);
798 /* something to keep things happy */
799 sys_path = def_path;
800 }
801 }
802 return sys_path;
803 }
804
805 static int
806 parse_command_args(int argc, char **argv, int *use_syspath)
807 {
808 int sv_argc = argc;
809 char *cp, c;
810
811 *use_syspath = 0;
812
813 for (;;) {
814 argv++;
815 if (--argc == 0)
816 break;
817 cp = *argv;
818 if (*cp++ != '-')
819 break;
820 if (*cp == '-' && cp[1] == 0) {
821 argv++;
822 argc--;
823 break;
824 }
825 while ((c = *cp++)) {
826 switch (c) {
827 case 'p':
828 *use_syspath = 1;
829 break;
830 default:
831 /* run 'typecmd' for other options */
832 return 0;
833 }
834 }
835 }
836 return sv_argc - argc;
837 }
838
839 int vforked = 0;
840 extern char *trap[];
841
842 /*
843 * Execute a simple command.
844 */
845
846 STATIC void
847 evalcommand(union node *cmd, int flgs, struct backcmd *backcmd)
848 {
849 struct stackmark smark;
850 union node *argp;
851 struct arglist arglist;
852 struct arglist varlist;
853 volatile int flags = flgs;
854 char ** volatile argv;
855 volatile int argc;
856 char **envp;
857 int varflag;
858 struct strlist *sp;
859 volatile int mode;
860 int pip[2];
861 struct cmdentry cmdentry;
862 struct job * volatile jp;
863 struct jmploc jmploc;
864 struct jmploc *volatile savehandler = NULL;
865 const char *volatile savecmdname;
866 volatile struct shparam saveparam;
867 struct localvar *volatile savelocalvars;
868 volatile int e;
869 char * volatile lastarg;
870 const char * volatile path = pathval();
871 volatile int temp_path;
872 const int savefuncline = funclinebase;
873 const int savefuncabs = funclineabs;
874
875 vforked = 0;
876 /* First expand the arguments. */
877 CTRACE(DBG_EVAL, ("evalcommand(%p, %d) called [%s]\n", cmd, flags,
878 cmd->ncmd.args ? cmd->ncmd.args->narg.text : ""));
879 setstackmark(&smark);
880 back_exitstatus = 0;
881
882 line_number = cmd->ncmd.lineno;
883
884 arglist.lastp = &arglist.list;
885 varflag = 1;
886 /* Expand arguments, ignoring the initial 'name=value' ones */
887 for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
888 char *p = argp->narg.text;
889
890 line_number = argp->narg.lineno;
891 if (varflag && is_name(*p)) {
892 do {
893 p++;
894 } while (is_in_name(*p));
895 if (*p == '=')
896 continue;
897 }
898 expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
899 varflag = 0;
900 }
901 *arglist.lastp = NULL;
902
903 expredir(cmd->ncmd.redirect);
904
905 /* Now do the initial 'name=value' ones we skipped above */
906 varlist.lastp = &varlist.list;
907 for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
908 char *p = argp->narg.text;
909
910 line_number = argp->narg.lineno;
911 if (!is_name(*p))
912 break;
913 do
914 p++;
915 while (is_in_name(*p));
916 if (*p != '=')
917 break;
918 expandarg(argp, &varlist, EXP_VARTILDE);
919 }
920 *varlist.lastp = NULL;
921
922 argc = 0;
923 for (sp = arglist.list ; sp ; sp = sp->next)
924 argc++;
925 argv = stalloc(sizeof (char *) * (argc + 1));
926
927 for (sp = arglist.list ; sp ; sp = sp->next) {
928 VTRACE(DBG_EVAL, ("evalcommand arg: %s\n", sp->text));
929 *argv++ = sp->text;
930 }
931 *argv = NULL;
932 lastarg = NULL;
933 if (iflag && funcnest == 0 && argc > 0)
934 lastarg = argv[-1];
935 argv -= argc;
936
937 /* Print the command if xflag is set. */
938 if (xflag) {
939 char sep = 0;
940 union node *rn;
941
942 outxstr(expandstr(ps4val(), line_number));
943 for (sp = varlist.list ; sp ; sp = sp->next) {
944 char *p;
945
946 if (sep != 0)
947 outxc(sep);
948
949 /*
950 * The "var=" part should not be quoted, regardless
951 * of the value, or it would not represent an
952 * assignment, but rather a command
953 */
954 p = strchr(sp->text, '=');
955 if (p != NULL) {
956 *p = '\0'; /*XXX*/
957 outxshstr(sp->text);
958 outxc('=');
959 *p++ = '='; /*XXX*/
960 } else
961 p = sp->text;
962 outxshstr(p);
963 sep = ' ';
964 }
965 for (sp = arglist.list ; sp ; sp = sp->next) {
966 if (sep != 0)
967 outxc(sep);
968 outxshstr(sp->text);
969 sep = ' ';
970 }
971 for (rn = cmd->ncmd.redirect; rn; rn = rn->nfile.next)
972 if (outredir(outx, rn, sep))
973 sep = ' ';
974 outxc('\n');
975 flushout(outx);
976 }
977
978 /* Now locate the command. */
979 if (argc == 0) {
980 cmdentry.cmdtype = CMDSPLBLTIN;
981 cmdentry.u.bltin = bltincmd;
982 } else {
983 static const char PATH[] = "PATH=";
984 int cmd_flags = 0;
985
986 /*
987 * Modify the command lookup path, if a PATH= assignment
988 * is present
989 */
990 for (sp = varlist.list; sp; sp = sp->next)
991 if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0)
992 path = sp->text + sizeof(PATH) - 1;
993
994 do {
995 int argsused, use_syspath;
996
997 find_command(argv[0], &cmdentry, cmd_flags, path);
998 #if 0
999 /*
1000 * This short circuits all of the processing that
1001 * should be done (including processing the
1002 * redirects), so just don't ...
1003 *
1004 * (eventually this whole #if'd block will vanish)
1005 */
1006 if (cmdentry.cmdtype == CMDUNKNOWN) {
1007 exitstatus = 127;
1008 flushout(&errout);
1009 goto out;
1010 }
1011 #endif
1012
1013 /* implement the 'command' builtin here */
1014 if (cmdentry.cmdtype != CMDBUILTIN ||
1015 cmdentry.u.bltin != bltincmd)
1016 break;
1017 cmd_flags |= DO_NOFUNC;
1018 argsused = parse_command_args(argc, argv, &use_syspath);
1019 if (argsused == 0) {
1020 /* use 'type' builtin to display info */
1021 cmdentry.u.bltin = typecmd;
1022 break;
1023 }
1024 argc -= argsused;
1025 argv += argsused;
1026 if (use_syspath)
1027 path = syspath() + 5;
1028 } while (argc != 0);
1029 if (cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC)
1030 /* posix mandates that 'command <splbltin>' act as if
1031 <splbltin> was a normal builtin */
1032 cmdentry.cmdtype = CMDBUILTIN;
1033 }
1034
1035 /* Fork off a child process if necessary. */
1036 if (cmd->ncmd.backgnd || (trap[0] && (flags & EV_EXIT) != 0)
1037 || ((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
1038 && (flags & EV_EXIT) == 0)
1039 || ((flags & EV_BACKCMD) != 0 &&
1040 ((cmdentry.cmdtype != CMDBUILTIN && cmdentry.cmdtype != CMDSPLBLTIN)
1041 || cmdentry.u.bltin == dotcmd
1042 || cmdentry.u.bltin == evalcmd))) {
1043 INTOFF;
1044 jp = makejob(cmd, 1);
1045 mode = cmd->ncmd.backgnd;
1046 if (flags & EV_BACKCMD) {
1047 mode = FORK_NOJOB;
1048 if (sh_pipe(pip) < 0)
1049 error("Pipe call failed");
1050 }
1051 #ifdef DO_SHAREDVFORK
1052 /* It is essential that if DO_SHAREDVFORK is defined that the
1053 * child's address space is actually shared with the parent as
1054 * we rely on this.
1055 */
1056 if (usefork == 0 && cmdentry.cmdtype == CMDNORMAL) {
1057 pid_t pid;
1058 int serrno;
1059
1060 savelocalvars = localvars;
1061 localvars = NULL;
1062 vforked = 1;
1063 VFORK_BLOCK
1064 switch (pid = vfork()) {
1065 case -1:
1066 serrno = errno;
1067 VTRACE(DBG_EVAL, ("vfork() failed, errno=%d\n",
1068 serrno));
1069 INTON;
1070 error("Cannot vfork (%s)", strerror(serrno));
1071 break;
1072 case 0:
1073 /* Make sure that exceptions only unwind to
1074 * after the vfork(2)
1075 */
1076 SHELL_FORKED();
1077 if (setjmp(jmploc.loc)) {
1078 if (exception == EXSHELLPROC) {
1079 /*
1080 * We can't progress with the
1081 * vfork, so, set vforked = 2
1082 * so the parent knows,
1083 * and _exit();
1084 */
1085 vforked = 2;
1086 _exit(0);
1087 } else {
1088 _exit(exception == EXEXIT ?
1089 exitstatus : exerrno);
1090 }
1091 }
1092 savehandler = handler;
1093 handler = &jmploc;
1094 listmklocal(varlist.list, VEXPORT | VNOFUNC);
1095 forkchild(jp, cmd, mode, vforked);
1096 break;
1097 default:
1098 VFORK_UNDO();
1099 /* restore from vfork(2) */
1100 handler = savehandler;
1101 poplocalvars();
1102 localvars = savelocalvars;
1103 if (vforked == 2) {
1104 vforked = 0;
1105
1106 (void)waitpid(pid, NULL, 0);
1107 /*
1108 * We need to progress in a
1109 * normal fork fashion
1110 */
1111 goto normal_fork;
1112 }
1113 /*
1114 * Here the child has left home,
1115 * getting on with its life, so
1116 * so must we...
1117 */
1118 vforked = 0;
1119 forkparent(jp, cmd, mode, pid);
1120 goto parent;
1121 }
1122 VFORK_END
1123 } else {
1124 normal_fork:
1125 #endif
1126 if (forkshell(jp, cmd, mode) != 0)
1127 goto parent; /* at end of routine */
1128 flags |= EV_EXIT;
1129 FORCEINTON;
1130 #ifdef DO_SHAREDVFORK
1131 }
1132 #endif
1133 if (flags & EV_BACKCMD) {
1134 if (!vforked) {
1135 FORCEINTON;
1136 }
1137 close(pip[0]);
1138 movefd(pip[1], 1);
1139 }
1140 flags |= EV_EXIT;
1141 }
1142
1143 /* This is the child process if a fork occurred. */
1144 /* Execute the command. */
1145 switch (cmdentry.cmdtype) {
1146 volatile int saved;
1147
1148 case CMDFUNCTION:
1149 VXTRACE(DBG_EVAL, ("Shell function%s: ",vforked?" VF":""),
1150 trargs(argv));
1151 redirect(cmd->ncmd.redirect, saved =
1152 !(flags & EV_EXIT) || have_traps() ? REDIR_PUSH : 0);
1153 saveparam = shellparam;
1154 shellparam.malloc = 0;
1155 shellparam.reset = 1;
1156 shellparam.nparam = argc - 1;
1157 shellparam.p = argv + 1;
1158 shellparam.optnext = NULL;
1159 INTOFF;
1160 savelocalvars = localvars;
1161 localvars = NULL;
1162 reffunc(cmdentry.u.func);
1163 INTON;
1164 if (setjmp(jmploc.loc)) {
1165 if (exception == EXSHELLPROC) {
1166 freeparam((volatile struct shparam *)
1167 &saveparam);
1168 } else {
1169 freeparam(&shellparam);
1170 shellparam = saveparam;
1171 }
1172 if (saved)
1173 popredir();;
1174 unreffunc(cmdentry.u.func);
1175 poplocalvars();
1176 localvars = savelocalvars;
1177 funclinebase = savefuncline;
1178 funclineabs = savefuncabs;
1179 handler = savehandler;
1180 longjmp(handler->loc, 1);
1181 }
1182 savehandler = handler;
1183 handler = &jmploc;
1184 if (cmdentry.u.func) {
1185 if (cmdentry.lno_frel)
1186 funclinebase = cmdentry.lineno - 1;
1187 else
1188 funclinebase = 0;
1189 funclineabs = cmdentry.lineno;
1190
1191 VTRACE(DBG_EVAL,
1192 ("function: node: %d '%s' # %d%s; funclinebase=%d\n",
1193 getfuncnode(cmdentry.u.func)->type,
1194 NODETYPENAME(getfuncnode(cmdentry.u.func)->type),
1195 cmdentry.lineno, cmdentry.lno_frel?" (=1)":"",
1196 funclinebase));
1197 }
1198 listmklocal(varlist.list, VEXPORT);
1199 /* stop shell blowing its stack */
1200 if (++funcnest > 1000)
1201 error("too many nested function calls");
1202 evaltree(getfuncnode(cmdentry.u.func),
1203 flags & (EV_TESTED|EV_EXIT));
1204 funcnest--;
1205 INTOFF;
1206 unreffunc(cmdentry.u.func);
1207 poplocalvars();
1208 localvars = savelocalvars;
1209 funclinebase = savefuncline;
1210 funclineabs = savefuncabs;
1211 freeparam(&shellparam);
1212 shellparam = saveparam;
1213 handler = savehandler;
1214 if (saved)
1215 popredir();
1216 INTON;
1217 if (evalskip == SKIPFUNC) {
1218 evalskip = SKIPNONE;
1219 skipcount = 0;
1220 }
1221 if (flags & EV_EXIT)
1222 exitshell(exitstatus);
1223 break;
1224
1225 case CMDBUILTIN:
1226 case CMDSPLBLTIN:
1227 VXTRACE(DBG_EVAL, ("builtin command%s: ",vforked?" VF":""), trargs(argv));
1228 mode = (cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH;
1229 if (flags == EV_BACKCMD) {
1230 memout.nleft = 0;
1231 memout.nextc = memout.buf;
1232 memout.bufsize = 64;
1233 mode |= REDIR_BACKQ;
1234 }
1235 e = -1;
1236 savehandler = handler;
1237 savecmdname = commandname;
1238 handler = &jmploc;
1239 temp_path = 0;
1240 if (!setjmp(jmploc.loc)) {
1241 /*
1242 * We need to ensure the command hash table isn't
1243 * corrupted by temporary PATH assignments.
1244 * However we must ensure the 'local' command works!
1245 */
1246 if (path != pathval() && (cmdentry.u.bltin == hashcmd ||
1247 cmdentry.u.bltin == typecmd)) {
1248 savelocalvars = localvars;
1249 localvars = 0;
1250 temp_path = 1;
1251 mklocal(path - 5 /* PATH= */, 0);
1252 }
1253 redirect(cmd->ncmd.redirect, mode);
1254
1255 /* exec is a special builtin, but needs this list... */
1256 cmdenviron = varlist.list;
1257 /* we must check 'readonly' flag for all builtins */
1258 listsetvar(varlist.list,
1259 cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET);
1260 commandname = argv[0];
1261 /* initialize nextopt */
1262 argptr = argv + 1;
1263 optptr = NULL;
1264 /* and getopt */
1265 optreset = 1;
1266 optind = 1;
1267 builtin_flags = flags;
1268 exitstatus = cmdentry.u.bltin(argc, argv);
1269 } else {
1270 e = exception;
1271 if (e == EXINT)
1272 exitstatus = SIGINT + 128;
1273 else if (e == EXEXEC)
1274 exitstatus = exerrno;
1275 else if (e != EXEXIT)
1276 exitstatus = 2;
1277 }
1278 handler = savehandler;
1279 flushall();
1280 out1 = &output;
1281 out2 = &errout;
1282 freestdout();
1283 if (temp_path) {
1284 poplocalvars();
1285 localvars = savelocalvars;
1286 }
1287 cmdenviron = NULL;
1288 if (e != EXSHELLPROC) {
1289 commandname = savecmdname;
1290 if (flags & EV_EXIT)
1291 exitshell(exitstatus);
1292 }
1293 if (e != -1) {
1294 if ((e != EXERROR && e != EXEXEC)
1295 || cmdentry.cmdtype == CMDSPLBLTIN)
1296 exraise(e);
1297 FORCEINTON;
1298 }
1299 if (cmdentry.u.bltin != execcmd)
1300 popredir();
1301 if (flags == EV_BACKCMD) {
1302 backcmd->buf = memout.buf;
1303 backcmd->nleft = memout.nextc - memout.buf;
1304 memout.buf = NULL;
1305 }
1306 break;
1307
1308 default:
1309 VXTRACE(DBG_EVAL, ("normal command%s: ", vforked?" VF":""),
1310 trargs(argv));
1311 redirect(cmd->ncmd.redirect,
1312 (vforked ? REDIR_VFORK : 0) | REDIR_KEEP);
1313 if (!vforked)
1314 for (sp = varlist.list ; sp ; sp = sp->next)
1315 setvareq(sp->text, VEXPORT|VSTACK);
1316 envp = environment();
1317 shellexec(argv, envp, path, cmdentry.u.index, vforked);
1318 break;
1319 }
1320 goto out;
1321
1322 parent: /* parent process gets here (if we forked) */
1323
1324 exitstatus = 0; /* if not altered just below */
1325 if (mode == FORK_FG) { /* argument to fork */
1326 exitstatus = waitforjob(jp);
1327 } else if (mode == FORK_NOJOB) {
1328 backcmd->fd = pip[0];
1329 close(pip[1]);
1330 backcmd->jp = jp;
1331 }
1332 FORCEINTON;
1333
1334 out:
1335 if (lastarg)
1336 /* implement $_ for whatever use that really is */
1337 (void) setvarsafe("_", lastarg, VNOERROR);
1338 popstackmark(&smark);
1339 }
1340
1341
1342 /*
1343 * Search for a command. This is called before we fork so that the
1344 * location of the command will be available in the parent as well as
1345 * the child. The check for "goodname" is an overly conservative
1346 * check that the name will not be subject to expansion.
1347 */
1348
1349 STATIC void
1350 prehash(union node *n)
1351 {
1352 struct cmdentry entry;
1353
1354 if (n && n->type == NCMD && n->ncmd.args)
1355 if (goodname(n->ncmd.args->narg.text))
1356 find_command(n->ncmd.args->narg.text, &entry, 0,
1357 pathval());
1358 }
1359
1360 int
1361 in_function(void)
1362 {
1363 return funcnest;
1364 }
1365
1366 enum skipstate
1367 current_skipstate(void)
1368 {
1369 return evalskip;
1370 }
1371
1372 void
1373 stop_skipping(void)
1374 {
1375 evalskip = SKIPNONE;
1376 skipcount = 0;
1377 }
1378
1379 /*
1380 * Builtin commands. Builtin commands whose functions are closely
1381 * tied to evaluation are implemented here.
1382 */
1383
1384 /*
1385 * No command given.
1386 */
1387
1388 int
1389 bltincmd(int argc, char **argv)
1390 {
1391 /*
1392 * Preserve exitstatus of a previous possible redirection
1393 * as POSIX mandates
1394 */
1395 return back_exitstatus;
1396 }
1397
1398
1399 /*
1400 * Handle break and continue commands. Break, continue, and return are
1401 * all handled by setting the evalskip flag. The evaluation routines
1402 * above all check this flag, and if it is set they start skipping
1403 * commands rather than executing them. The variable skipcount is
1404 * the number of loops to break/continue, or the number of function
1405 * levels to return. (The latter is always 1.) It should probably
1406 * be an error to break out of more loops than exist, but it isn't
1407 * in the standard shell so we don't make it one here.
1408 */
1409
1410 int
1411 breakcmd(int argc, char **argv)
1412 {
1413 int n = argc > 1 ? number(argv[1]) : 1;
1414
1415 if (n <= 0)
1416 error("invalid count: %d", n);
1417 if (n > loopnest)
1418 n = loopnest;
1419 if (n > 0) {
1420 evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
1421 skipcount = n;
1422 }
1423 return 0;
1424 }
1425
1426 int
1427 dotcmd(int argc, char **argv)
1428 {
1429 exitstatus = 0;
1430
1431 if (argc >= 2) { /* That's what SVR2 does */
1432 char *fullname;
1433 /*
1434 * dot_funcnest needs to be 0 when not in a dotcmd, so it
1435 * cannot be restored with (funcnest + 1).
1436 */
1437 int dot_funcnest_old;
1438 struct stackmark smark;
1439
1440 setstackmark(&smark);
1441 fullname = find_dot_file(argv[1]);
1442 setinputfile(fullname, 1);
1443 commandname = fullname;
1444 dot_funcnest_old = dot_funcnest;
1445 dot_funcnest = funcnest + 1;
1446 cmdloop(0);
1447 dot_funcnest = dot_funcnest_old;
1448 popfile();
1449 popstackmark(&smark);
1450 }
1451 return exitstatus;
1452 }
1453
1454 /*
1455 * Take commands from a file. To be compatible we should do a path
1456 * search for the file, which is necessary to find sub-commands.
1457 */
1458
1459 STATIC char *
1460 find_dot_file(char *basename)
1461 {
1462 char *fullname;
1463 const char *path = pathval();
1464 struct stat statb;
1465
1466 /* don't try this for absolute or relative paths */
1467 if (strchr(basename, '/')) {
1468 if (stat(basename, &statb) == 0) {
1469 if (S_ISDIR(statb.st_mode))
1470 error("%s: is a directory", basename);
1471 if (S_ISBLK(statb.st_mode))
1472 error("%s: is a block device", basename);
1473 return basename;
1474 }
1475 } else while ((fullname = padvance(&path, basename, 1)) != NULL) {
1476 if ((stat(fullname, &statb) == 0)) {
1477 /* weird format is to ease future code... */
1478 if (S_ISDIR(statb.st_mode) || S_ISBLK(statb.st_mode))
1479 ;
1480 #if notyet
1481 else if (unreadable()) {
1482 /*
1483 * testing this via st_mode is ugly to get
1484 * correct (and would ignore ACLs).
1485 * better way is just to open the file.
1486 * But doing that here would (currently)
1487 * mean opening the file twice, which
1488 * might not be safe. So, defer this
1489 * test until code is restructures so
1490 * we can return a fd. Then we also
1491 * get to fix the mem leak just below...
1492 */
1493 }
1494 #endif
1495 else {
1496 /*
1497 * Don't bother freeing here, since
1498 * it will be freed by the caller.
1499 * XXX no it won't - a bug for later.
1500 */
1501 return fullname;
1502 }
1503 }
1504 stunalloc(fullname);
1505 }
1506
1507 /* not found in the PATH */
1508 error("%s: not found", basename);
1509 /* NOTREACHED */
1510 }
1511
1512
1513
1514 /*
1515 * The return command.
1516 *
1517 * Quoth the POSIX standard:
1518 * The return utility shall cause the shell to stop executing the current
1519 * function or dot script. If the shell is not currently executing
1520 * a function or dot script, the results are unspecified.
1521 *
1522 * As for the unspecified part, there seems to be no de-facto standard: bash
1523 * ignores the return with a warning, zsh ignores the return in interactive
1524 * mode but seems to liken it to exit in a script. (checked May 2014)
1525 *
1526 * We choose to silently ignore the return. Older versions of this shell
1527 * set evalskip to SKIPFILE causing the shell to (indirectly) exit. This
1528 * had at least the problem of circumventing the check for stopped jobs,
1529 * which would occur for exit or ^D.
1530 */
1531
1532 int
1533 returncmd(int argc, char **argv)
1534 {
1535 int ret = argc > 1 ? number(argv[1]) : exitstatus;
1536
1537 if ((dot_funcnest == 0 && funcnest)
1538 || (dot_funcnest > 0 && funcnest - (dot_funcnest - 1) > 0)) {
1539 evalskip = SKIPFUNC;
1540 skipcount = 1;
1541 } else if (dot_funcnest > 0) {
1542 evalskip = SKIPFILE;
1543 skipcount = 1;
1544 } else {
1545 /* XXX: should a warning be issued? */
1546 ret = 0;
1547 }
1548
1549 return ret;
1550 }
1551
1552
1553 int
1554 falsecmd(int argc, char **argv)
1555 {
1556 return 1;
1557 }
1558
1559
1560 int
1561 truecmd(int argc, char **argv)
1562 {
1563 return 0;
1564 }
1565
1566
1567 int
1568 execcmd(int argc, char **argv)
1569 {
1570 if (argc > 1) {
1571 struct strlist *sp;
1572
1573 iflag = 0; /* exit on error */
1574 mflag = 0;
1575 optschanged();
1576 for (sp = cmdenviron; sp; sp = sp->next)
1577 setvareq(sp->text, VEXPORT|VSTACK);
1578 shellexec(argv + 1, environment(), pathval(), 0, 0);
1579 }
1580 return 0;
1581 }
1582
1583 static int
1584 conv_time(clock_t ticks, char *seconds, size_t l)
1585 {
1586 static clock_t tpm = 0;
1587 clock_t mins;
1588 int i;
1589
1590 if (!tpm)
1591 tpm = sysconf(_SC_CLK_TCK) * 60;
1592
1593 mins = ticks / tpm;
1594 snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm );
1595
1596 if (seconds[0] == '6' && seconds[1] == '0') {
1597 /* 59.99995 got rounded up... */
1598 mins++;
1599 strlcpy(seconds, "0.0", l);
1600 return mins;
1601 }
1602
1603 /* suppress trailing zeros */
1604 i = strlen(seconds) - 1;
1605 for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--)
1606 seconds[i] = 0;
1607 return mins;
1608 }
1609
1610 int
1611 timescmd(int argc, char **argv)
1612 {
1613 struct tms tms;
1614 int u, s, cu, cs;
1615 char us[8], ss[8], cus[8], css[8];
1616
1617 nextopt("");
1618
1619 times(&tms);
1620
1621 u = conv_time(tms.tms_utime, us, sizeof(us));
1622 s = conv_time(tms.tms_stime, ss, sizeof(ss));
1623 cu = conv_time(tms.tms_cutime, cus, sizeof(cus));
1624 cs = conv_time(tms.tms_cstime, css, sizeof(css));
1625
1626 outfmt(out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n",
1627 u, us, s, ss, cu, cus, cs, css);
1628
1629 return 0;
1630 }
1631