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