]>
Commit | Line | Data |
---|---|---|
71aad674 A |
1 | /*- |
2 | * Copyright (c) 1993 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * This code is derived from software contributed to Berkeley by | |
6 | * Kenneth Almquist. | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * 1. Redistributions of source code must retain the above copyright | |
12 | * notice, this list of conditions and the following disclaimer. | |
13 | * 2. Redistributions in binary form must reproduce the above copyright | |
14 | * notice, this list of conditions and the following disclaimer in the | |
15 | * documentation and/or other materials provided with the distribution. | |
254f12f7 | 16 | * 3. Neither the name of the University nor the names of its contributors |
71aad674 A |
17 | * may be used to endorse or promote products derived from this software |
18 | * without specific prior written permission. | |
19 | * | |
20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
30 | * SUCH DAMAGE. | |
31 | */ | |
32 | ||
33 | #ifndef lint | |
34 | #if 0 | |
35 | static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; | |
36 | #endif | |
37 | #endif /* not lint */ | |
38 | #include <sys/cdefs.h> | |
254f12f7 | 39 | __FBSDID("$FreeBSD: head/bin/sh/eval.c 327212 2017-12-26 16:23:18Z jilles $"); |
71aad674 A |
40 | |
41 | #include <paths.h> | |
42 | #include <signal.h> | |
43 | #include <stdlib.h> | |
44 | #include <unistd.h> | |
45 | #include <sys/resource.h> | |
71aad674 A |
46 | #include <errno.h> |
47 | ||
48 | /* | |
49 | * Evaluate a command. | |
50 | */ | |
51 | ||
52 | #include "shell.h" | |
53 | #include "nodes.h" | |
54 | #include "syntax.h" | |
55 | #include "expand.h" | |
56 | #include "parser.h" | |
57 | #include "jobs.h" | |
58 | #include "eval.h" | |
59 | #include "builtins.h" | |
60 | #include "options.h" | |
61 | #include "exec.h" | |
62 | #include "redir.h" | |
63 | #include "input.h" | |
64 | #include "output.h" | |
65 | #include "trap.h" | |
66 | #include "var.h" | |
67 | #include "memalloc.h" | |
68 | #include "error.h" | |
69 | #include "show.h" | |
70 | #include "mystring.h" | |
71 | #ifndef NO_HISTORY | |
72 | #include "myhistedit.h" | |
73 | #endif | |
74 | ||
75 | ||
76 | int evalskip; /* set if we are skipping commands */ | |
77 | int skipcount; /* number of levels to skip */ | |
78 | static int loopnest; /* current loop nesting level */ | |
79 | int funcnest; /* depth of function calls */ | |
80 | static int builtin_flags; /* evalcommand flags for builtins */ | |
81 | ||
82 | ||
83 | char *commandname; | |
deb63bfb | 84 | struct arglist *cmdenviron; |
71aad674 A |
85 | int exitstatus; /* exit status of last command */ |
86 | int oexitstatus; /* saved exit status */ | |
87 | ||
88 | ||
89 | static void evalloop(union node *, int); | |
90 | static void evalfor(union node *, int); | |
91 | static union node *evalcase(union node *); | |
92 | static void evalsubshell(union node *, int); | |
93 | static void evalredir(union node *, int); | |
94 | static void exphere(union node *, struct arglist *); | |
95 | static void expredir(union node *); | |
96 | static void evalpipe(union node *); | |
97 | static int is_valid_fast_cmdsubst(union node *n); | |
98 | static void evalcommand(union node *, int, struct backcmd *); | |
99 | static void prehash(union node *); | |
100 | ||
101 | ||
102 | /* | |
103 | * Called to reset things after an exception. | |
104 | */ | |
105 | ||
106 | void | |
107 | reseteval(void) | |
108 | { | |
109 | evalskip = 0; | |
110 | loopnest = 0; | |
111 | } | |
112 | ||
113 | ||
114 | /* | |
115 | * The eval command. | |
116 | */ | |
117 | ||
118 | int | |
119 | evalcmd(int argc, char **argv) | |
120 | { | |
121 | char *p; | |
122 | char *concat; | |
123 | char **ap; | |
124 | ||
125 | if (argc > 1) { | |
126 | p = argv[1]; | |
127 | if (argc > 2) { | |
128 | STARTSTACKSTR(concat); | |
129 | ap = argv + 2; | |
130 | for (;;) { | |
131 | STPUTS(p, concat); | |
132 | if ((p = *ap++) == NULL) | |
133 | break; | |
134 | STPUTC(' ', concat); | |
135 | } | |
136 | STPUTC('\0', concat); | |
137 | p = grabstackstr(concat); | |
138 | } | |
139 | evalstring(p, builtin_flags); | |
140 | } else | |
141 | exitstatus = 0; | |
142 | return exitstatus; | |
143 | } | |
144 | ||
145 | ||
146 | /* | |
147 | * Execute a command or commands contained in a string. | |
148 | */ | |
149 | ||
150 | void | |
151 | evalstring(const char *s, int flags) | |
152 | { | |
153 | union node *n; | |
154 | struct stackmark smark; | |
155 | int flags_exit; | |
156 | int any; | |
157 | ||
158 | flags_exit = flags & EV_EXIT; | |
159 | flags &= ~EV_EXIT; | |
160 | any = 0; | |
161 | setstackmark(&smark); | |
162 | setinputstring(s, 1); | |
163 | while ((n = parsecmd(0)) != NEOF) { | |
164 | if (n != NULL && !nflag) { | |
165 | if (flags_exit && preadateof()) | |
166 | evaltree(n, flags | EV_EXIT); | |
167 | else | |
168 | evaltree(n, flags); | |
169 | any = 1; | |
170 | if (evalskip) | |
171 | break; | |
172 | } | |
173 | popstackmark(&smark); | |
174 | setstackmark(&smark); | |
175 | } | |
176 | popfile(); | |
177 | popstackmark(&smark); | |
178 | if (!any) | |
179 | exitstatus = 0; | |
180 | if (flags_exit) | |
181 | exraise(EXEXIT); | |
182 | } | |
183 | ||
184 | ||
185 | /* | |
186 | * Evaluate a parse tree. The value is left in the global variable | |
187 | * exitstatus. | |
188 | */ | |
189 | ||
190 | void | |
191 | evaltree(union node *n, int flags) | |
192 | { | |
193 | int do_etest; | |
194 | union node *next; | |
195 | struct stackmark smark; | |
196 | ||
197 | setstackmark(&smark); | |
198 | do_etest = 0; | |
199 | if (n == NULL) { | |
200 | TRACE(("evaltree(NULL) called\n")); | |
201 | exitstatus = 0; | |
202 | goto out; | |
203 | } | |
204 | do { | |
205 | next = NULL; | |
206 | #ifndef NO_HISTORY | |
207 | displayhist = 1; /* show history substitutions done with fc */ | |
208 | #endif | |
209 | TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); | |
210 | switch (n->type) { | |
211 | case NSEMI: | |
212 | evaltree(n->nbinary.ch1, flags & ~EV_EXIT); | |
213 | if (evalskip) | |
214 | goto out; | |
215 | next = n->nbinary.ch2; | |
216 | break; | |
217 | case NAND: | |
218 | evaltree(n->nbinary.ch1, EV_TESTED); | |
219 | if (evalskip || exitstatus != 0) { | |
220 | goto out; | |
221 | } | |
222 | next = n->nbinary.ch2; | |
223 | break; | |
224 | case NOR: | |
225 | evaltree(n->nbinary.ch1, EV_TESTED); | |
226 | if (evalskip || exitstatus == 0) | |
227 | goto out; | |
228 | next = n->nbinary.ch2; | |
229 | break; | |
230 | case NREDIR: | |
231 | evalredir(n, flags); | |
232 | break; | |
233 | case NSUBSHELL: | |
234 | evalsubshell(n, flags); | |
235 | do_etest = !(flags & EV_TESTED); | |
236 | break; | |
237 | case NBACKGND: | |
238 | evalsubshell(n, flags); | |
239 | break; | |
240 | case NIF: { | |
241 | evaltree(n->nif.test, EV_TESTED); | |
242 | if (evalskip) | |
243 | goto out; | |
244 | if (exitstatus == 0) | |
245 | next = n->nif.ifpart; | |
246 | else if (n->nif.elsepart) | |
247 | next = n->nif.elsepart; | |
248 | else | |
249 | exitstatus = 0; | |
250 | break; | |
251 | } | |
252 | case NWHILE: | |
253 | case NUNTIL: | |
254 | evalloop(n, flags & ~EV_EXIT); | |
255 | break; | |
256 | case NFOR: | |
257 | evalfor(n, flags & ~EV_EXIT); | |
258 | break; | |
259 | case NCASE: | |
260 | next = evalcase(n); | |
261 | break; | |
262 | case NCLIST: | |
263 | next = n->nclist.body; | |
264 | break; | |
265 | case NCLISTFALLTHRU: | |
266 | if (n->nclist.body) { | |
267 | evaltree(n->nclist.body, flags & ~EV_EXIT); | |
268 | if (evalskip) | |
269 | goto out; | |
270 | } | |
271 | next = n->nclist.next; | |
272 | break; | |
273 | case NDEFUN: | |
274 | defun(n->narg.text, n->narg.next); | |
275 | exitstatus = 0; | |
276 | break; | |
277 | case NNOT: | |
278 | evaltree(n->nnot.com, EV_TESTED); | |
279 | if (evalskip) | |
280 | goto out; | |
281 | exitstatus = !exitstatus; | |
282 | break; | |
283 | ||
284 | case NPIPE: | |
285 | evalpipe(n); | |
286 | do_etest = !(flags & EV_TESTED); | |
287 | break; | |
288 | case NCMD: | |
289 | evalcommand(n, flags, (struct backcmd *)NULL); | |
290 | do_etest = !(flags & EV_TESTED); | |
291 | break; | |
292 | default: | |
293 | out1fmt("Node type = %d\n", n->type); | |
294 | flushout(&output); | |
295 | break; | |
296 | } | |
297 | n = next; | |
298 | popstackmark(&smark); | |
299 | setstackmark(&smark); | |
300 | } while (n != NULL); | |
301 | out: | |
302 | popstackmark(&smark); | |
303 | if (pendingsig) | |
304 | dotrap(); | |
305 | if (eflag && exitstatus != 0 && do_etest) | |
306 | exitshell(exitstatus); | |
307 | if (flags & EV_EXIT) | |
308 | exraise(EXEXIT); | |
309 | } | |
310 | ||
311 | ||
312 | static void | |
313 | evalloop(union node *n, int flags) | |
314 | { | |
315 | int status; | |
316 | ||
317 | loopnest++; | |
318 | status = 0; | |
319 | for (;;) { | |
320 | if (!evalskip) | |
321 | evaltree(n->nbinary.ch1, EV_TESTED); | |
322 | if (evalskip) { | |
323 | if (evalskip == SKIPCONT && --skipcount <= 0) { | |
324 | evalskip = 0; | |
325 | continue; | |
326 | } | |
327 | if (evalskip == SKIPBREAK && --skipcount <= 0) | |
328 | evalskip = 0; | |
329 | if (evalskip == SKIPRETURN) | |
330 | status = exitstatus; | |
331 | break; | |
332 | } | |
333 | if (n->type == NWHILE) { | |
334 | if (exitstatus != 0) | |
335 | break; | |
336 | } else { | |
337 | if (exitstatus == 0) | |
338 | break; | |
339 | } | |
340 | evaltree(n->nbinary.ch2, flags); | |
341 | status = exitstatus; | |
342 | } | |
343 | loopnest--; | |
344 | exitstatus = status; | |
345 | } | |
346 | ||
347 | ||
348 | ||
349 | static void | |
350 | evalfor(union node *n, int flags) | |
351 | { | |
352 | struct arglist arglist; | |
353 | union node *argp; | |
deb63bfb | 354 | int i; |
71aad674 A |
355 | int status; |
356 | ||
deb63bfb | 357 | emptyarglist(&arglist); |
71aad674 A |
358 | for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { |
359 | oexitstatus = exitstatus; | |
360 | expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); | |
361 | } | |
71aad674 A |
362 | |
363 | loopnest++; | |
364 | status = 0; | |
deb63bfb A |
365 | for (i = 0; i < arglist.count; i++) { |
366 | setvar(n->nfor.var, arglist.args[i], 0); | |
71aad674 A |
367 | evaltree(n->nfor.body, flags); |
368 | status = exitstatus; | |
369 | if (evalskip) { | |
370 | if (evalskip == SKIPCONT && --skipcount <= 0) { | |
371 | evalskip = 0; | |
372 | continue; | |
373 | } | |
374 | if (evalskip == SKIPBREAK && --skipcount <= 0) | |
375 | evalskip = 0; | |
376 | break; | |
377 | } | |
378 | } | |
379 | loopnest--; | |
380 | exitstatus = status; | |
381 | } | |
382 | ||
383 | ||
384 | /* | |
385 | * Evaluate a case statement, returning the selected tree. | |
386 | * | |
387 | * The exit status needs care to get right. | |
388 | */ | |
389 | ||
390 | static union node * | |
391 | evalcase(union node *n) | |
392 | { | |
393 | union node *cp; | |
394 | union node *patp; | |
395 | struct arglist arglist; | |
396 | ||
deb63bfb | 397 | emptyarglist(&arglist); |
71aad674 A |
398 | oexitstatus = exitstatus; |
399 | expandarg(n->ncase.expr, &arglist, EXP_TILDE); | |
400 | for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { | |
401 | for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { | |
deb63bfb | 402 | if (casematch(patp, arglist.args[0])) { |
71aad674 A |
403 | while (cp->nclist.next && |
404 | cp->type == NCLISTFALLTHRU && | |
405 | cp->nclist.body == NULL) | |
406 | cp = cp->nclist.next; | |
407 | if (cp->nclist.next && | |
408 | cp->type == NCLISTFALLTHRU) | |
409 | return (cp); | |
410 | if (cp->nclist.body == NULL) | |
411 | exitstatus = 0; | |
412 | return (cp->nclist.body); | |
413 | } | |
414 | } | |
415 | } | |
416 | exitstatus = 0; | |
417 | return (NULL); | |
418 | } | |
419 | ||
420 | ||
421 | ||
422 | /* | |
423 | * Kick off a subshell to evaluate a tree. | |
424 | */ | |
425 | ||
426 | static void | |
427 | evalsubshell(union node *n, int flags) | |
428 | { | |
429 | struct job *jp; | |
430 | int backgnd = (n->type == NBACKGND); | |
431 | ||
432 | oexitstatus = exitstatus; | |
433 | expredir(n->nredir.redirect); | |
434 | if ((!backgnd && flags & EV_EXIT && !have_traps()) || | |
435 | forkshell(jp = makejob(n, 1), n, backgnd) == 0) { | |
436 | if (backgnd) | |
437 | flags &=~ EV_TESTED; | |
438 | redirect(n->nredir.redirect, 0); | |
439 | evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ | |
440 | } else if (! backgnd) { | |
441 | INTOFF; | |
442 | exitstatus = waitforjob(jp, (int *)NULL); | |
443 | INTON; | |
444 | } else | |
445 | exitstatus = 0; | |
446 | } | |
447 | ||
448 | ||
449 | /* | |
450 | * Evaluate a redirected compound command. | |
451 | */ | |
452 | ||
453 | static void | |
454 | evalredir(union node *n, int flags) | |
455 | { | |
456 | struct jmploc jmploc; | |
457 | struct jmploc *savehandler; | |
458 | volatile int in_redirect = 1; | |
459 | ||
460 | oexitstatus = exitstatus; | |
461 | expredir(n->nredir.redirect); | |
462 | savehandler = handler; | |
463 | if (setjmp(jmploc.loc)) { | |
464 | int e; | |
465 | ||
466 | handler = savehandler; | |
467 | e = exception; | |
468 | popredir(); | |
469 | if (e == EXERROR || e == EXEXEC) { | |
470 | if (in_redirect) { | |
471 | exitstatus = 2; | |
254f12f7 | 472 | FORCEINTON; |
71aad674 A |
473 | return; |
474 | } | |
475 | } | |
476 | longjmp(handler->loc, 1); | |
477 | } else { | |
478 | INTOFF; | |
479 | handler = &jmploc; | |
480 | redirect(n->nredir.redirect, REDIR_PUSH); | |
481 | in_redirect = 0; | |
482 | INTON; | |
483 | evaltree(n->nredir.n, flags); | |
484 | } | |
485 | INTOFF; | |
486 | handler = savehandler; | |
487 | popredir(); | |
488 | INTON; | |
489 | } | |
490 | ||
491 | ||
492 | static void | |
493 | exphere(union node *redir, struct arglist *fn) | |
494 | { | |
495 | struct jmploc jmploc; | |
496 | struct jmploc *savehandler; | |
497 | struct localvar *savelocalvars; | |
498 | int need_longjmp = 0; | |
deb63bfb | 499 | unsigned char saveoptreset; |
71aad674 A |
500 | |
501 | redir->nhere.expdoc = ""; | |
502 | savelocalvars = localvars; | |
503 | localvars = NULL; | |
deb63bfb | 504 | saveoptreset = shellparam.reset; |
71aad674 A |
505 | forcelocal++; |
506 | savehandler = handler; | |
507 | if (setjmp(jmploc.loc)) | |
508 | need_longjmp = exception != EXERROR && exception != EXEXEC; | |
509 | else { | |
510 | handler = &jmploc; | |
511 | expandarg(redir->nhere.doc, fn, 0); | |
deb63bfb | 512 | redir->nhere.expdoc = fn->args[0]; |
71aad674 A |
513 | INTOFF; |
514 | } | |
515 | handler = savehandler; | |
516 | forcelocal--; | |
517 | poplocalvars(); | |
518 | localvars = savelocalvars; | |
deb63bfb | 519 | shellparam.reset = saveoptreset; |
71aad674 A |
520 | if (need_longjmp) |
521 | longjmp(handler->loc, 1); | |
522 | INTON; | |
523 | } | |
524 | ||
525 | ||
526 | /* | |
527 | * Compute the names of the files in a redirection list. | |
528 | */ | |
529 | ||
530 | static void | |
531 | expredir(union node *n) | |
532 | { | |
533 | union node *redir; | |
534 | ||
535 | for (redir = n ; redir ; redir = redir->nfile.next) { | |
536 | struct arglist fn; | |
deb63bfb | 537 | emptyarglist(&fn); |
71aad674 A |
538 | switch (redir->type) { |
539 | case NFROM: | |
540 | case NTO: | |
541 | case NFROMTO: | |
542 | case NAPPEND: | |
543 | case NCLOBBER: | |
544 | expandarg(redir->nfile.fname, &fn, EXP_TILDE); | |
deb63bfb | 545 | redir->nfile.expfname = fn.args[0]; |
71aad674 A |
546 | break; |
547 | case NFROMFD: | |
548 | case NTOFD: | |
549 | if (redir->ndup.vname) { | |
550 | expandarg(redir->ndup.vname, &fn, EXP_TILDE); | |
deb63bfb | 551 | fixredir(redir, fn.args[0], 1); |
71aad674 A |
552 | } |
553 | break; | |
554 | case NXHERE: | |
555 | exphere(redir, &fn); | |
556 | break; | |
557 | } | |
558 | } | |
559 | } | |
560 | ||
561 | ||
562 | ||
563 | /* | |
564 | * Evaluate a pipeline. All the processes in the pipeline are children | |
565 | * of the process creating the pipeline. (This differs from some versions | |
566 | * of the shell, which make the last process in a pipeline the parent | |
567 | * of all the rest.) | |
568 | */ | |
569 | ||
570 | static void | |
571 | evalpipe(union node *n) | |
572 | { | |
573 | struct job *jp; | |
574 | struct nodelist *lp; | |
575 | int pipelen; | |
576 | int prevfd; | |
577 | int pip[2]; | |
578 | ||
579 | TRACE(("evalpipe(%p) called\n", (void *)n)); | |
580 | pipelen = 0; | |
581 | for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) | |
582 | pipelen++; | |
583 | INTOFF; | |
584 | jp = makejob(n, pipelen); | |
585 | prevfd = -1; | |
586 | for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { | |
587 | prehash(lp->n); | |
588 | pip[1] = -1; | |
589 | if (lp->next) { | |
590 | if (pipe(pip) < 0) { | |
591 | if (prevfd >= 0) | |
592 | close(prevfd); | |
593 | error("Pipe call failed: %s", strerror(errno)); | |
594 | } | |
595 | } | |
596 | if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { | |
597 | INTON; | |
598 | if (prevfd > 0) { | |
599 | dup2(prevfd, 0); | |
600 | close(prevfd); | |
601 | } | |
602 | if (pip[1] >= 0) { | |
603 | if (!(prevfd >= 0 && pip[0] == 0)) | |
604 | close(pip[0]); | |
605 | if (pip[1] != 1) { | |
606 | dup2(pip[1], 1); | |
607 | close(pip[1]); | |
608 | } | |
609 | } | |
610 | evaltree(lp->n, EV_EXIT); | |
611 | } | |
612 | if (prevfd >= 0) | |
613 | close(prevfd); | |
614 | prevfd = pip[0]; | |
615 | if (pip[1] != -1) | |
616 | close(pip[1]); | |
617 | } | |
618 | INTON; | |
619 | if (n->npipe.backgnd == 0) { | |
620 | INTOFF; | |
621 | exitstatus = waitforjob(jp, (int *)NULL); | |
622 | TRACE(("evalpipe: job done exit status %d\n", exitstatus)); | |
623 | INTON; | |
624 | } else | |
625 | exitstatus = 0; | |
626 | } | |
627 | ||
628 | ||
629 | ||
630 | static int | |
631 | is_valid_fast_cmdsubst(union node *n) | |
632 | { | |
633 | ||
634 | return (n->type == NCMD); | |
635 | } | |
636 | ||
637 | /* | |
638 | * Execute a command inside back quotes. If it's a builtin command, we | |
639 | * want to save its output in a block obtained from malloc. Otherwise | |
640 | * we fork off a subprocess and get the output of the command via a pipe. | |
641 | * Should be called with interrupts off. | |
642 | */ | |
643 | ||
644 | void | |
645 | evalbackcmd(union node *n, struct backcmd *result) | |
646 | { | |
647 | int pip[2]; | |
648 | struct job *jp; | |
649 | struct stackmark smark; | |
650 | struct jmploc jmploc; | |
651 | struct jmploc *savehandler; | |
652 | struct localvar *savelocalvars; | |
deb63bfb | 653 | unsigned char saveoptreset; |
71aad674 A |
654 | |
655 | result->fd = -1; | |
656 | result->buf = NULL; | |
657 | result->nleft = 0; | |
658 | result->jp = NULL; | |
659 | if (n == NULL) { | |
660 | exitstatus = 0; | |
661 | return; | |
662 | } | |
663 | setstackmark(&smark); | |
664 | exitstatus = oexitstatus; | |
665 | if (is_valid_fast_cmdsubst(n)) { | |
666 | savelocalvars = localvars; | |
667 | localvars = NULL; | |
deb63bfb | 668 | saveoptreset = shellparam.reset; |
71aad674 A |
669 | forcelocal++; |
670 | savehandler = handler; | |
671 | if (setjmp(jmploc.loc)) { | |
672 | if (exception == EXERROR || exception == EXEXEC) | |
673 | exitstatus = 2; | |
674 | else if (exception != 0) { | |
675 | handler = savehandler; | |
676 | forcelocal--; | |
677 | poplocalvars(); | |
678 | localvars = savelocalvars; | |
deb63bfb | 679 | shellparam.reset = saveoptreset; |
71aad674 A |
680 | longjmp(handler->loc, 1); |
681 | } | |
682 | } else { | |
683 | handler = &jmploc; | |
684 | evalcommand(n, EV_BACKCMD, result); | |
685 | } | |
686 | handler = savehandler; | |
687 | forcelocal--; | |
688 | poplocalvars(); | |
689 | localvars = savelocalvars; | |
deb63bfb | 690 | shellparam.reset = saveoptreset; |
71aad674 A |
691 | } else { |
692 | if (pipe(pip) < 0) | |
693 | error("Pipe call failed: %s", strerror(errno)); | |
694 | jp = makejob(n, 1); | |
695 | if (forkshell(jp, n, FORK_NOJOB) == 0) { | |
696 | FORCEINTON; | |
697 | close(pip[0]); | |
698 | if (pip[1] != 1) { | |
699 | dup2(pip[1], 1); | |
700 | close(pip[1]); | |
701 | } | |
702 | evaltree(n, EV_EXIT); | |
703 | } | |
704 | close(pip[1]); | |
705 | result->fd = pip[0]; | |
706 | result->jp = jp; | |
707 | } | |
708 | popstackmark(&smark); | |
709 | TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", | |
710 | result->fd, result->buf, result->nleft, result->jp)); | |
711 | } | |
712 | ||
713 | static int | |
714 | mustexpandto(const char *argtext, const char *mask) | |
715 | { | |
716 | for (;;) { | |
717 | if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { | |
718 | argtext++; | |
719 | continue; | |
720 | } | |
721 | if (*argtext == CTLESC) | |
722 | argtext++; | |
723 | else if (BASESYNTAX[(int)*argtext] == CCTL) | |
724 | return (0); | |
725 | if (*argtext != *mask) | |
726 | return (0); | |
727 | if (*argtext == '\0') | |
728 | return (1); | |
729 | argtext++; | |
730 | mask++; | |
731 | } | |
732 | } | |
733 | ||
734 | static int | |
735 | isdeclarationcmd(struct narg *arg) | |
736 | { | |
737 | int have_command = 0; | |
738 | ||
739 | if (arg == NULL) | |
740 | return (0); | |
741 | while (mustexpandto(arg->text, "command")) { | |
742 | have_command = 1; | |
743 | arg = &arg->next->narg; | |
744 | if (arg == NULL) | |
745 | return (0); | |
746 | /* | |
747 | * To also allow "command -p" and "command --" as part of | |
748 | * a declaration command, add code here. | |
749 | * We do not do this, as ksh does not do it either and it | |
750 | * is not required by POSIX. | |
751 | */ | |
752 | } | |
753 | return (mustexpandto(arg->text, "export") || | |
754 | mustexpandto(arg->text, "readonly") || | |
755 | (mustexpandto(arg->text, "local") && | |
756 | (have_command || !isfunc("local")))); | |
757 | } | |
758 | ||
759 | static void | |
deb63bfb | 760 | xtracecommand(struct arglist *varlist, int argc, char **argv) |
71aad674 | 761 | { |
71aad674 | 762 | char sep = 0; |
deb63bfb A |
763 | const char *text, *p, *ps4; |
764 | int i; | |
71aad674 A |
765 | |
766 | ps4 = expandstr(ps4val()); | |
767 | out2str(ps4 != NULL ? ps4 : ps4val()); | |
deb63bfb A |
768 | for (i = 0; i < varlist->count; i++) { |
769 | text = varlist->args[i]; | |
71aad674 A |
770 | if (sep != 0) |
771 | out2c(' '); | |
deb63bfb | 772 | p = strchr(text, '='); |
71aad674 A |
773 | if (p != NULL) { |
774 | p++; | |
deb63bfb | 775 | outbin(text, p - text, out2); |
71aad674 A |
776 | out2qstr(p); |
777 | } else | |
deb63bfb | 778 | out2qstr(text); |
71aad674 A |
779 | sep = ' '; |
780 | } | |
deb63bfb A |
781 | for (i = 0; i < argc; i++) { |
782 | text = argv[i]; | |
71aad674 A |
783 | if (sep != 0) |
784 | out2c(' '); | |
deb63bfb | 785 | out2qstr(text); |
71aad674 A |
786 | sep = ' '; |
787 | } | |
788 | out2c('\n'); | |
789 | flushout(&errout); | |
790 | } | |
791 | ||
792 | /* | |
793 | * Check if a builtin can safely be executed in the same process, | |
794 | * even though it should be in a subshell (command substitution). | |
795 | * Note that jobid, jobs, times and trap can show information not | |
796 | * available in a child process; this is deliberate. | |
797 | * The arguments should already have been expanded. | |
798 | */ | |
799 | static int | |
800 | safe_builtin(int idx, int argc, char **argv) | |
801 | { | |
254f12f7 A |
802 | /* Generated from builtins.def. */ |
803 | if (safe_builtin_always(idx)) | |
71aad674 A |
804 | return (1); |
805 | if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || | |
806 | idx == UMASKCMD) | |
807 | return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); | |
808 | if (idx == SETCMD) | |
809 | return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || | |
810 | argv[1][0] == '+') && argv[1][1] == 'o' && | |
811 | argv[1][2] == '\0')); | |
812 | return (0); | |
813 | } | |
814 | ||
815 | /* | |
816 | * Execute a simple command. | |
817 | * Note: This may or may not return if (flags & EV_EXIT). | |
818 | */ | |
819 | ||
820 | static void | |
821 | evalcommand(union node *cmd, int flags, struct backcmd *backcmd) | |
822 | { | |
823 | union node *argp; | |
824 | struct arglist arglist; | |
825 | struct arglist varlist; | |
826 | char **argv; | |
827 | int argc; | |
828 | char **envp; | |
829 | int varflag; | |
71aad674 A |
830 | int mode; |
831 | int pip[2]; | |
832 | struct cmdentry cmdentry; | |
833 | struct job *jp; | |
834 | struct jmploc jmploc; | |
835 | struct jmploc *savehandler; | |
836 | char *savecmdname; | |
837 | struct shparam saveparam; | |
838 | struct localvar *savelocalvars; | |
839 | struct parsefile *savetopfile; | |
840 | volatile int e; | |
841 | char *lastarg; | |
254f12f7 | 842 | int signaled; |
71aad674 A |
843 | int do_clearcmdentry; |
844 | const char *path = pathval(); | |
deb63bfb | 845 | int i; |
71aad674 A |
846 | |
847 | /* First expand the arguments. */ | |
848 | TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); | |
deb63bfb A |
849 | emptyarglist(&arglist); |
850 | emptyarglist(&varlist); | |
71aad674 A |
851 | varflag = 1; |
852 | jp = NULL; | |
853 | do_clearcmdentry = 0; | |
854 | oexitstatus = exitstatus; | |
855 | exitstatus = 0; | |
deb63bfb A |
856 | /* Add one slot at the beginning for tryexec(). */ |
857 | appendarglist(&arglist, nullstr); | |
71aad674 A |
858 | for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { |
859 | if (varflag && isassignment(argp->narg.text)) { | |
860 | expandarg(argp, varflag == 1 ? &varlist : &arglist, | |
861 | EXP_VARTILDE); | |
862 | continue; | |
863 | } else if (varflag == 1) | |
864 | varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; | |
865 | expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); | |
866 | } | |
deb63bfb | 867 | appendarglist(&arglist, nullstr); |
71aad674 | 868 | expredir(cmd->ncmd.redirect); |
deb63bfb A |
869 | argc = arglist.count - 2; |
870 | argv = &arglist.args[1]; | |
71aad674 | 871 | |
deb63bfb | 872 | argv[argc] = NULL; |
71aad674 A |
873 | lastarg = NULL; |
874 | if (iflag && funcnest == 0 && argc > 0) | |
deb63bfb | 875 | lastarg = argv[argc - 1]; |
71aad674 A |
876 | |
877 | /* Print the command if xflag is set. */ | |
878 | if (xflag) | |
deb63bfb | 879 | xtracecommand(&varlist, argc, argv); |
71aad674 A |
880 | |
881 | /* Now locate the command. */ | |
882 | if (argc == 0) { | |
883 | /* Variable assignment(s) without command */ | |
884 | cmdentry.cmdtype = CMDBUILTIN; | |
885 | cmdentry.u.index = BLTINCMD; | |
886 | cmdentry.special = 0; | |
887 | } else { | |
888 | static const char PATH[] = "PATH="; | |
889 | int cmd_flags = 0, bltinonly = 0; | |
890 | ||
891 | /* | |
892 | * Modify the command lookup path, if a PATH= assignment | |
893 | * is present | |
894 | */ | |
deb63bfb A |
895 | for (i = 0; i < varlist.count; i++) |
896 | if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { | |
897 | path = varlist.args[i] + sizeof(PATH) - 1; | |
71aad674 A |
898 | /* |
899 | * On `PATH=... command`, we need to make | |
900 | * sure that the command isn't using the | |
901 | * non-updated hash table of the outer PATH | |
902 | * setting and we need to make sure that | |
903 | * the hash table isn't filled with items | |
904 | * from the temporary setting. | |
905 | * | |
906 | * It would be better to forbit using and | |
907 | * updating the table while this command | |
908 | * runs, by the command finding mechanism | |
909 | * is heavily integrated with hash handling, | |
910 | * so we just delete the hash before and after | |
911 | * the command runs. Partly deleting like | |
912 | * changepatch() does doesn't seem worth the | |
913 | * bookinging effort, since most such runs add | |
914 | * directories in front of the new PATH. | |
915 | */ | |
916 | clearcmdentry(); | |
917 | do_clearcmdentry = 1; | |
918 | } | |
919 | ||
920 | for (;;) { | |
921 | if (bltinonly) { | |
922 | cmdentry.u.index = find_builtin(*argv, &cmdentry.special); | |
923 | if (cmdentry.u.index < 0) { | |
924 | cmdentry.u.index = BLTINCMD; | |
925 | argv--; | |
926 | argc++; | |
927 | break; | |
928 | } | |
929 | } else | |
930 | find_command(argv[0], &cmdentry, cmd_flags, path); | |
931 | /* implement the bltin and command builtins here */ | |
932 | if (cmdentry.cmdtype != CMDBUILTIN) | |
933 | break; | |
934 | if (cmdentry.u.index == BLTINCMD) { | |
935 | if (argc == 1) | |
936 | break; | |
937 | argv++; | |
938 | argc--; | |
939 | bltinonly = 1; | |
940 | } else if (cmdentry.u.index == COMMANDCMD) { | |
941 | if (argc == 1) | |
942 | break; | |
943 | if (!strcmp(argv[1], "-p")) { | |
944 | if (argc == 2) | |
945 | break; | |
946 | if (argv[2][0] == '-') { | |
947 | if (strcmp(argv[2], "--")) | |
948 | break; | |
949 | if (argc == 3) | |
950 | break; | |
951 | argv += 3; | |
952 | argc -= 3; | |
953 | } else { | |
954 | argv += 2; | |
955 | argc -= 2; | |
956 | } | |
957 | path = _PATH_STDPATH; | |
958 | clearcmdentry(); | |
959 | do_clearcmdentry = 1; | |
960 | } else if (!strcmp(argv[1], "--")) { | |
961 | if (argc == 2) | |
962 | break; | |
963 | argv += 2; | |
964 | argc -= 2; | |
965 | } else if (argv[1][0] == '-') | |
966 | break; | |
967 | else { | |
968 | argv++; | |
969 | argc--; | |
970 | } | |
971 | cmd_flags |= DO_NOFUNC; | |
972 | bltinonly = 0; | |
973 | } else | |
974 | break; | |
975 | } | |
976 | /* | |
977 | * Special builtins lose their special properties when | |
978 | * called via 'command'. | |
979 | */ | |
980 | if (cmd_flags & DO_NOFUNC) | |
981 | cmdentry.special = 0; | |
982 | } | |
983 | ||
984 | /* Fork off a child process if necessary. */ | |
985 | if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) | |
986 | && ((flags & EV_EXIT) == 0 || have_traps())) | |
987 | || ((flags & EV_BACKCMD) != 0 | |
988 | && (cmdentry.cmdtype != CMDBUILTIN || | |
989 | !safe_builtin(cmdentry.u.index, argc, argv)))) { | |
990 | jp = makejob(cmd, 1); | |
991 | mode = FORK_FG; | |
992 | if (flags & EV_BACKCMD) { | |
993 | mode = FORK_NOJOB; | |
994 | if (pipe(pip) < 0) | |
995 | error("Pipe call failed: %s", strerror(errno)); | |
996 | } | |
997 | if (cmdentry.cmdtype == CMDNORMAL && | |
998 | cmd->ncmd.redirect == NULL && | |
deb63bfb | 999 | varlist.count == 0 && |
71aad674 A |
1000 | (mode == FORK_FG || mode == FORK_NOJOB) && |
1001 | !disvforkset() && !iflag && !mflag) { | |
1002 | vforkexecshell(jp, argv, environment(), path, | |
1003 | cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); | |
1004 | goto parent; | |
1005 | } | |
1006 | if (forkshell(jp, cmd, mode) != 0) | |
1007 | goto parent; /* at end of routine */ | |
1008 | if (flags & EV_BACKCMD) { | |
1009 | FORCEINTON; | |
1010 | close(pip[0]); | |
1011 | if (pip[1] != 1) { | |
1012 | dup2(pip[1], 1); | |
1013 | close(pip[1]); | |
1014 | } | |
1015 | flags &= ~EV_BACKCMD; | |
1016 | } | |
1017 | flags |= EV_EXIT; | |
1018 | } | |
1019 | ||
1020 | /* This is the child process if a fork occurred. */ | |
1021 | /* Execute the command. */ | |
1022 | if (cmdentry.cmdtype == CMDFUNCTION) { | |
1023 | #ifdef DEBUG | |
1024 | trputs("Shell function: "); trargs(argv); | |
1025 | #endif | |
1026 | saveparam = shellparam; | |
1027 | shellparam.malloc = 0; | |
1028 | shellparam.reset = 1; | |
1029 | shellparam.nparam = argc - 1; | |
1030 | shellparam.p = argv + 1; | |
1031 | shellparam.optp = NULL; | |
1032 | shellparam.optnext = NULL; | |
1033 | INTOFF; | |
1034 | savelocalvars = localvars; | |
1035 | localvars = NULL; | |
1036 | reffunc(cmdentry.u.func); | |
1037 | savehandler = handler; | |
1038 | if (setjmp(jmploc.loc)) { | |
71aad674 A |
1039 | popredir(); |
1040 | unreffunc(cmdentry.u.func); | |
1041 | poplocalvars(); | |
1042 | localvars = savelocalvars; | |
deb63bfb A |
1043 | freeparam(&shellparam); |
1044 | shellparam = saveparam; | |
71aad674 A |
1045 | funcnest--; |
1046 | handler = savehandler; | |
1047 | longjmp(handler->loc, 1); | |
1048 | } | |
1049 | handler = &jmploc; | |
1050 | funcnest++; | |
1051 | redirect(cmd->ncmd.redirect, REDIR_PUSH); | |
1052 | INTON; | |
deb63bfb A |
1053 | for (i = 0; i < varlist.count; i++) |
1054 | mklocal(varlist.args[i]); | |
71aad674 A |
1055 | exitstatus = oexitstatus; |
1056 | evaltree(getfuncnode(cmdentry.u.func), | |
1057 | flags & (EV_TESTED | EV_EXIT)); | |
1058 | INTOFF; | |
1059 | unreffunc(cmdentry.u.func); | |
1060 | poplocalvars(); | |
1061 | localvars = savelocalvars; | |
1062 | freeparam(&shellparam); | |
1063 | shellparam = saveparam; | |
1064 | handler = savehandler; | |
1065 | funcnest--; | |
1066 | popredir(); | |
1067 | INTON; | |
1068 | if (evalskip == SKIPRETURN) { | |
1069 | evalskip = 0; | |
1070 | skipcount = 0; | |
1071 | } | |
1072 | if (jp) | |
1073 | exitshell(exitstatus); | |
1074 | } else if (cmdentry.cmdtype == CMDBUILTIN) { | |
1075 | #ifdef DEBUG | |
1076 | trputs("builtin command: "); trargs(argv); | |
1077 | #endif | |
1078 | mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; | |
1079 | if (flags == EV_BACKCMD) { | |
71aad674 | 1080 | memout.nextc = memout.buf; |
71aad674 A |
1081 | mode |= REDIR_BACKQ; |
1082 | } | |
1083 | savecmdname = commandname; | |
1084 | savetopfile = getcurrentfile(); | |
deb63bfb | 1085 | cmdenviron = &varlist; |
71aad674 A |
1086 | e = -1; |
1087 | savehandler = handler; | |
1088 | if (setjmp(jmploc.loc)) { | |
1089 | e = exception; | |
1090 | if (e == EXINT) | |
1091 | exitstatus = SIGINT+128; | |
1092 | else if (e != EXEXIT) | |
1093 | exitstatus = 2; | |
1094 | goto cmddone; | |
1095 | } | |
1096 | handler = &jmploc; | |
1097 | redirect(cmd->ncmd.redirect, mode); | |
1098 | outclearerror(out1); | |
1099 | /* | |
1100 | * If there is no command word, redirection errors should | |
1101 | * not be fatal but assignment errors should. | |
1102 | */ | |
1103 | if (argc == 0) | |
1104 | cmdentry.special = 1; | |
1105 | listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); | |
1106 | if (argc > 0) | |
1107 | bltinsetlocale(); | |
1108 | commandname = argv[0]; | |
1109 | argptr = argv + 1; | |
1110 | nextopt_optptr = NULL; /* initialize nextopt */ | |
1111 | builtin_flags = flags; | |
1112 | exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); | |
1113 | flushall(); | |
1114 | if (outiserror(out1)) { | |
1115 | warning("write error on stdout"); | |
1116 | if (exitstatus == 0 || exitstatus == 1) | |
1117 | exitstatus = 2; | |
1118 | } | |
1119 | cmddone: | |
1120 | if (argc > 0) | |
1121 | bltinunsetlocale(); | |
1122 | cmdenviron = NULL; | |
1123 | out1 = &output; | |
1124 | out2 = &errout; | |
1125 | freestdout(); | |
1126 | handler = savehandler; | |
1127 | commandname = savecmdname; | |
1128 | if (jp) | |
1129 | exitshell(exitstatus); | |
1130 | if (flags == EV_BACKCMD) { | |
1131 | backcmd->buf = memout.buf; | |
254f12f7 A |
1132 | backcmd->nleft = memout.buf != NULL ? |
1133 | memout.nextc - memout.buf : 0; | |
71aad674 | 1134 | memout.buf = NULL; |
254f12f7 A |
1135 | memout.nextc = NULL; |
1136 | memout.bufend = NULL; | |
1137 | memout.bufsize = 64; | |
71aad674 A |
1138 | } |
1139 | if (cmdentry.u.index != EXECCMD) | |
1140 | popredir(); | |
1141 | if (e != -1) { | |
1142 | if ((e != EXERROR && e != EXEXEC) | |
1143 | || cmdentry.special) | |
1144 | exraise(e); | |
1145 | popfilesupto(savetopfile); | |
1146 | if (flags != EV_BACKCMD) | |
1147 | FORCEINTON; | |
1148 | } | |
1149 | } else { | |
1150 | #ifdef DEBUG | |
1151 | trputs("normal command: "); trargs(argv); | |
1152 | #endif | |
1153 | redirect(cmd->ncmd.redirect, 0); | |
deb63bfb A |
1154 | for (i = 0; i < varlist.count; i++) |
1155 | setvareq(varlist.args[i], VEXPORT|VSTACK); | |
71aad674 A |
1156 | envp = environment(); |
1157 | shellexec(argv, envp, path, cmdentry.u.index); | |
1158 | /*NOTREACHED*/ | |
1159 | } | |
1160 | goto out; | |
1161 | ||
1162 | parent: /* parent process gets here (if we forked) */ | |
1163 | if (mode == FORK_FG) { /* argument to fork */ | |
1164 | INTOFF; | |
254f12f7 | 1165 | exitstatus = waitforjob(jp, &signaled); |
71aad674 | 1166 | INTON; |
254f12f7 | 1167 | if (iflag && loopnest > 0 && signaled) { |
71aad674 A |
1168 | evalskip = SKIPBREAK; |
1169 | skipcount = loopnest; | |
1170 | } | |
1171 | } else if (mode == FORK_NOJOB) { | |
1172 | backcmd->fd = pip[0]; | |
1173 | close(pip[1]); | |
1174 | backcmd->jp = jp; | |
1175 | } | |
1176 | ||
1177 | out: | |
1178 | if (lastarg) | |
1179 | setvar("_", lastarg, 0); | |
1180 | if (do_clearcmdentry) | |
1181 | clearcmdentry(); | |
1182 | } | |
1183 | ||
1184 | ||
1185 | ||
1186 | /* | |
1187 | * Search for a command. This is called before we fork so that the | |
1188 | * location of the command will be available in the parent as well as | |
1189 | * the child. The check for "goodname" is an overly conservative | |
1190 | * check that the name will not be subject to expansion. | |
1191 | */ | |
1192 | ||
1193 | static void | |
1194 | prehash(union node *n) | |
1195 | { | |
1196 | struct cmdentry entry; | |
1197 | ||
1198 | if (n && n->type == NCMD && n->ncmd.args) | |
1199 | if (goodname(n->ncmd.args->narg.text)) | |
1200 | find_command(n->ncmd.args->narg.text, &entry, 0, | |
1201 | pathval()); | |
1202 | } | |
1203 | ||
1204 | ||
1205 | ||
1206 | /* | |
1207 | * Builtin commands. Builtin commands whose functions are closely | |
1208 | * tied to evaluation are implemented here. | |
1209 | */ | |
1210 | ||
1211 | /* | |
1212 | * No command given, a bltin command with no arguments, or a bltin command | |
1213 | * with an invalid name. | |
1214 | */ | |
1215 | ||
1216 | int | |
1217 | bltincmd(int argc, char **argv) | |
1218 | { | |
1219 | if (argc > 1) { | |
1220 | out2fmt_flush("%s: not found\n", argv[1]); | |
1221 | return 127; | |
1222 | } | |
1223 | /* | |
254f12f7 | 1224 | * Preserve exitstatus of a previous possible command substitution |
71aad674 A |
1225 | * as POSIX mandates |
1226 | */ | |
1227 | return exitstatus; | |
1228 | } | |
1229 | ||
1230 | ||
1231 | /* | |
1232 | * Handle break and continue commands. Break, continue, and return are | |
1233 | * all handled by setting the evalskip flag. The evaluation routines | |
1234 | * above all check this flag, and if it is set they start skipping | |
1235 | * commands rather than executing them. The variable skipcount is | |
1236 | * the number of loops to break/continue, or the number of function | |
1237 | * levels to return. (The latter is always 1.) It should probably | |
1238 | * be an error to break out of more loops than exist, but it isn't | |
1239 | * in the standard shell so we don't make it one here. | |
1240 | */ | |
1241 | ||
1242 | int | |
1243 | breakcmd(int argc, char **argv) | |
1244 | { | |
1245 | long n; | |
1246 | char *end; | |
1247 | ||
1248 | if (argc > 1) { | |
1249 | /* Allow arbitrarily large numbers. */ | |
1250 | n = strtol(argv[1], &end, 10); | |
1251 | if (!is_digit(argv[1][0]) || *end != '\0') | |
1252 | error("Illegal number: %s", argv[1]); | |
1253 | } else | |
1254 | n = 1; | |
1255 | if (n > loopnest) | |
1256 | n = loopnest; | |
1257 | if (n > 0) { | |
1258 | evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; | |
1259 | skipcount = n; | |
1260 | } | |
1261 | return 0; | |
1262 | } | |
1263 | ||
1264 | /* | |
1265 | * The `command' command. | |
1266 | */ | |
1267 | int | |
1268 | commandcmd(int argc __unused, char **argv __unused) | |
1269 | { | |
1270 | const char *path; | |
1271 | int ch; | |
1272 | int cmd = -1; | |
1273 | ||
1274 | path = bltinlookup("PATH", 1); | |
1275 | ||
1276 | while ((ch = nextopt("pvV")) != '\0') { | |
1277 | switch (ch) { | |
1278 | case 'p': | |
1279 | path = _PATH_STDPATH; | |
1280 | break; | |
1281 | case 'v': | |
1282 | cmd = TYPECMD_SMALLV; | |
1283 | break; | |
1284 | case 'V': | |
1285 | cmd = TYPECMD_BIGV; | |
1286 | break; | |
1287 | } | |
1288 | } | |
1289 | ||
1290 | if (cmd != -1) { | |
1291 | if (*argptr == NULL || argptr[1] != NULL) | |
1292 | error("wrong number of arguments"); | |
1293 | return typecmd_impl(2, argptr - 1, cmd, path); | |
1294 | } | |
1295 | if (*argptr != NULL) | |
1296 | error("commandcmd bad call"); | |
1297 | ||
1298 | /* | |
1299 | * Do nothing successfully if no command was specified; | |
1300 | * ksh also does this. | |
1301 | */ | |
1302 | return 0; | |
1303 | } | |
1304 | ||
1305 | ||
1306 | /* | |
1307 | * The return command. | |
1308 | */ | |
1309 | ||
1310 | int | |
1311 | returncmd(int argc, char **argv) | |
1312 | { | |
1313 | int ret = argc > 1 ? number(argv[1]) : oexitstatus; | |
1314 | ||
1315 | evalskip = SKIPRETURN; | |
1316 | skipcount = 1; | |
1317 | return ret; | |
1318 | } | |
1319 | ||
1320 | ||
1321 | int | |
1322 | falsecmd(int argc __unused, char **argv __unused) | |
1323 | { | |
1324 | return 1; | |
1325 | } | |
1326 | ||
1327 | ||
1328 | int | |
1329 | truecmd(int argc __unused, char **argv __unused) | |
1330 | { | |
1331 | return 0; | |
1332 | } | |
1333 | ||
1334 | ||
1335 | int | |
1336 | execcmd(int argc, char **argv) | |
1337 | { | |
deb63bfb A |
1338 | int i; |
1339 | ||
71aad674 A |
1340 | /* |
1341 | * Because we have historically not supported any options, | |
1342 | * only treat "--" specially. | |
1343 | */ | |
1344 | if (argc > 1 && strcmp(argv[1], "--") == 0) | |
1345 | argc--, argv++; | |
1346 | if (argc > 1) { | |
71aad674 A |
1347 | iflag = 0; /* exit on error */ |
1348 | mflag = 0; | |
1349 | optschanged(); | |
deb63bfb A |
1350 | for (i = 0; i < cmdenviron->count; i++) |
1351 | setvareq(cmdenviron->args[i], VEXPORT|VSTACK); | |
71aad674 A |
1352 | shellexec(argv + 1, environment(), pathval(), 0); |
1353 | ||
1354 | } | |
1355 | return 0; | |
1356 | } | |
1357 | ||
1358 | ||
1359 | int | |
1360 | timescmd(int argc __unused, char **argv __unused) | |
1361 | { | |
1362 | struct rusage ru; | |
1363 | long shumins, shsmins, chumins, chsmins; | |
1364 | double shusecs, shssecs, chusecs, chssecs; | |
1365 | ||
1366 | if (getrusage(RUSAGE_SELF, &ru) < 0) | |
1367 | return 1; | |
1368 | shumins = ru.ru_utime.tv_sec / 60; | |
1369 | shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; | |
1370 | shsmins = ru.ru_stime.tv_sec / 60; | |
1371 | shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; | |
1372 | if (getrusage(RUSAGE_CHILDREN, &ru) < 0) | |
1373 | return 1; | |
1374 | chumins = ru.ru_utime.tv_sec / 60; | |
1375 | chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; | |
1376 | chsmins = ru.ru_stime.tv_sec / 60; | |
1377 | chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; | |
1378 | out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, | |
1379 | shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); | |
1380 | return 0; | |
1381 | } |