1 /***********************************************************************
2  *                                                                      *
3  *               This software is part of the ast package               *
4  *          Copyright (c) 1982-2014 AT&T Intellectual Property          *
5  *                      and is licensed under the                       *
6  *                 Eclipse Public License, Version 1.0                  *
7  *                    by AT&T Intellectual Property                     *
8  *                                                                      *
9  *                A copy of the License is available at                 *
10  *          http://www.eclipse.org/org/documents/epl-v10.html           *
11  *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12  *                                                                      *
13  *              Information and Software Systems Research               *
14  *                            AT&T Research                             *
15  *                           Florham Park NJ                            *
16  *                                                                      *
17  *                    David Korn <dgkorn@gmail.com>                     *
18  *                                                                      *
19  ***********************************************************************/
20 //
21 // UNIX shell
22 //
23 // S. R. Bourne
24 // Rewritten By David Korn
25 // AT&T Labs
26 //
27 #include "config_ast.h"  // IWYU pragma: keep
28 
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <setjmp.h>
32 #include <signal.h>
33 #include <stdbool.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <time.h>
38 #include <unistd.h>
39 
40 #include "argnod.h"
41 #include "ast.h"
42 #include "ast_assert.h"
43 #include "defs.h"
44 #include "error.h"
45 #include "fault.h"
46 #include "fcin.h"
47 #include "history.h"
48 #include "io.h"
49 #include "jobs.h"
50 #include "name.h"
51 #include "option.h"
52 #include "path.h"
53 #include "sfio.h"
54 #include "shlex.h"
55 #include "shnodes.h"
56 #include "stk.h"
57 #include "terminal.h"
58 #include "variables.h"
59 
60 // These routines are referenced by this module.
61 static_fn void exfile(Shell_t *, Sfio_t *, int);
62 static_fn void chkmail(Shell_t *shp, char *);
63 
64 #ifndef environ
65 extern char **environ;
66 #endif
67 
68 static struct stat lastmail;
69 static time_t mailtime;
70 static char beenhere = 0;
71 
72 #ifdef PATH_BFPATH
73 #define PATHCOMP NULL
74 #else
75 #define PATHCOMP ""
76 #endif
77 
78 //
79 // Search for file and exfile() it if it exists.
80 // Rreturn 1 if file found, 0 otherwise.
81 //
sh_source(Shell_t * shp,Sfio_t * iop,const char * file)82 bool sh_source(Shell_t *shp, Sfio_t *iop, const char *file) {
83     char *oid;
84     char *nid;
85     int fd;
86 
87     if (!file || !*file || (fd = path_open(shp, file, PATHCOMP)) < 0) {
88         return false;
89     }
90     oid = error_info.id;
91     nid = error_info.id = strdup(file);
92     shp->st.filename = path_fullname(shp, stkptr(shp->stk, PATH_OFFSET));
93     exfile(shp, iop, fd);
94     error_info.id = oid;
95     free(nid);
96     return true;
97 }
98 
sh_main(int ac,char * av[],Shinit_f userinit)99 int sh_main(int ac, char *av[], Shinit_f userinit) {
100     int fdin = 0;
101     char *name;
102     Sfio_t *iop;
103     Shell_t *shp;
104     struct stat statb;
105     int i;        /* set for restricted shell */
106     bool rshflag; /* set for restricted shell */
107     char *command;
108 
109     // Make sure we weren't started with several critical signals blocked from delivery.
110     sh_sigaction(SIGALRM, SIG_UNBLOCK);
111     sh_sigaction(SIGCHLD, SIG_UNBLOCK);
112     sh_sigaction(SIGHUP, SIG_UNBLOCK);
113 
114     shp = sh_init(ac, av, userinit);
115     set_debug_filename(av[0]);
116     time(&mailtime);
117     rshflag = sh_isoption(shp, SH_RESTRICTED);
118     if (rshflag) sh_offoption(shp, SH_RESTRICTED);
119     if (sigsetjmp(shp->jmpbuffer->buff, 0)) {
120         // Begin script execution here.
121         sh_reinit(shp, NULL);
122         shp->gd->pid = getpid();
123         shp->gd->ppid = getppid();
124     }
125     shp->fn_depth = shp->dot_depth = 0;
126     command = error_info.id;
127     // Set pidname '$$'.
128     srand(shp->gd->pid & 0x7fff);
129     if (nv_isnull(VAR_PS4)) nv_putval(VAR_PS4, e_traceprompt, NV_RDONLY);
130     path_pwd(shp);
131     iop = NULL;
132     sh_onoption(shp, SH_BRACEEXPAND);
133     if ((beenhere++) == 0) {
134         sh_onstate(shp, SH_PROFILE);
135         shp->lex_context->nonstandard = 0;
136         if (shp->gd->ppid == 1) shp->login_sh++;
137         if (shp->login_sh >= 2) sh_onoption(shp, SH_LOGIN_SHELL);
138         // Decide whether shell is interactive.
139         if (!sh_isoption(shp, SH_INTERACTIVE) && !sh_isoption(shp, SH_TFLAG) &&
140             !sh_isoption(shp, SH_CFLAG) && sh_isoption(shp, SH_SFLAG) && tty_check(0) &&
141             tty_check(STDERR_FILENO)) {
142             sh_onoption(shp, SH_INTERACTIVE);
143         }
144         if (sh_isoption(shp, SH_INTERACTIVE)) {
145             sh_onoption(shp, SH_BGNICE);
146             sh_onoption(shp, SH_RC);
147         }
148         if (!sh_isoption(shp, SH_RC) &&
149             (sh_isoption(shp, SH_BASH) && !sh_isoption(shp, SH_POSIX))) {
150             sh_onoption(shp, SH_RC);
151         }
152         for (i = 0; i < elementsof(shp->offoptions.v); i++) {
153             shp->options.v[i] &= ~shp->offoptions.v[i];
154         }
155         if (sh_isoption(shp, SH_INTERACTIVE)) {
156 #ifdef SIGXCPU
157             sh_signal(SIGXCPU, (sh_sigfun_t)(SIG_DFL));
158 #endif  // SIGXCPU
159 #ifdef SIGXFSZ
160             sh_signal(SIGXFSZ, (sh_sigfun_t)(SIG_DFL));
161 #endif  // SIGXFSZ
162             sh_onoption(shp, SH_MONITOR);
163         }
164         job_init(shp, sh_isoption(shp, SH_LOGIN_SHELL));
165         sh_source(shp, iop, INSTALL_PREFIX "/share/ksh/config.ksh");
166         if (sh_isoption(shp, SH_LOGIN_SHELL)) {
167             //  System profile.
168             sh_source(shp, iop, e_sysprofile);
169             if (!sh_isoption(shp, SH_NOUSRPROFILE) && !sh_isoption(shp, SH_PRIVILEGED)) {
170                 char **files = shp->gd->login_files;
171                 while ((name = *files++) && !sh_source(shp, iop, sh_mactry(shp, name))) {
172                     ;  // empty loop
173                 }
174             }
175         }
176         // Make sure PWD is set up correctly.
177         path_pwd(shp);
178         if (!sh_isoption(shp, SH_NOEXEC)) {
179             if (!sh_isoption(shp, SH_NOUSRPROFILE) && !sh_isoption(shp, SH_PRIVILEGED) &&
180                 sh_isoption(shp, SH_RC)) {
181 #if SHOPT_BASH
182                 if (sh_isoption(shp, SH_BASH) && !sh_isoption(shp, SH_POSIX)) {
183                     sh_source(shp, iop, e_bash_sysrc);
184                     sh_source(
185                         shp, iop,
186                         shp->gd->rcfile ? shp->gd->rcfile : sh_mactry(shp, (char *)e_bash_rc));
187                 } else
188 #endif
189                 {
190                     name = sh_mactry(shp, nv_getval(VAR_ENV));
191                     if (name) name = *name ? strdup(name) : NULL;
192                     if (!name || !strmatch(name, "?(.)/./*")) sh_source(shp, iop, e_sysrc);
193                     if (name) {
194                         sh_source(shp, iop, name);
195                         free(name);
196                     }
197                 }
198             } else if (sh_isoption(shp, SH_INTERACTIVE) && sh_isoption(shp, SH_PRIVILEGED)) {
199                 sh_source(shp, iop, e_suidprofile);
200             }
201         }
202         // Add enum type _Bool.
203         i = 0;
204         if (sh_isoption(shp, SH_XTRACE)) {
205             i = 1;
206             sh_offoption(shp, SH_XTRACE);
207         }
208         if (sh_isoption(shp, SH_NOEXEC)) {
209             i |= 2;
210             sh_offoption(shp, SH_NOEXEC);
211         }
212         if (sh_isoption(shp, SH_VERBOSE)) {
213             i |= 4;
214             sh_offoption(shp, SH_VERBOSE);
215         }
216         sh_trap(shp, "enum _Bool=(false true) ;", 0);
217         if (i & 1) sh_onoption(shp, SH_XTRACE);
218         if (i & 2) sh_onoption(shp, SH_NOEXEC);
219         if (i & 4) sh_onoption(shp, SH_VERBOSE);
220         shp->st.cmdname = error_info.id = command;
221         sh_offstate(shp, SH_PROFILE);
222         if (rshflag) sh_onoption(shp, SH_RESTRICTED);
223         // Open input file if specified.
224         if (shp->comdiv) {
225         shell_c:
226             iop = sfnew(NULL, shp->comdiv, strlen(shp->comdiv), 0, SF_STRING | SF_READ);
227         } else {
228             name = error_info.id;
229             error_info.id = shp->shname;
230             if (sh_isoption(shp, SH_SFLAG)) {
231                 fdin = 0;
232             } else {
233                 char *sp;
234                 /* open stream should have been passed into shell */
235                 if (strmatch(name, e_devfdNN)) {
236 #if !__CYGWIN__
237                     char *cp;
238                     int type;
239 #endif
240                     fdin = (int)strtol(name + 8, NULL, 10);
241                     if (fstat(fdin, &statb) < 0) {
242                         errormsg(SH_DICT, ERROR_system(1), e_open, name);
243                         __builtin_unreachable();
244                     }
245 #if !__CYGWIN__
246                     //
247                     // Try to undo effect of solaris 2.5+ change for argv for setuid scripts.
248                     //
249                     if (shp->st.repl_index > 0) av[shp->st.repl_index] = shp->st.repl_arg;
250                     if (((type = sh_type(cp = av[0])) & SH_TYPE_SH) &&
251                         (name = nv_getval(VAR_underscore)) &&
252                         (!((type = sh_type(cp = name)) & SH_TYPE_SH))) {
253                         av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp);
254                         // Exec to change $0 for ps.
255                         execv(pathshell(), av);
256                         // Exec failed.
257                         shp->st.dolv[0] = av[0];
258                     }
259 #endif
260                     name = av[0];
261                     sh_offoption(shp, SH_VERBOSE);
262                     sh_offoption(shp, SH_XTRACE);
263                 } else {
264                     int isdir = 0;
265                     if ((fdin = sh_open(name, O_RDONLY, 0)) >= 0 &&
266                         (fstat(fdin, &statb) < 0 || S_ISDIR(statb.st_mode))) {
267                         sh_close(fdin);
268                         isdir = 1;
269                         fdin = -1;
270                     } else {
271                         shp->st.filename = path_fullname(shp, name);
272                     }
273                     sp = NULL;
274                     if (fdin < 0 && !strchr(name, '/')) {
275 #ifdef PATH_BFPATH
276                         if (path_absolute(shp, name, NULL)) {
277                             sp = stkptr(shp->stk, PATH_OFFSET);
278                         }
279 #else
280                         sp = path_absolute(shp, name, NULL);
281 #endif
282                         if (sp) {
283                             fdin = sh_open(sp, O_RDONLY, 0);
284                             if (fdin >= 0) shp->st.filename = path_fullname(shp, sp);
285                         }
286                     }
287                     if (fdin < 0) {
288                         if (isdir) errno = EISDIR;
289                         error_info.id = av[0];
290                         if (sp || errno != ENOENT) {
291                             errormsg(SH_DICT, ERROR_system(ERROR_NOEXEC), e_open, name);
292                             __builtin_unreachable();
293                         }
294                         // Try sh -c 'name "$@"'.
295                         sh_onoption(shp, SH_CFLAG);
296                         shp->comdiv = malloc(strlen(name) + 7);
297                         name = stpcpy(shp->comdiv, name);
298                         if (shp->st.dolc) stpcpy(name, " \"$@\"");
299                         goto shell_c;
300                     }
301                     if (fdin == 0) fdin = sh_iomovefd(shp, fdin);
302                 }
303                 shp->readscript = shp->shname;
304             }
305             error_info.id = name;
306             shp->comdiv--;
307         }
308     } else {
309         fdin = shp->infd;
310     }
311 
312     if (sh_isoption(shp, SH_INTERACTIVE)) sh_onstate(shp, SH_INTERACTIVE);
313     nv_putval(VAR_IFS, (char *)e_sptbnl, NV_RDONLY);
314     exfile(shp, iop, fdin);
315     sh_done(shp, 0);
316     // NOTREACHED
317     // TODO: If this isn't reached it should probably be an abort() call.
318     return 0;
319 }
320 
321 //
322 // <iop> is not null when the input is a string.
323 // <fno> is the input file descriptor.
324 //
exfile(Shell_t * shp,Sfio_t * iop,int fno)325 static_fn void exfile(Shell_t *shp, Sfio_t *iop, int fno) {
326     time_t curtime;
327     Shnode_t *t;
328     int maxtry = IOMAXTRY, tdone = 0, execflags;
329     int states, jmpval;
330     checkpt_t buff;
331 
332     sh_pushcontext(shp, &buff, SH_JMPERREXIT);
333     // Open input stream.
334     nv_putval(VAR_sh_file, shp->st.filename, NV_NOFREE);
335     if (!iop) {
336         if (fno > 0) {
337             int r;
338             if (fno < 10 && ((r = sh_fcntl(fno, F_DUPFD, 10)) >= 10)) {
339                 shp->fdstatus[r] = shp->fdstatus[fno];
340                 sh_close(fno);
341                 fno = r;
342             }
343             (void)fcntl(fno, F_SETFD, FD_CLOEXEC);
344             shp->fdstatus[fno] |= IOCLEX;
345             iop = sh_iostream(shp, fno, fno);
346         } else {
347             iop = sfstdin;
348         }
349     } else {
350         fno = -1;
351     }
352     shp->infd = fno;
353     if (sh_isstate(shp, SH_INTERACTIVE)) {
354         if (nv_isnull(VAR_PS1)) {
355             nv_putval(VAR_PS1, (shp->gd->euserid ? e_stdprompt : e_supprompt), NV_RDONLY);
356         }
357         sh_sigdone(shp);
358         if (sh_histinit(shp)) sh_onoption(shp, SH_HISTORY);
359     } else {
360         if (!sh_isstate(shp, SH_PROFILE)) {
361             buff.mode = SH_JMPEXIT;
362             sh_onoption(shp, SH_TRACKALL);
363         }
364         sh_offstate(shp, SH_INTERACTIVE);
365         if (sh_isoption(shp, SH_MONITOR)) sh_onstate(shp, SH_MONITOR);
366         sh_offstate(shp, SH_HISTORY);
367         sh_offoption(shp, SH_HISTORY);
368     }
369     states = sh_getstate(shp);
370     jmpval = sigsetjmp(buff.buff, 0);
371     if (jmpval) {
372         Sfio_t *top;
373         sh_iorestore(shp, 0, jmpval);
374         hist_flush(shp->gd->hist_ptr);
375         sfsync(shp->outpool);
376         shp->st.execbrk = shp->st.breakcnt = 0;
377         // Check for return from profile or env file.
378         if (sh_isstate(shp, SH_PROFILE) &&
379             (jmpval == SH_JMPFUN || jmpval == SH_JMPEXIT || jmpval == SH_JMPERREXIT)) {
380             sh_setstate(shp, states);
381             goto done;
382         }
383         if (!sh_isoption(shp, SH_INTERACTIVE) || sh_isstate(shp, SH_FORKED) ||
384             (jmpval > SH_JMPERREXIT && job_close(shp) >= 0)) {
385             sh_offstate(shp, SH_INTERACTIVE);
386             sh_offstate(shp, SH_MONITOR);
387             goto done;
388         }
389         exitset(shp);
390         // Skip over remaining input.
391         top = fcfile();
392         if (top) {
393             while (fcget() > 0) {
394                 ;  // empty loop
395             }
396             fcclose();
397             while ((top = sfstack(iop, SF_POPSTACK))) sfclose(top);
398         }
399         // Make sure that we own the terminal.
400 #ifdef SIGTSTP
401         tcsetpgrp(job.fd, shp->gd->pid);
402 #endif  // SIGTSTP
403     }
404     // Error return here.
405     sfclrerr(iop);
406     sh_setstate(shp, states);
407     shp->st.optindex = 1;
408     opt_info.offset = 0;
409     shp->st.loopcnt = 0;
410     shp->trapnote = 0;
411     shp->intrap = 0;
412     error_info.line = 1;
413     shp->inlineno = 1;
414     shp->binscript = 0;
415     shp->exittrap = 0;
416     shp->errtrap = 0;
417     shp->end_fn = 0;
418     if (sfeof(iop)) goto eof_or_error;
419     // Command loop.
420     while (1) {
421         shp->nextprompt = 1;
422         sh_freeup(shp);
423         stkset(shp->stk, NULL, 0);
424         sh_offstate(shp, SH_STOPOK);
425         sh_offstate(shp, SH_ERREXIT);
426         sh_offstate(shp, SH_VERBOSE);
427         sh_offstate(shp, SH_TIMING);
428         sh_offstate(shp, SH_GRACE);
429         sh_offstate(shp, SH_TTYWAIT);
430         if (sh_isoption(shp, SH_VERBOSE)) sh_onstate(shp, SH_VERBOSE);
431         sh_onstate(shp, SH_ERREXIT);
432         // -eim  flags don't apply to profiles.
433         if (sh_isstate(shp, SH_PROFILE)) {
434             sh_offstate(shp, SH_INTERACTIVE);
435             sh_offstate(shp, SH_ERREXIT);
436             sh_offstate(shp, SH_MONITOR);
437         }
438         if (sh_isstate(shp, SH_INTERACTIVE) && !tdone) {
439             char *mail;
440 #ifdef JOBS
441             sh_offstate(shp, SH_MONITOR);
442             if (sh_isoption(shp, SH_MONITOR)) sh_onstate(shp, SH_MONITOR);
443             if (job.pwlist) {
444                 job_walk(shp, sfstderr, job_list, JOB_NFLAG, NULL);
445                 job_wait((pid_t)0);
446             }
447 #endif  // JOBS
448             if ((mail = nv_getval(VAR_MAILPATH)) || (mail = nv_getval(VAR_MAIL))) {
449                 time(&curtime);
450                 if ((curtime - mailtime) >= sh_mailchk) {
451                     chkmail(shp, mail);
452                     mailtime = curtime;
453                 }
454             }
455             if (shp->gd->hist_ptr) hist_eof(shp->gd->hist_ptr);
456             // Sets timeout for command entry.
457             shp->timeout = shp->st.tmout;
458 #if READ_TIMEOUT > 0
459             if (shp->timeout <= 0 || shp->timeout > READ_TIMEOUT) shp->timeout = READ_TIMEOUT;
460 #endif
461             shp->inlineno = 1;
462             error_info.line = 1;
463             shp->trapnote = 0;
464             if (buff.mode == SH_JMPEXIT) {
465                 buff.mode = SH_JMPERREXIT;
466 #ifdef DEBUG
467                 errormsg(SH_DICT, ERROR_warn(0), "%d: mode changed to JMP_EXIT", getpid());
468 #endif
469             }
470         }
471         errno = 0;
472         if (tdone || !sfreserve(iop, 0, 0)) {
473         eof_or_error:
474             if (sh_isstate(shp, SH_INTERACTIVE) && !sferror(iop)) {
475                 if (--maxtry > 0 && sh_isoption(shp, SH_IGNOREEOF) && !sferror(sfstderr)) {
476                     // It is theoretically possible for fno == -1 at this point. That would be bad.
477                     assert(fno >= 0);
478                     if ((shp->fdstatus[fno] & IOTTY)) {
479                         sfclrerr(iop);
480                         errormsg(SH_DICT, 0, e_logout);
481                         continue;
482                     }
483                 } else if (job_close(shp) < 0) {
484                     continue;
485                 }
486             }
487             if (errno == 0 && sferror(iop) && --maxtry > 0) {
488                 sfclrlock(iop);
489                 sfclrerr(iop);
490                 continue;
491             }
492             goto done;
493         }
494         shp->exitval = shp->savexit;
495         maxtry = IOMAXTRY;
496         if (sh_isstate(shp, SH_INTERACTIVE) && shp->gd->hist_ptr) {
497             job_wait((pid_t)0);
498             hist_eof(shp->gd->hist_ptr);
499             sfsync(sfstderr);
500         }
501         if (sh_isoption(shp, SH_HISTORY)) sh_onstate(shp, SH_HISTORY);
502         job.waitall = job.curpgid = 0;
503         error_info.flags |= ERROR_INTERACTIVE;
504         t = sh_parse(shp, iop, 0);
505         if (!sh_isstate(shp, SH_INTERACTIVE) && !sh_isoption(shp, SH_CFLAG)) {
506             error_info.flags &= ~ERROR_INTERACTIVE;
507         }
508         shp->readscript = NULL;
509         if (sh_isstate(shp, SH_INTERACTIVE) && shp->gd->hist_ptr) hist_flush(shp->gd->hist_ptr);
510         sh_offstate(shp, SH_HISTORY);
511         if (!t) continue;
512 
513         execflags = sh_state(SH_ERREXIT) | sh_state(SH_INTERACTIVE);
514 
515         shp->st.execbrk = 0;
516         sh_exec(shp, t, execflags);
517         if (shp->forked) {
518             sh_offstate(shp, SH_INTERACTIVE);
519             goto done;
520         }
521         // This is for sh -t.
522         if (sh_isoption(shp, SH_TFLAG) && !sh_isstate(shp, SH_PROFILE)) tdone++;
523     }
524 done:
525     sh_popcontext(shp, &buff);
526     if (sh_isstate(shp, SH_INTERACTIVE)) {
527         sfputc(sfstderr, '\n');
528         job_close(shp);
529     }
530     if (jmpval == SH_JMPSCRIPT) {
531         siglongjmp(shp->jmplist->buff, jmpval);
532     } else if (jmpval == SH_JMPEXIT || jmpval == SH_JMPERREXIT) {
533         sh_done(shp, 0);
534     }
535     if (fno > 0) sh_close(fno);
536     if (shp->st.filename) {
537         free(shp->st.filename);
538         shp->st.filename = NULL;
539     }
540 }
541 
542 // Prints out messages if files in list have been modified since last call.
chkmail(Shell_t * shp,char * files)543 static_fn void chkmail(Shell_t *shp, char *files) {
544     char *cp, *sp, *qp;
545     char save;
546     struct argnod *arglist = NULL;
547     int offset = stktell(shp->stk);
548     char *savstak = stkptr(shp->stk, 0);
549     struct stat statb;
550 
551     if (*(cp = files) == 0) return;
552     sp = cp;
553     do {
554         // Skip to : or end of string saving first '?'.
555         for (qp = NULL; *sp && *sp != ':'; sp++) {
556             if ((*sp == '?' || *sp == '%') && qp == 0) qp = sp;
557         }
558         save = *sp;
559         *sp = 0;
560         // Change '?' to end-of-string.
561         if (qp) *qp = 0;
562         do {
563             // See if time has been modified since last checked and the access
564             // time <= the modification time.
565             if (sh_stat(cp, &statb) >= 0 && statb.st_mtime >= mailtime &&
566                 statb.st_atime <= statb.st_mtime) {
567                 // Check for directory.
568                 if (!arglist && S_ISDIR(statb.st_mode)) {
569                     // Generate list of directory entries.
570                     path_complete(shp, cp, "/*", &arglist);
571                 } else {
572                     // If the file has shrunk, or if the size is zero then
573                     // don't print anything.
574                     if (statb.st_size &&
575                         (statb.st_ino != lastmail.st_ino || statb.st_dev != lastmail.st_dev ||
576                          statb.st_size > lastmail.st_size)) {
577                         // Save and restore $_.
578                         char *save = shp->lastarg;
579                         shp->lastarg = cp;
580                         errormsg(SH_DICT, 0, sh_mactry(shp, qp ? qp + 1 : (char *)e_mailmsg));
581                         shp->lastarg = save;
582                     }
583                     lastmail = statb;
584                     break;
585                 }
586             }
587             if (arglist) {
588                 cp = arglist->argval;
589                 arglist = arglist->argchn.ap;
590             } else {
591                 cp = 0;
592             }
593         } while (cp);
594         if (qp) *qp = '?';
595         *sp++ = save;
596         cp = sp;
597     } while (save);
598     stkset(shp->stk, savstak, offset);
599 }
600 
601 #undef EXECARGS
602 #undef PSTAT
603 #if _hdr_execargs && defined(pdp11)
604 #include <execargs.h>
605 #define EXECARGS 1
606 #endif
607 
608 #if _lib_pstat && _hdr_sys_pstat
609 #include <sys/pstat.h>
610 #define PSTAT 1
611 #endif
612