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