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 * Create and manage subshells avoiding forks when possible 23 * 24 * David Korn 25 * AT&T Labs 26 * 27 */ 28 29 #include "defs.h" 30 #include <ls.h> 31 #include "io.h" 32 #include "fault.h" 33 #include "shnodes.h" 34 #include "shlex.h" 35 #include "jobs.h" 36 #include "variables.h" 37 #include "path.h" 38 39 #ifndef PIPE_BUF 40 # define PIPE_BUF 512 41 #endif 42 43 /* 44 * Note that the following structure must be the same 45 * size as the Dtlink_t structure 46 */ 47 struct Link 48 { 49 struct Link *next; 50 Namval_t *child; 51 Dt_t *dict; 52 Namval_t *node; 53 }; 54 55 /* 56 * The following structure is used for command substitution and (...) 57 */ 58 static struct subshell 59 { 60 Shell_t *shp; /* shell interpreter */ 61 struct subshell *prev; /* previous subshell data */ 62 struct subshell *pipe; /* subshell where output goes to pipe on fork */ 63 Dt_t *var; /* variable table at time of subshell */ 64 struct Link *svar; /* save shell variable table */ 65 Dt_t *sfun; /* function scope for subshell */ 66 Dt_t *salias;/* alias scope for subshell */ 67 Pathcomp_t *pathlist; /* for PATH variable */ 68 #if (ERROR_VERSION >= 20030214L) 69 struct Error_context_s *errcontext; 70 #else 71 struct errorcontext *errcontext; 72 #endif 73 Shopt_t options;/* save shell options */ 74 pid_t subpid; /* child process id */ 75 Sfio_t* saveout;/*saved standard output */ 76 char *pwd; /* present working directory */ 77 const char *shpwd; /* saved pointer to sh.pwd */ 78 void *jobs; /* save job info */ 79 mode_t mask; /* saved umask */ 80 short tmpfd; /* saved tmp file descriptor */ 81 short pipefd; /* read fd if pipe is created */ 82 char jobcontrol; 83 char monitor; 84 unsigned char fdstatus; 85 int fdsaved; /* bit make for saved files */ 86 int sig; /* signal for $$ */ 87 pid_t bckpid; 88 pid_t cpid; 89 int coutpipe; 90 int cpipe; 91 int nofork; 92 } *subshell_data; 93 94 static int subenv; 95 96 /* 97 * This routine will turn the sftmp() file into a real /tmp file or pipe 98 */ 99 void sh_subtmpfile(int pflag) 100 { 101 Shell_t *shp = &sh; 102 int fds[2]; 103 Sfoff_t off; 104 if(sfset(sfstdout,0,0)&SF_STRING) 105 { 106 register int fd; 107 register struct checkpt *pp = (struct checkpt*)shp->jmplist; 108 register struct subshell *sp = subshell_data->pipe; 109 /* save file descriptor 1 if open */ 110 if((sp->tmpfd = fd = fcntl(1,F_DUPFD,10)) >= 0) 111 { 112 fcntl(fd,F_SETFD,FD_CLOEXEC); 113 shp->fdstatus[fd] = shp->fdstatus[1]|IOCLEX; 114 close(1); 115 } 116 else if(errno!=EBADF) 117 errormsg(SH_DICT,ERROR_system(1),e_toomany); 118 if(!pflag) 119 { 120 sfdisc(sfstdout,SF_POPDISC); 121 if((fd=sffileno(sfstdout))>=0) 122 { 123 sh.fdstatus[fd] = IOREAD|IOWRITE; 124 sfsync(sfstdout); 125 if(fd==1) 126 fcntl(1,F_SETFD,0); 127 else 128 { 129 sfsetfd(sfstdout,1); 130 sh.fdstatus[1] = sh.fdstatus[fd]; 131 sh.fdstatus[fd] = IOCLOSE; 132 } 133 goto skip; 134 } 135 } 136 sh_pipe(fds); 137 sp->pipefd = fds[0]; 138 sh_fcntl(sp->pipefd,F_SETFD,FD_CLOEXEC); 139 /* write the data to the pipe */ 140 if(off = sftell(sfstdout)) 141 { 142 write(fds[1],sfsetbuf(sfstdout,(Void_t*)sfstdout,0),(size_t)off); 143 sfpurge(sfstdout); 144 } 145 sfclose(sfstdout); 146 if((sh_fcntl(fds[1],F_DUPFD, 1)) != 1) 147 errormsg(SH_DICT,ERROR_system(1),e_file+4); 148 sh_close(fds[1]); 149 skip: 150 sh_iostream(shp,1); 151 sfset(sfstdout,SF_SHARE|SF_PUBLIC,1); 152 sfpool(sfstdout,shp->outpool,SF_WRITE); 153 if(pp && pp->olist && pp->olist->strm == sfstdout) 154 pp->olist->strm = 0; 155 } 156 } 157 158 /* 159 * This routine creates a temp file if necessary and creates a subshell. 160 * The parent routine longjmps back to sh_subshell() 161 * The child continues possibly with its standard output replaced by temp file 162 */ 163 void sh_subfork(void) 164 { 165 register struct subshell *sp = subshell_data; 166 Shell_t *shp = sp->shp; 167 int curenv = shp->curenv; 168 pid_t pid; 169 /* see whether inside $(...) */ 170 if(sp->pipe) 171 sh_subtmpfile(1); 172 shp->curenv = 0; 173 if(pid = sh_fork(0,NIL(int*))) 174 { 175 shp->curenv = curenv; 176 /* this is the parent part of the fork */ 177 if(sp->subpid==0) 178 sp->subpid = pid; 179 siglongjmp(*shp->jmplist,SH_JMPSUB); 180 } 181 else 182 { 183 /* this is the child part of the fork */ 184 /* setting subpid to 1 causes subshell to exit when reached */ 185 sh_onstate(SH_FORKED); 186 sh_onstate(SH_NOLOG); 187 sh_offstate(SH_MONITOR); 188 subshell_data = 0; 189 shp->subshell = 0; 190 SH_SUBSHELLNOD->nvalue.s = 0; 191 sp->subpid=0; 192 } 193 } 194 195 int nv_subsaved(register Namval_t *np) 196 { 197 register struct subshell *sp; 198 register struct Link *lp; 199 for(sp = (struct subshell*)subshell_data; sp; sp=sp->prev) 200 { 201 for(lp=sp->svar; lp; lp = lp->next) 202 { 203 if(lp->node==np) 204 return(1); 205 } 206 } 207 return(0); 208 } 209 210 /* 211 * This routine will make a copy of the given node in the 212 * layer created by the most recent subshell_fork if the 213 * node hasn't already been copied 214 */ 215 Namval_t *sh_assignok(register Namval_t *np,int add) 216 { 217 register Namval_t *mp; 218 register struct Link *lp; 219 register struct subshell *sp = (struct subshell*)subshell_data; 220 struct Ufunction *rp; 221 Shell_t *shp = sp->shp; 222 Dt_t *dp; 223 Namval_t *mpnext; 224 Namarr_t *ap; 225 int save; 226 /* don't bother with this */ 227 if(!sp->shpwd || (nv_isnull(np) && !add)) 228 return(np); 229 /* don't bother to save if in newer scope */ 230 if(!(rp=shp->st.real_fun) || !(dp=rp->sdict)) 231 dp = sp->var; 232 if(np->nvenv && !nv_isattr(np,NV_MINIMAL|NV_EXPORT) && shp->last_root) 233 dp = shp->last_root; 234 if((mp=nv_search((char*)np,dp,HASH_BUCKET))!=np) 235 { 236 if(mp || !np->nvfun || np->nvfun->subshell>=sh.subshell) 237 return(np); 238 } 239 if((ap=nv_arrayptr(np)) && (mp=nv_opensub(np))) 240 { 241 shp->last_root = ap->table; 242 sh_assignok(mp,add); 243 if(!add || array_assoc(ap)) 244 return(np); 245 } 246 for(lp=subshell_data->svar; lp; lp = lp->next) 247 { 248 if(lp->node==np) 249 return(np); 250 } 251 /* first two pointers use linkage from np */ 252 lp = (struct Link*)malloc(sizeof(*np)+2*sizeof(void*)); 253 memset(lp,0, sizeof(*mp)+2*sizeof(void*)); 254 lp->node = np; 255 if(!add && nv_isvtree(np)) 256 { 257 Namval_t fake; 258 Dt_t *walk, *root=shp->var_tree; 259 char *name = nv_name(np); 260 int len = strlen(name); 261 fake.nvname = name; 262 mpnext = dtnext(root,&fake); 263 dp = root->walk?root->walk:root; 264 while(mp=mpnext) 265 { 266 walk = root->walk?root->walk:root; 267 mpnext = dtnext(root,mp); 268 if(memcmp(name,mp->nvname,len) || mp->nvname[len]!='.') 269 break; 270 nv_delete(mp,walk,NV_NOFREE); 271 *((Namval_t**)mp) = lp->child; 272 lp->child = mp; 273 274 } 275 } 276 lp->dict = dp; 277 mp = (Namval_t*)&lp->dict; 278 lp->next = subshell_data->svar; 279 subshell_data->svar = lp; 280 save = shp->subshell; 281 shp->subshell = 0; 282 mp->nvname = np->nvname; 283 nv_clone(np,mp,(add?(nv_isnull(np)?0:NV_NOFREE)|NV_ARRAY:NV_MOVE)); 284 shp->subshell = save; 285 return(np); 286 } 287 288 /* 289 * restore the variables 290 */ 291 static void nv_restore(struct subshell *sp) 292 { 293 register struct Link *lp, *lq; 294 register Namval_t *mp, *np; 295 const char *save = sp->shpwd; 296 Namval_t *mpnext; 297 sp->shpwd = 0; /* make sure sh_assignok doesn't save with nv_unset() */ 298 for(lp=sp->svar; lp; lp=lq) 299 { 300 np = (Namval_t*)&lp->dict; 301 lq = lp->next; 302 mp = lp->node; 303 if(!mp->nvname) 304 continue; 305 if(nv_isarray(mp)) 306 nv_putsub(mp,NIL(char*),ARRAY_SCAN); 307 _nv_unset(mp,NV_RDONLY); 308 if(nv_isarray(np)) 309 { 310 nv_clone(np,mp,NV_MOVE); 311 goto skip; 312 } 313 nv_setsize(mp,nv_size(np)); 314 if(!nv_isattr(np,NV_MINIMAL) || nv_isattr(np,NV_EXPORT)) 315 mp->nvenv = np->nvenv; 316 mp->nvfun = np->nvfun; 317 mp->nvflag = np->nvflag; 318 if(nv_cover(mp)) 319 nv_putval(mp, np->nvalue.cp,0); 320 else 321 mp->nvalue.cp = np->nvalue.cp; 322 np->nvfun = 0; 323 if(nv_isattr(mp,NV_EXPORT)) 324 { 325 char *name = nv_name(mp); 326 sh_envput(sh.env,mp); 327 if(*name=='_' && strcmp(name,"_AST_FEATURES")==0) 328 astconf(NiL, NiL, NiL); 329 } 330 else if(nv_isattr(np,NV_EXPORT)) 331 env_delete(sh.env,nv_name(mp)); 332 skip: 333 for(mp=lp->child; mp; mp=mpnext) 334 { 335 mpnext = *((Namval_t**)mp); 336 dtinsert(lp->dict,mp); 337 } 338 free((void*)lp); 339 sp->svar = lq; 340 } 341 sp->shpwd=save; 342 } 343 344 /* 345 * return pointer to alias tree 346 * create new one if in a subshell and one doesn't exist and create is non-zero 347 */ 348 Dt_t *sh_subaliastree(int create) 349 { 350 register struct subshell *sp = subshell_data; 351 if(!sp || sh.curenv==0) 352 return(sh.alias_tree); 353 if(!sp->salias && create) 354 { 355 sp->salias = dtopen(&_Nvdisc,Dtoset); 356 dtview(sp->salias,sh.alias_tree); 357 sh.alias_tree = sp->salias; 358 } 359 return(sp->salias); 360 } 361 362 /* 363 * return pointer to function tree 364 * create new one if in a subshell and one doesn't exist and create is non-zero 365 */ 366 Dt_t *sh_subfuntree(int create) 367 { 368 register struct subshell *sp = subshell_data; 369 if(!sp || sh.curenv==0) 370 return(sh.fun_tree); 371 if(!sp->sfun && create) 372 { 373 sp->sfun = dtopen(&_Nvdisc,Dtoset); 374 dtview(sp->sfun,sh.fun_tree); 375 sh.fun_tree = sp->sfun; 376 } 377 return(sh.fun_tree); 378 } 379 380 static void table_unset(register Dt_t *root,int fun) 381 { 382 register Namval_t *np,*nq; 383 int flag; 384 for(np=(Namval_t*)dtfirst(root);np;np=nq) 385 { 386 nq = (Namval_t*)dtnext(root,np); 387 flag=0; 388 if(fun && np->nvalue.rp->fname && *np->nvalue.rp->fname=='/') 389 { 390 np->nvalue.rp->fdict = 0; 391 flag = NV_NOFREE; 392 } 393 else 394 _nv_unset(np,NV_RDONLY); 395 nv_delete(np,root,flag|NV_FUNCTION); 396 } 397 } 398 399 int sh_subsavefd(register int fd) 400 { 401 register struct subshell *sp = subshell_data; 402 register int old=0; 403 if(sp) 404 { 405 old = !(sp->fdsaved&(1<<(fd-1))); 406 sp->fdsaved |= (1<<(fd-1)); 407 } 408 return(old); 409 } 410 411 void sh_subjobcheck(pid_t pid) 412 { 413 register struct subshell *sp = subshell_data; 414 while(sp) 415 { 416 if(sp->cpid==pid) 417 { 418 sh_close(sp->coutpipe); 419 sh_close(sp->cpipe); 420 sp->coutpipe = sp->cpipe = -1; 421 return; 422 } 423 sp = sp->prev; 424 } 425 } 426 427 /* 428 * Run command tree <t> in a virtual sub-shell 429 * If comsub is not null, then output will be placed in temp file (or buffer) 430 * If comsub is not null, the return value will be a stream consisting of 431 * output of command <t>. Otherwise, NULL will be returned. 432 */ 433 434 Sfio_t *sh_subshell(Shnode_t *t, int flags, int comsub) 435 { 436 Shell_t *shp = &sh; 437 struct subshell sub_data; 438 register struct subshell *sp = &sub_data; 439 int jmpval,nsig=0; 440 int savecurenv = shp->curenv; 441 int savejobpgid = job.curpgid; 442 int16_t subshell; 443 char *savsig; 444 Sfio_t *iop=0; 445 struct checkpt buff; 446 struct sh_scoped savst; 447 struct dolnod *argsav=0; 448 memset((char*)sp, 0, sizeof(*sp)); 449 sfsync(shp->outpool); 450 argsav = sh_arguse(shp); 451 if(shp->curenv==0) 452 { 453 subshell_data=0; 454 subenv = 0; 455 } 456 shp->curenv = ++subenv; 457 job.curpgid = 0; 458 savst = shp->st; 459 sh_pushcontext(&buff,SH_JMPSUB); 460 subshell = shp->subshell+1; 461 SH_SUBSHELLNOD->nvalue.s = subshell; 462 shp->subshell = subshell; 463 sp->prev = subshell_data; 464 sp->shp = shp; 465 sp->sig = 0; 466 subshell_data = sp; 467 sp->errcontext = &buff.err; 468 sp->var = shp->var_tree; 469 sp->options = shp->options; 470 sp->jobs = job_subsave(); 471 /* make sure initialization has occurred */ 472 if(!shp->pathlist) 473 path_get("."); 474 sp->pathlist = path_dup((Pathcomp_t*)shp->pathlist); 475 if(!shp->pwd) 476 path_pwd(0); 477 sp->bckpid = shp->bckpid; 478 if(comsub) 479 sh_stats(STAT_COMSUB); 480 if(!comsub || (comsub==1 && !sh_isoption(SH_SUBSHARE))) 481 { 482 sp->shpwd = shp->pwd; 483 sp->pwd = (shp->pwd?strdup(shp->pwd):0); 484 sp->mask = shp->mask; 485 sh_stats(STAT_SUBSHELL); 486 /* save trap table */ 487 shp->st.otrapcom = 0; 488 if((nsig=shp->st.trapmax*sizeof(char*))>0 || shp->st.trapcom[0]) 489 { 490 nsig += sizeof(char*); 491 memcpy(savsig=malloc(nsig),(char*)&shp->st.trapcom[0],nsig); 492 /* this nonsense needed for $(trap) */ 493 shp->st.otrapcom = (char**)savsig; 494 } 495 sp->cpid = shp->cpid; 496 sp->coutpipe = shp->coutpipe; 497 sp->cpipe = shp->cpipe[1]; 498 shp->coutpipe = shp->cpipe[1] = -1; 499 shp->cpid = 0; 500 sh_sigreset(0); 501 } 502 jmpval = sigsetjmp(buff.buff,0); 503 if(jmpval==0) 504 { 505 if(comsub) 506 { 507 /* disable job control */ 508 shp->spid = 0; 509 sp->jobcontrol = job.jobcontrol; 510 sp->monitor = (sh_isstate(SH_MONITOR)!=0); 511 job.jobcontrol=0; 512 sh_offstate(SH_MONITOR); 513 sp->pipe = sp; 514 /* save sfstdout and status */ 515 sp->saveout = sfswap(sfstdout,NIL(Sfio_t*)); 516 sp->fdstatus = shp->fdstatus[1]; 517 sp->tmpfd = -1; 518 sp->pipefd = -1; 519 /* use sftmp() file for standard output */ 520 if(!(iop = sftmp(PIPE_BUF))) 521 { 522 sfswap(sp->saveout,sfstdout); 523 errormsg(SH_DICT,ERROR_system(1),e_tmpcreate); 524 } 525 sfswap(iop,sfstdout); 526 sfset(sfstdout,SF_READ,0); 527 shp->fdstatus[1] = IOWRITE; 528 if(!(sp->nofork = sh_state(SH_NOFORK))) 529 sh_onstate(SH_NOFORK); 530 flags |= sh_state(SH_NOFORK); 531 } 532 else if(sp->prev) 533 { 534 sp->pipe = sp->prev->pipe; 535 flags &= ~sh_state(SH_NOFORK); 536 } 537 sh_exec(t,flags); 538 } 539 if(comsub!=2 && jmpval!=SH_JMPSUB && shp->st.trapcom[0] && shp->subshell) 540 { 541 /* trap on EXIT not handled by child */ 542 char *trap=shp->st.trapcom[0]; 543 shp->st.trapcom[0] = 0; /* prevent recursion */ 544 shp->oldexit = shp->exitval; 545 sh_trap(trap,0); 546 free(trap); 547 } 548 sh_popcontext(&buff); 549 if(shp->subshell==0) /* must be child process */ 550 { 551 subshell_data = sp->prev; 552 if(jmpval==SH_JMPSCRIPT) 553 siglongjmp(*shp->jmplist,jmpval); 554 sh_done(shp,0); 555 } 556 if(comsub) 557 { 558 /* re-enable job control */ 559 if(!sp->nofork) 560 sh_offstate(SH_NOFORK); 561 job.jobcontrol = sp->jobcontrol; 562 if(sp->monitor) 563 sh_onstate(SH_MONITOR); 564 if(sp->pipefd>=0) 565 { 566 /* sftmp() file has been returned into pipe */ 567 iop = sh_iostream(shp,sp->pipefd); 568 sfclose(sfstdout); 569 } 570 else 571 { 572 /* move tmp file to iop and restore sfstdout */ 573 iop = sfswap(sfstdout,NIL(Sfio_t*)); 574 if(!iop) 575 { 576 /* maybe locked try again */ 577 sfclrlock(sfstdout); 578 iop = sfswap(sfstdout,NIL(Sfio_t*)); 579 } 580 if(iop && sffileno(iop)==1) 581 { 582 int fd=sfsetfd(iop,3); 583 if(fd<0) 584 errormsg(SH_DICT,ERROR_system(1),e_toomany); 585 shp->sftable[fd] = iop; 586 fcntl(fd,F_SETFD,FD_CLOEXEC); 587 shp->fdstatus[fd] = (shp->fdstatus[1]|IOCLEX); 588 shp->fdstatus[1] = IOCLOSE; 589 } 590 sfset(iop,SF_READ,1); 591 } 592 sfswap(sp->saveout,sfstdout); 593 /* check if standard output was preserved */ 594 if(sp->tmpfd>=0) 595 { 596 close(1); 597 fcntl(sp->tmpfd,F_DUPFD,1); 598 sh_close(sp->tmpfd); 599 } 600 shp->fdstatus[1] = sp->fdstatus; 601 } 602 if(sp->subpid) 603 { 604 if(shp->exitval > SH_EXITSIG) 605 sp->sig = (shp->exitval&SH_EXITMASK); 606 shp->exitval = 0; 607 if(comsub) 608 shp->spid = sp->subpid; 609 else 610 job_wait(sp->subpid); 611 } 612 if(comsub && iop && sp->pipefd<0) 613 sfseek(iop,(off_t)0,SEEK_SET); 614 path_delete((Pathcomp_t*)shp->pathlist); 615 shp->pathlist = (void*)sp->pathlist; 616 job_subrestore(sp->jobs); 617 shp->jobenv = savecurenv; 618 job.curpgid = savejobpgid; 619 shp->bckpid = sp->bckpid; 620 if(sp->shpwd) /* restore environment if saved */ 621 { 622 int n; 623 shp->options = sp->options; 624 nv_restore(sp); 625 if(sp->salias) 626 { 627 shp->alias_tree = dtview(sp->salias,0); 628 table_unset(sp->salias,0); 629 dtclose(sp->salias); 630 } 631 if(sp->sfun) 632 { 633 shp->fun_tree = dtview(sp->sfun,0); 634 table_unset(sp->sfun,1); 635 dtclose(sp->sfun); 636 } 637 n = shp->st.trapmax-savst.trapmax; 638 sh_sigreset(1); 639 if(n>0) 640 memset(&shp->st.trapcom[savst.trapmax],0,n*sizeof(char*)); 641 shp->st = savst; 642 shp->curenv = savecurenv; 643 if(nsig) 644 { 645 memcpy((char*)&shp->st.trapcom[0],savsig,nsig); 646 free((void*)savsig); 647 } 648 shp->options = sp->options; 649 if(!shp->pwd || strcmp(sp->pwd,shp->pwd)) 650 { 651 /* restore PWDNOD */ 652 Namval_t *pwdnod = sh_scoped(shp,PWDNOD); 653 if(shp->pwd) 654 { 655 chdir(shp->pwd=sp->pwd); 656 path_newdir(shp->pathlist); 657 } 658 if(nv_isattr(pwdnod,NV_NOFREE)) 659 pwdnod->nvalue.cp = (const char*)sp->pwd; 660 } 661 else if(sp->shpwd != shp->pwd) 662 { 663 shp->pwd = sp->pwd; 664 if(PWDNOD->nvalue.cp==sp->shpwd) 665 PWDNOD->nvalue.cp = sp->pwd; 666 } 667 else 668 free((void*)sp->pwd); 669 if(sp->mask!=shp->mask) 670 umask(shp->mask=sp->mask); 671 if(shp->coutpipe>=0) 672 { 673 sh_close(shp->coutpipe); 674 sh_close(shp->cpipe[1]); 675 } 676 shp->cpid = sp->cpid; 677 shp->cpipe[1] = sp->cpipe; 678 shp->coutpipe = sp->coutpipe; 679 } 680 if(shp->subshell) 681 SH_SUBSHELLNOD->nvalue.s = --shp->subshell; 682 if(sp->sig) 683 { 684 if(sp->prev) 685 sp->prev->sig = sp->sig; 686 else 687 { 688 sh_fault(sp->sig); 689 sh_chktrap(); 690 } 691 } 692 subshell = shp->subshell; 693 subshell_data = sp->prev; 694 sh_argfree(shp,argsav,0); 695 shp->trapnote = 0; 696 if(shp->topfd != buff.topfd) 697 sh_iorestore(shp,buff.topfd|IOSUBSHELL,jmpval); 698 if(shp->exitval > SH_EXITSIG) 699 { 700 int sig = shp->exitval&SH_EXITMASK; 701 if(sig==SIGINT || sig== SIGQUIT) 702 sh_fault(sig); 703 } 704 return(iop); 705 } 706