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