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