xref: /titanic_50/usr/src/lib/libshell/common/sh/main.c (revision efd4c9b63ad77503c101fc6c2ed8ba96c9d52964)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1982-2010 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                  David Korn <dgk@research.att.com>                   *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * UNIX shell
23  *
24  * S. R. Bourne
25  * Rewritten By David Korn
26  * AT&T Labs
27  *
28  */
29 
30 #include	<ast.h>
31 #include	<sfio.h>
32 #include	<stak.h>
33 #include	<ls.h>
34 #include	<fcin.h>
35 #include	"defs.h"
36 #include	"variables.h"
37 #include	"path.h"
38 #include	"io.h"
39 #include	"jobs.h"
40 #include	"shlex.h"
41 #include	"shnodes.h"
42 #include	"history.h"
43 #include	"timeout.h"
44 #include	"FEATURE/time"
45 #include	"FEATURE/pstat"
46 #include	"FEATURE/execargs"
47 #include	"FEATURE/externs"
48 #ifdef	_hdr_nc
49 #   include	<nc.h>
50 #endif	/* _hdr_nc */
51 
52 #define CMD_LENGTH	64
53 
54 /* These routines are referenced by this module */
55 static void	exfile(Shell_t*, Sfio_t*,int);
56 static void	chkmail(Shell_t *shp, char*);
57 #if defined(_lib_fork) && !defined(_NEXT_SOURCE)
58     static void	fixargs(char**,int);
59 #else
60 #   define fixargs(a,b)
61 #endif
62 
63 #ifndef environ
64     extern char	**environ;
65 #endif
66 
67 static struct stat lastmail;
68 static time_t	mailtime;
69 static char	beenhere = 0;
70 
71 #ifdef _lib_sigvec
72     void clearsigmask(register int sig)
73     {
74 	struct sigvec vec;
75 	if(sigvec(sig,NIL(struct sigvec*),&vec)>=0 && vec.sv_mask)
76 	{
77 		vec.sv_mask = 0;
78 		sigvec(sig,&vec,NIL(struct sigvec*));
79 	}
80     }
81 #endif /* _lib_sigvec */
82 
83 #ifdef _lib_fts_notify
84 #   include	<fts.h>
85     /* check for interrupts during tree walks */
86     static int fts_sigcheck(FTS* fp, FTSENT* ep, void* context)
87     {
88 	Shell_t *shp = (Shell_t*)context;
89 	NOT_USED(fp);
90 	NOT_USED(ep);
91 	if(shp->trapnote&SH_SIGSET)
92 	{
93 		errno = EINTR;
94 		return(-1);
95 	}
96 	return(0);
97     }
98 #endif /* _lib_fts_notify */
99 
100 #ifdef PATH_BFPATH
101 #define PATHCOMP	NIL(Pathcomp_t*)
102 #else
103 #define PATHCOMP	""
104 #endif
105 
106 /*
107  * search for file and exfile() it if it exists
108  * 1 returned if file found, 0 otherwise
109  */
110 
111 int sh_source(Shell_t *shp, Sfio_t *iop, const char *file)
112 {
113 	char*	oid;
114 	char*	nid;
115 	int	fd;
116 
117 	if (!file || !*file || (fd = path_open(file, PATHCOMP)) < 0)
118 	{
119 		REGRESS(source, "sh_source", ("%s:ENOENT", file));
120 		return 0;
121 	}
122 	oid = error_info.id;
123 	nid = error_info.id = strdup(file);
124 	shp->st.filename = path_fullname(stakptr(PATH_OFFSET));
125 	REGRESS(source, "sh_source", ("%s", file));
126 	exfile(shp, iop, fd);
127 	error_info.id = oid;
128 	free(nid);
129 	return 1;
130 }
131 
132 #ifdef S_ISSOCK
133 #define REMOTE(m)	(S_ISSOCK(m)||!(m))
134 #else
135 #define REMOTE(m)	!(m)
136 #endif
137 
138 int sh_main(int ac, char *av[], Shinit_f userinit)
139 {
140 	register char	*name;
141 	register int	fdin;
142 	register Sfio_t  *iop;
143 	register Shell_t *shp;
144 	struct stat	statb;
145 	int i, rshflag;		/* set for restricted shell */
146 	char *command;
147 #ifdef _lib_sigvec
148 	/* This is to clear mask that may be left on by rlogin */
149 	clearsigmask(SIGALRM);
150 	clearsigmask(SIGHUP);
151 	clearsigmask(SIGCHLD);
152 #endif /* _lib_sigvec */
153 #ifdef	_hdr_nc
154 	_NutConf(_NC_SET_SUFFIXED_SEARCHING, 1);
155 #endif	/* _hdr_nc */
156 	fixargs(av,0);
157 	shp = sh_init(ac,av,userinit);
158 	time(&mailtime);
159 	if(rshflag=sh_isoption(SH_RESTRICTED))
160 		sh_offoption(SH_RESTRICTED);
161 #ifdef _lib_fts_notify
162 	fts_notify(fts_sigcheck,(void*)shp);
163 #endif /* _lib_fts_notify */
164 	if(sigsetjmp(*((sigjmp_buf*)shp->jmpbuffer),0))
165 	{
166 		/* begin script execution here */
167 		sh_reinit((char**)0);
168 	}
169 	shp->fn_depth = shp->dot_depth = 0;
170 	command = error_info.id;
171 	/* set pidname '$$' */
172 	shp->pid = getpid();
173 	srand(shp->pid&0x7fff);
174 	shp->ppid = getppid();
175 	if(nv_isnull(PS4NOD))
176 		nv_putval(PS4NOD,e_traceprompt,NV_RDONLY);
177 	path_pwd(1);
178 	iop = (Sfio_t*)0;
179 #if SHOPT_BRACEPAT
180 	sh_onoption(SH_BRACEEXPAND);
181 #endif
182 	if((beenhere++)==0)
183 	{
184 		sh_onstate(SH_PROFILE);
185 		((Lex_t*)shp->lex_context)->nonstandard = 0;
186 		if(shp->ppid==1)
187 			shp->login_sh++;
188 		if(shp->login_sh >= 2)
189 			sh_onoption(SH_LOGIN_SHELL);
190 		/* decide whether shell is interactive */
191 		if(!sh_isoption(SH_INTERACTIVE) && !sh_isoption(SH_TFLAG) && !sh_isoption(SH_CFLAG) &&
192 		   sh_isoption(SH_SFLAG) && tty_check(0) && tty_check(ERRIO))
193 			sh_onoption(SH_INTERACTIVE);
194 		if(sh_isoption(SH_INTERACTIVE))
195 		{
196 			sh_onoption(SH_BGNICE);
197 			sh_onoption(SH_RC);
198 		}
199 		if(!sh_isoption(SH_RC) && (sh_isoption(SH_BASH) && !sh_isoption(SH_POSIX)
200 #if SHOPT_REMOTE
201 		   || !fstat(0, &statb) && REMOTE(statb.st_mode)
202 #endif
203 		  ))
204 			sh_onoption(SH_RC);
205 		for(i=0; i<elementsof(shp->offoptions.v); i++)
206 			shp->options.v[i] &= ~shp->offoptions.v[i];
207 		if(sh_isoption(SH_INTERACTIVE))
208 		{
209 #ifdef SIGXCPU
210 			signal(SIGXCPU,SIG_DFL);
211 #endif /* SIGXCPU */
212 #ifdef SIGXFSZ
213 			signal(SIGXFSZ,SIG_DFL);
214 #endif /* SIGXFSZ */
215 			sh_onoption(SH_MONITOR);
216 		}
217 		job_init(shp,sh_isoption(SH_LOGIN_SHELL));
218 		if(sh_isoption(SH_LOGIN_SHELL))
219 		{
220 			/*	system profile	*/
221 			sh_source(shp, iop, e_sysprofile);
222 			if(!sh_isoption(SH_NOUSRPROFILE) && !sh_isoption(SH_PRIVILEGED))
223 			{
224 				char **files = shp->login_files;
225 				while ((name = *files++) && !sh_source(shp, iop, sh_mactry(shp,name)));
226 			}
227 		}
228 		/* make sure PWD is set up correctly */
229 		path_pwd(1);
230 		if(!sh_isoption(SH_NOEXEC))
231 		{
232 			if(!sh_isoption(SH_NOUSRPROFILE) && !sh_isoption(SH_PRIVILEGED) && sh_isoption(SH_RC))
233 			{
234 #if SHOPT_BASH
235 				if(sh_isoption(SH_BASH) && !sh_isoption(SH_POSIX))
236 				{
237 #if SHOPT_SYSRC
238 					sh_source(shp, iop, e_bash_sysrc);
239 #endif
240 					sh_source(shp, iop, shp->rcfile ? shp->rcfile : sh_mactry(shp,(char*)e_bash_rc));
241 				}
242 				else
243 #endif
244 				{
245 					if(name = sh_mactry(shp,nv_getval(ENVNOD)))
246 						name = *name ? strdup(name) : (char*)0;
247 #if SHOPT_SYSRC
248 					if(!strmatch(name, "?(.)/./*"))
249 						sh_source(shp, iop, e_sysrc);
250 #endif
251 					if(name)
252 					{
253 						sh_source(shp, iop, name);
254 						free(name);
255 					}
256 				}
257 			}
258 			else if(sh_isoption(SH_INTERACTIVE) && sh_isoption(SH_PRIVILEGED))
259 				sh_source(shp, iop, e_suidprofile);
260 		}
261 		shp->st.cmdname = error_info.id = command;
262 		sh_offstate(SH_PROFILE);
263 		if(rshflag)
264 			sh_onoption(SH_RESTRICTED);
265 		/* open input file if specified */
266 		if(shp->comdiv)
267 		{
268 		shell_c:
269 			iop = sfnew(NIL(Sfio_t*),shp->comdiv,strlen(shp->comdiv),0,SF_STRING|SF_READ);
270 		}
271 		else
272 		{
273 			name = error_info.id;
274 			error_info.id = shp->shname;
275 			if(sh_isoption(SH_SFLAG))
276 				fdin = 0;
277 			else
278 			{
279 				char *sp;
280 				/* open stream should have been passed into shell */
281 				if(strmatch(name,e_devfdNN))
282 				{
283 #if !_WINIX
284 					char *cp;
285 					int type;
286 #endif
287 					fdin = (int)strtol(name+8, (char**)0, 10);
288 					if(fstat(fdin,&statb)<0)
289 						errormsg(SH_DICT,ERROR_system(1),e_open,name);
290 #if !_WINIX
291 					/*
292 					 * try to undo effect of solaris 2.5+
293 					 * change for argv for setuid scripts
294 					 */
295 					if(((type = sh_type(cp = av[0])) & SH_TYPE_SH) && (!(name = nv_getval(L_ARGNOD)) || !((type = sh_type(cp = name)) & SH_TYPE_SH)))
296 					{
297 						av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp);
298 						/*  exec to change $0 for ps */
299 						execv(pathshell(),av);
300 						/* exec fails */
301 						shp->st.dolv[0] = av[0];
302 						fixargs(shp->st.dolv,1);
303 					}
304 #endif
305 					name = av[0];
306 					sh_offoption(SH_VERBOSE);
307 					sh_offoption(SH_XTRACE);
308 				}
309 				else
310 				{
311 					int isdir = 0;
312 					if((fdin=sh_open(name,O_RDONLY,0))>=0 &&(fstat(fdin,&statb)<0 || S_ISDIR(statb.st_mode)))
313 					{
314 						close(fdin);
315 						isdir = 1;
316 						fdin = -1;
317 					}
318 					else
319 						shp->st.filename = path_fullname(name);
320 					sp = 0;
321 					if(fdin < 0 && !strchr(name,'/'))
322 					{
323 #ifdef PATH_BFPATH
324 						if(path_absolute(name,NIL(Pathcomp_t*)))
325 							sp = stakptr(PATH_OFFSET);
326 #else
327 							sp = path_absolute(name,NIL(char*));
328 #endif
329 						if(sp)
330 						{
331 							if((fdin=sh_open(sp,O_RDONLY,0))>=0)
332 								shp->st.filename = path_fullname(sp);
333 						}
334 					}
335 					if(fdin<0)
336 					{
337 						if(isdir)
338 							errno = EISDIR;
339 						 error_info.id = av[0];
340 						if(sp || errno!=ENOENT)
341 							errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_open,name);
342 						/* try sh -c 'name "$@"' */
343 						sh_onoption(SH_CFLAG);
344 						shp->comdiv = (char*)malloc(strlen(name)+7);
345 						name = strcopy(shp->comdiv,name);
346 						if(shp->st.dolc)
347 							strcopy(name," \"$@\"");
348 						goto shell_c;
349 					}
350 					if(fdin==0)
351 						fdin = sh_iomovefd(fdin);
352 				}
353 				shp->readscript = shp->shname;
354 			}
355 			error_info.id = name;
356 			shp->comdiv--;
357 #if SHOPT_ACCT
358 			sh_accinit();
359 			if(fdin != 0)
360 				sh_accbegin(error_info.id);
361 #endif	/* SHOPT_ACCT */
362 		}
363 	}
364 	else
365 	{
366 		fdin = shp->infd;
367 		fixargs(shp->st.dolv,1);
368 	}
369 	if(sh_isoption(SH_INTERACTIVE))
370 		sh_onstate(SH_INTERACTIVE);
371 	nv_putval(IFSNOD,(char*)e_sptbnl,NV_RDONLY);
372 	exfile(shp,iop,fdin);
373 	sh_done(shp,0);
374 	/* NOTREACHED */
375 	return(0);
376 }
377 
378 /*
379  * iop is not null when the input is a string
380  * fdin is the input file descriptor
381  */
382 
383 static void	exfile(register Shell_t *shp, register Sfio_t *iop,register int fno)
384 {
385 	time_t curtime;
386 	Shnode_t *t;
387 	int maxtry=IOMAXTRY, tdone=0, execflags;
388 	int states,jmpval;
389 	struct checkpt buff;
390 	sh_pushcontext(&buff,SH_JMPERREXIT);
391 	/* open input stream */
392 	nv_putval(SH_PATHNAMENOD, shp->st.filename ,NV_NOFREE);
393 	if(!iop)
394 	{
395 		if(fno > 0)
396 		{
397 			int r;
398 			if(fno < 10 && ((r=sh_fcntl(fno,F_DUPFD,10))>=10))
399 			{
400 				shp->fdstatus[r] = shp->fdstatus[fno];
401 				sh_close(fno);
402 				fno = r;
403 			}
404 			fcntl(fno,F_SETFD,FD_CLOEXEC);
405 			shp->fdstatus[fno] |= IOCLEX;
406 			iop = sh_iostream((void*)shp,fno);
407 		}
408 		else
409 			iop = sfstdin;
410 	}
411 	else
412 		fno = -1;
413 	shp->infd = fno;
414 	if(sh_isstate(SH_INTERACTIVE))
415 	{
416 		if(nv_isnull(PS1NOD))
417 			nv_putval(PS1NOD,(shp->euserid?e_stdprompt:e_supprompt),NV_RDONLY);
418 		sh_sigdone();
419 		if(sh_histinit((void*)shp))
420 			sh_onoption(SH_HISTORY);
421 	}
422 	else
423 	{
424 		if(!sh_isstate(SH_PROFILE))
425 		{
426 			buff.mode = SH_JMPEXIT;
427 			sh_onoption(SH_TRACKALL);
428 			sh_offoption(SH_MONITOR);
429 		}
430 		sh_offstate(SH_INTERACTIVE);
431 		sh_offstate(SH_MONITOR);
432 		sh_offstate(SH_HISTORY);
433 		sh_offoption(SH_HISTORY);
434 	}
435 	states = sh_getstate();
436 	jmpval = sigsetjmp(buff.buff,0);
437 	if(jmpval)
438 	{
439 		Sfio_t *top;
440 		sh_iorestore((void*)shp,0,jmpval);
441 		hist_flush(shp->hist_ptr);
442 		sfsync(shp->outpool);
443 		shp->st.execbrk = shp->st.breakcnt = 0;
444 		/* check for return from profile or env file */
445 		if(sh_isstate(SH_PROFILE) && (jmpval==SH_JMPFUN || jmpval==SH_JMPEXIT))
446 		{
447 			sh_setstate(states);
448 			goto done;
449 		}
450 		if(!sh_isoption(SH_INTERACTIVE) || sh_isstate(SH_FORKED) || (jmpval > SH_JMPERREXIT && job_close(shp) >=0))
451 		{
452 			sh_offstate(SH_INTERACTIVE);
453 			sh_offstate(SH_MONITOR);
454 			goto done;
455 		}
456 		/* skip over remaining input */
457 		if(top = fcfile())
458 		{
459 			while(fcget()>0);
460 			fcclose();
461 			while(top=sfstack(iop,SF_POPSTACK))
462 				sfclose(top);
463 		}
464 		/* make sure that we own the terminal */
465 #ifdef SIGTSTP
466 		tcsetpgrp(job.fd,shp->pid);
467 #endif /* SIGTSTP */
468 	}
469 	/* error return here */
470 	sfclrerr(iop);
471 	sh_setstate(states);
472 	shp->st.optindex = 1;
473 	opt_info.offset = 0;
474 	shp->st.loopcnt = 0;
475 	shp->trapnote = 0;
476 	shp->intrap = 0;
477 	error_info.line = 1;
478 	shp->inlineno = 1;
479 	shp->binscript = 0;
480 	if(sfeof(iop))
481 		goto eof_or_error;
482 	/* command loop */
483 	while(1)
484 	{
485 		shp->nextprompt = 1;
486 		sh_freeup(shp);
487 		stakset(NIL(char*),0);
488 		exitset();
489 		sh_offstate(SH_STOPOK);
490 		sh_offstate(SH_ERREXIT);
491 		sh_offstate(SH_VERBOSE);
492 		sh_offstate(SH_TIMING);
493 		sh_offstate(SH_GRACE);
494 		sh_offstate(SH_TTYWAIT);
495 		if(sh_isoption(SH_VERBOSE))
496 			sh_onstate(SH_VERBOSE);
497 		sh_onstate(SH_ERREXIT);
498 		/* -eim  flags don't apply to profiles */
499 		if(sh_isstate(SH_PROFILE))
500 		{
501 			sh_offstate(SH_INTERACTIVE);
502 			sh_offstate(SH_ERREXIT);
503 			sh_offstate(SH_MONITOR);
504 		}
505 		if(sh_isstate(SH_INTERACTIVE) && !tdone)
506 		{
507 			register char *mail;
508 #ifdef JOBS
509 			sh_offstate(SH_MONITOR);
510 			if(sh_isoption(SH_MONITOR))
511 				sh_onstate(SH_MONITOR);
512 			if(job.pwlist)
513 			{
514 				job_walk(sfstderr,job_list,JOB_NFLAG,(char**)0);
515 				job_wait((pid_t)0);
516 			}
517 #endif	/* JOBS */
518 			if((mail=nv_getval(MAILPNOD)) || (mail=nv_getval(MAILNOD)))
519 			{
520 				time(&curtime);
521 				if ((curtime - mailtime) >= sh_mailchk)
522 				{
523 					chkmail(shp,mail);
524 					mailtime = curtime;
525 				}
526 			}
527 			if(shp->hist_ptr)
528 				hist_eof(shp->hist_ptr);
529 			/* sets timeout for command entry */
530 			shp->timeout = shp->st.tmout;
531 #if SHOPT_TIMEOUT
532 			if(shp->timeout <= 0 || shp->timeout > SHOPT_TIMEOUT)
533 				shp->timeout = SHOPT_TIMEOUT;
534 #endif /* SHOPT_TIMEOUT */
535 			shp->inlineno = 1;
536 			error_info.line = 1;
537 			shp->exitval = 0;
538 			shp->trapnote = 0;
539 			if(buff.mode == SH_JMPEXIT)
540 			{
541 				buff.mode = SH_JMPERREXIT;
542 #ifdef DEBUG
543 				errormsg(SH_DICT,ERROR_warn(0),"%d: mode changed to JMP_EXIT",getpid());
544 #endif
545 			}
546 		}
547 		errno = 0;
548 		if(tdone || !sfreserve(iop,0,0))
549 		{
550 		eof_or_error:
551 			if(sh_isstate(SH_INTERACTIVE) && !sferror(iop))
552 			{
553 				if(--maxtry>0 && sh_isoption(SH_IGNOREEOF) &&
554 					 !sferror(sfstderr) && (shp->fdstatus[fno]&IOTTY))
555 				{
556 					sfclrerr(iop);
557 					errormsg(SH_DICT,0,e_logout);
558 					continue;
559 				}
560 				else if(job_close(shp)<0)
561 					continue;
562 			}
563 			if(errno==0 && sferror(iop) && --maxtry>0)
564 			{
565 				sfclrlock(iop);
566 				sfclrerr(iop);
567 				continue;
568 			}
569 			goto done;
570 		}
571 		maxtry = IOMAXTRY;
572 		if(sh_isstate(SH_INTERACTIVE) && shp->hist_ptr)
573 		{
574 			job_wait((pid_t)0);
575 			hist_eof(shp->hist_ptr);
576 			sfsync(sfstderr);
577 		}
578 		if(sh_isoption(SH_HISTORY))
579 			sh_onstate(SH_HISTORY);
580 		job.waitall = job.curpgid = 0;
581 		error_info.flags |= ERROR_INTERACTIVE;
582 		t = (Shnode_t*)sh_parse(shp,iop,0);
583 		if(!sh_isstate(SH_INTERACTIVE) && !sh_isstate(SH_CFLAG))
584 			error_info.flags &= ~ERROR_INTERACTIVE;
585 		shp->readscript = 0;
586 		if(sh_isstate(SH_INTERACTIVE) && shp->hist_ptr)
587 			hist_flush(shp->hist_ptr);
588 		sh_offstate(SH_HISTORY);
589 		if(t)
590 		{
591 			execflags = sh_state(SH_ERREXIT)|sh_state(SH_INTERACTIVE);
592 			/* The last command may not have to fork */
593 			if(!sh_isstate(SH_PROFILE) && !sh_isstate(SH_INTERACTIVE) &&
594 				(fno<0 || !(shp->fdstatus[fno]&(IOTTY|IONOSEEK)))
595 				&& !sfreserve(iop,0,0))
596 			{
597 					execflags |= sh_state(SH_NOFORK);
598 			}
599 			shp->st.execbrk = 0;
600 			sh_exec(t,execflags);
601 			if(shp->forked)
602 			{
603 				sh_offstate(SH_INTERACTIVE);
604 				goto done;
605 			}
606 			/* This is for sh -t */
607 			if(sh_isoption(SH_TFLAG) && !sh_isstate(SH_PROFILE))
608 				tdone++;
609 		}
610 	}
611 done:
612 	sh_popcontext(&buff);
613 	if(sh_isstate(SH_INTERACTIVE))
614 	{
615 		sfputc(sfstderr,'\n');
616 		job_close(shp);
617 	}
618 	if(jmpval == SH_JMPSCRIPT)
619 		siglongjmp(*shp->jmplist,jmpval);
620 	else if(jmpval == SH_JMPEXIT)
621 		sh_done(shp,0);
622 	if(fno>0)
623 		sh_close(fno);
624 	if(shp->st.filename)
625 		free((void*)shp->st.filename);
626 	shp->st.filename = 0;
627 }
628 
629 
630 /* prints out messages if files in list have been modified since last call */
631 static void chkmail(Shell_t *shp, char *files)
632 {
633 	register char *cp,*sp,*qp;
634 	register char save;
635 	struct argnod *arglist=0;
636 	int	offset = staktell();
637 	char 	*savstak=stakptr(0);
638 	struct stat	statb;
639 	if(*(cp=files) == 0)
640 		return;
641 	sp = cp;
642 	do
643 	{
644 		/* skip to : or end of string saving first '?' */
645 		for(qp=0;*sp && *sp != ':';sp++)
646 			if((*sp == '?' || *sp=='%') && qp == 0)
647 				qp = sp;
648 		save = *sp;
649 		*sp = 0;
650 		/* change '?' to end-of-string */
651 		if(qp)
652 			*qp = 0;
653 		do
654 		{
655 			/* see if time has been modified since last checked
656 			 * and the access time <= the modification time
657 			 */
658 			if(stat(cp,&statb) >= 0 && statb.st_mtime >= mailtime
659 				&& statb.st_atime <= statb.st_mtime)
660 			{
661 				/* check for directory */
662 				if(!arglist && S_ISDIR(statb.st_mode))
663 				{
664 					/* generate list of directory entries */
665 					path_complete(cp,"/*",&arglist);
666 				}
667 				else
668 				{
669 					/*
670 					 * If the file has shrunk,
671 					 * or if the size is zero
672 					 * then don't print anything
673 					 */
674 					if(statb.st_size &&
675 						(  statb.st_ino != lastmail.st_ino
676 						|| statb.st_dev != lastmail.st_dev
677 						|| statb.st_size > lastmail.st_size))
678 					{
679 						/* save and restore $_ */
680 						char *save = shp->lastarg;
681 						shp->lastarg = cp;
682 						errormsg(SH_DICT,0,sh_mactry(shp,qp?qp+1:(char*)e_mailmsg));
683 						shp->lastarg = save;
684 					}
685 					lastmail = statb;
686 					break;
687 				}
688 			}
689 			if(arglist)
690 			{
691 				cp = arglist->argval;
692 				arglist = arglist->argchn.ap;
693 			}
694 			else
695 				cp = 0;
696 		}
697 		while(cp);
698 		if(qp)
699 			*qp = '?';
700 		*sp++ = save;
701 		cp = sp;
702 	}
703 	while(save);
704 	stakset(savstak,offset);
705 }
706 
707 #undef EXECARGS
708 #undef PSTAT
709 #if defined(_hdr_execargs) && defined(pdp11)
710 #   include	<execargs.h>
711 #   define EXECARGS	1
712 #endif
713 
714 #if defined(_lib_pstat) && defined(_sys_pstat)
715 #   include	<sys/pstat.h>
716 #   define PSTAT	1
717 #endif
718 
719 #if defined(_lib_fork) && !defined(_NEXT_SOURCE)
720 /*
721  * fix up command line for ps command
722  * mode is 0 for initialization
723  */
724 static void fixargs(char **argv, int mode)
725 {
726 #if EXECARGS
727 	*execargs=(char *)argv;
728 #else
729 	static char *buff;
730 	static int command_len;
731 	register char *cp;
732 	int offset=0,size;
733 #   ifdef PSTAT
734 	union pstun un;
735 	if(mode==0)
736 	{
737 		struct pst_static st;
738 		un.pst_static = &st;
739 		if(pstat(PSTAT_STATIC, un, sizeof(struct pst_static), 1, 0)<0)
740 			return;
741 		command_len = st.command_length;
742 		return;
743 	}
744 	stakseek(command_len+2);
745 	buff = stakseek(0);
746 #   else
747 	if(mode==0)
748 	{
749 		buff = argv[0];
750 		while(cp = *argv++)
751 			command_len += strlen(cp)+1;
752 		if(environ && *environ==buff+command_len)
753 		{
754 			for(argv=environ; cp = *argv; cp++)
755 			{
756 				if(command_len > CMD_LENGTH)
757 				{
758 					command_len = CMD_LENGTH;
759 					break;
760 				}
761 				*argv++ = strdup(cp);
762 				command_len += strlen(cp)+1;
763 			}
764 		}
765 		command_len -= 1;
766 		return;
767 	}
768 #   endif /* PSTAT */
769 	if(command_len==0)
770 		return;
771 	while((cp = *argv++) && offset < command_len)
772 	{
773 		if(offset + (size=strlen(cp)) >= command_len)
774 			size = command_len - offset;
775 		memcpy(buff+offset,cp,size);
776 		offset += size;
777 		buff[offset++] = ' ';
778 	}
779 	buff[offset-1] = 0;
780 #   ifdef PSTAT
781 	un.pst_command = stakptr(0);
782 	pstat(PSTAT_SETCMD,un,0,0,0);
783 #   endif /* PSTAT */
784 #endif /* EXECARGS */
785 }
786 #endif /* _lib_fork */
787