/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1982-2012 AT&T Intellectual Property * * Copyright (c) 2020-2022 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.eclipse.org/org/documents/epl-v10.html * * (with md5 checksum b35adb5213ca9657e911e9befb180842) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * David Korn * * * ***********************************************************************/ /* * UNIX shell * * S. R. Bourne * Rewritten By David Korn * AT&T Labs * */ #include #include #include #include #include #include "defs.h" #include "variables.h" #include "path.h" #include "io.h" #include "jobs.h" #include "shlex.h" #include "shnodes.h" #include "history.h" #include "timeout.h" #include "FEATURE/time" #include "FEATURE/pstat" #include "FEATURE/setproctitle" #include "FEATURE/execargs" #include "FEATURE/externs" #ifdef _hdr_nc # include #endif /* _hdr_nc */ /* These routines are referenced by this module */ static void exfile(Sfio_t*,int); static void chkmail(char*); #if !defined(_NEXT_SOURCE) && !defined(__sun) static void fixargs(char**,int); # undef fixargs_disabled #else # define fixargs(a,b) # define fixargs_disabled 1 #endif #ifndef environ extern char **environ; #endif static struct stat lastmail; static time_t mailtime; static char beenhere = 0; #ifdef _lib_sigvec void clearsigmask(register int sig) { struct sigvec vec; if(sigvec(sig,NIL(struct sigvec*),&vec)>=0 && vec.sv_mask) { vec.sv_mask = 0; sigvec(sig,&vec,NIL(struct sigvec*)); } } #endif /* _lib_sigvec */ /* * search for file and exfile() it if it exists * 1 returned if file found, 0 otherwise */ static int sh_source(Sfio_t *iop, const char *file) { char* oid; char* nid; int fd; if (!file || !*file || (fd = path_open(file, NIL(Pathcomp_t*))) < 0) { REGRESS(source, "sh_source", ("%s:ENOENT", file)); return 0; } oid = error_info.id; nid = error_info.id = sh_strdup(file); sh.st.filename = path_fullname(stakptr(PATH_OFFSET)); REGRESS(source, "sh_source", ("%s", file)); exfile(iop, fd); error_info.id = oid; free(nid); return 1; } #ifdef S_ISSOCK #define REMOTE(m) (S_ISSOCK(m)||!(m)) #else #define REMOTE(m) !(m) #endif int sh_main(int ac, char *av[], Shinit_f userinit) { register char *name; register int fdin; register Sfio_t *iop; struct stat statb; int i, rshflag; /* set for restricted shell */ char *command; #ifdef _lib_sigvec /* This is to clear mask that may be left on by rlogin */ clearsigmask(SIGALRM); clearsigmask(SIGHUP); clearsigmask(SIGCHLD); #endif /* _lib_sigvec */ #ifdef _hdr_nc _NutConf(_NC_SET_SUFFIXED_SEARCHING, 1); #endif /* _hdr_nc */ fixargs(av,0); sh_init(ac,av,userinit); time(&mailtime); if(rshflag=sh_isoption(SH_RESTRICTED)) sh_offoption(SH_RESTRICTED); if(sigsetjmp(*((sigjmp_buf*)sh.jmpbuffer),0)) { /* begin script execution here */ sh_reinit((char**)0); } sh.fn_depth = sh.dot_depth = 0; command = error_info.id; path_pwd(); iop = (Sfio_t*)0; if(sh_isoption(SH_POSIX)) sh_onoption(SH_LETOCTAL); #if SHOPT_BRACEPAT else sh_onoption(SH_BRACEEXPAND); #endif if(!beenhere) { beenhere++; sh_onstate(SH_PROFILE); sh.sigflag[SIGTSTP] |= SH_SIGIGNORE; if(sh.ppid==1) sh.login_sh++; if(sh.login_sh >= 2) sh_onoption(SH_LOGIN_SHELL); /* decide whether shell is interactive */ if(!sh_isoption(SH_INTERACTIVE) && !sh_isoption(SH_TFLAG) && !sh_isoption(SH_CFLAG) && sh_isoption(SH_SFLAG) && tty_check(0) && tty_check(ERRIO)) sh_onoption(SH_INTERACTIVE); if(sh_isoption(SH_INTERACTIVE)) { sh_onoption(SH_BGNICE); sh_onoption(SH_RC); if(!sh_isoption(SH_POSIX)) { /* preset aliases for interactive non-POSIX ksh */ dtclose(sh.alias_tree); sh.alias_tree = sh_inittree(shtab_aliases); } } #if SHOPT_REMOTE /* * Building ksh with SHOPT_REMOTE=1 causes ksh to set --rc if stdin is * a socket (presumably part of a remote shell invocation). */ if(!sh_isoption(SH_RC) && !fstat(0, &statb) && REMOTE(statb.st_mode)) sh_onoption(SH_RC); #endif for(i=0; i 0) av[sh.st.repl_index] = sh.st.repl_arg; if(((type = sh_type(cp = av[0])) & SH_TYPE_SH) && (name = nv_getval(L_ARGNOD)) && (!((type = sh_type(cp = name)) & SH_TYPE_SH))) { av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp); /* exec to change $0 for ps */ execv(pathshell(),av); /* exec fails */ sh.st.dolv[0] = av[0]; fixargs(sh.st.dolv,1); } #endif name = av[0]; sh_offoption(SH_VERBOSE); sh_offoption(SH_XTRACE); } else { int isdir = 0; if((fdin=sh_open(name,O_RDONLY,0))>=0 &&(fstat(fdin,&statb)<0 || S_ISDIR(statb.st_mode))) { close(fdin); isdir = 1; fdin = -1; } else sh.st.filename = path_fullname(name); sp = 0; if(fdin < 0 && !strchr(name,'/')) { if(path_absolute(name,NIL(Pathcomp_t*),0)) sp = stakptr(PATH_OFFSET); if(sp) { if((fdin=sh_open(sp,O_RDONLY,0))>=0) sh.st.filename = path_fullname(sp); } } if(fdin<0) { if(isdir) errno = EISDIR; error_info.id = av[0]; if(sp || errno!=ENOENT) { errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_open,name); UNREACHABLE(); } /* try sh -c 'name "$@"' */ sh_onoption(SH_CFLAG); sh.comdiv = (char*)sh_malloc(strlen(name)+7); name = strcopy(sh.comdiv,name); if(sh.st.dolc) strcopy(name," \"$@\""); goto shell_c; } if(fdin==0) fdin = sh_iomovefd(fdin); } sh.readscript = sh.shname; } error_info.id = name; #if SHOPT_ACCT sh_accinit(); if(fdin != 0) sh_accbegin(error_info.id); #endif /* SHOPT_ACCT */ } /* If the shell is initialised with std{in,out,err} closed, make the shell's FD state reflect that. */ for(i=0; i<=2; i++) if(fcntl(i,F_GETFD,NiL)==-1 && errno==EBADF) /* closed at OS level? */ sh_close(i); /* update shell FD state */ } else { /* beenhere > 0: We're in a forked child, about to execute a script without a hashbang path. */ fdin = sh.infd; fixargs(sh.st.dolv,1); } if(sh_isoption(SH_INTERACTIVE)) { sh_onstate(SH_INTERACTIVE); #if SHOPT_ESH /* do not leave users without a line editor */ if(!sh_isoption(SH_GMACS) #if SHOPT_VSH && !sh_isoption(SH_VI) #endif /* SHOPT_VSH */ ) sh_onoption(SH_EMACS); #endif /* SHOPT_ESH */ } /* (Re)set PS4 and IFS, but don't export these now even if allexport is on. */ i = (sh_isoption(SH_ALLEXPORT) != 0); sh_offoption(SH_ALLEXPORT); if(nv_isnull(PS4NOD)) nv_putval(PS4NOD,e_traceprompt,NV_RDONLY); nv_putval(IFSNOD,(char*)e_sptbnl,NV_RDONLY); if(i) sh_onoption(SH_ALLEXPORT); /* Start main execution loop. */ exfile(iop,fdin); sh_done(0); } /* * iop is not null when the input is a string * fdin is the input file descriptor */ static void exfile(register Sfio_t *iop,register int fno) { time_t curtime; Shnode_t *t; int maxtry=IOMAXTRY, tdone=0, execflags; int states,jmpval; struct checkpt buff; sh_pushcontext(&buff,SH_JMPERREXIT); /* open input stream */ nv_putval(SH_PATHNAMENOD, sh.st.filename, NV_NOFREE); if(!iop) { if(fno > 0) { int r; if(fno < 10 && ((r=sh_fcntl(fno,F_DUPFD,10))>=10)) { sh.fdstatus[r] = sh.fdstatus[fno]; sh_close(fno); fno = r; } fcntl(fno,F_SETFD,FD_CLOEXEC); sh.fdstatus[fno] |= IOCLEX; iop = sh_iostream(fno); } else iop = sfstdin; } else fno = -1; sh.infd = fno; if(sh_isstate(SH_INTERACTIVE)) { if(nv_isnull(PS1NOD)) nv_putval(PS1NOD,(sh.euserid?e_stdprompt:e_supprompt),NV_RDONLY); sh_sigdone(); if(sh_histinit()) sh_onoption(SH_HISTORY); } else { if(!sh_isstate(SH_PROFILE)) { buff.mode = SH_JMPEXIT; sh_onoption(SH_TRACKALL); } if(sh_isoption(SH_MONITOR)) sh_onstate(SH_MONITOR); sh_offstate(SH_HISTORY); sh_offoption(SH_HISTORY); } states = sh_getstate(); jmpval = sigsetjmp(buff.buff,0); if(jmpval) { Sfio_t *top; sh_iorestore(0,jmpval); hist_flush(sh.hist_ptr); sfsync(sh.outpool); sh.st.execbrk = sh.st.breakcnt = 0; /* check for return from profile or env file */ if(sh_isstate(SH_PROFILE) && (jmpval==SH_JMPFUN || jmpval==SH_JMPEXIT)) { sh_setstate(states); goto done; } if(!sh_isoption(SH_INTERACTIVE) || sh_isstate(SH_FORKED) || (jmpval > SH_JMPERREXIT && job_close() >=0)) { sh_offstate(SH_INTERACTIVE); sh_offstate(SH_MONITOR); goto done; } exitset(); /* skip over remaining input */ if(top = fcfile()) { while(fcget()>0); fcclose(); while(top=sfstack(iop,SF_POPSTACK)) sfclose(top); } /* make sure that we own the terminal */ #ifdef SIGTSTP tcsetpgrp(job.fd,sh.pid); #endif /* SIGTSTP */ } /* error return here */ sfclrerr(iop); sh_setstate(states); sh.st.optindex = 1; opt_info.offset = 0; sh.st.loopcnt = 0; sh.trapnote = 0; sh.intrap = 0; error_info.line = 1; sh.inlineno = 1; sh.binscript = 0; sh.exittrap = 0; sh.errtrap = 0; sh.end_fn = 0; if(sfeof(iop)) goto eof_or_error; /* command loop */ while(1) { sh.nextprompt = 1; sh_freeup(); stakset(NIL(char*),0); sh_offstate(SH_STOPOK); sh_offstate(SH_ERREXIT); sh_offstate(SH_VERBOSE); sh_offstate(SH_TIMING); sh_offstate(SH_GRACE); sh_offstate(SH_TTYWAIT); if(sh_isoption(SH_VERBOSE)) sh_onstate(SH_VERBOSE); sh_onstate(SH_ERREXIT); /* -eim flags don't apply to profiles */ if(sh_isstate(SH_PROFILE)) { sh_offstate(SH_INTERACTIVE); sh_offstate(SH_ERREXIT); sh_offstate(SH_MONITOR); } if(sh_isstate(SH_INTERACTIVE) && !tdone) { register char *mail; #ifdef JOBS sh_offstate(SH_MONITOR); if(sh_isoption(SH_MONITOR)) sh_onstate(SH_MONITOR); if(job.pwlist) { job_walk(sfstderr,job_list,JOB_NFLAG,(char**)0); job_wait((pid_t)0); } #endif /* JOBS */ if((mail=nv_getval(MAILPNOD)) || (mail=nv_getval(MAILNOD))) { time(&curtime); if ((curtime - mailtime) >= sh_mailchk) { chkmail(mail); mailtime = curtime; } } if(sh.hist_ptr) hist_eof(sh.hist_ptr); /* sets timeout for command entry */ sh.timeout = sh.st.tmout; #if SHOPT_TIMEOUT if(sh.timeout <= 0 || sh.timeout > SHOPT_TIMEOUT) sh.timeout = SHOPT_TIMEOUT; #endif /* SHOPT_TIMEOUT */ sh.inlineno = 1; error_info.line = 1; sh.trapnote = 0; if(buff.mode == SH_JMPEXIT) { buff.mode = SH_JMPERREXIT; #ifdef DEBUG errormsg(SH_DICT,ERROR_warn(0),"%d: mode changed to JMP_EXIT",sh.current_pid); #endif } } errno = 0; if(tdone || !sfreserve(iop,0,0)) { int sferr; eof_or_error: sferr = sferror(iop); if(sh_isstate(SH_INTERACTIVE)) { if(!sferr) { if(--maxtry>0 && sh_isoption(SH_IGNOREEOF) && !sferror(sfstderr) && (sh.fdstatus[fno]&IOTTY)) { sfclrerr(iop); errormsg(SH_DICT,0,e_logout); continue; } else if(job_close()<0) continue; } else if(sferr==SH_EXITSIG) { /* Ctrl+C with SIGINT ignored */ sfputc(sfstderr,'\n'); continue; } } if(errno==0 && sferr && --maxtry>0) { sfclrlock(iop); sfclrerr(iop); continue; } goto done; } sh.exitval = sh.savexit; maxtry = IOMAXTRY; if(sh_isstate(SH_INTERACTIVE) && sh.hist_ptr) { job_wait((pid_t)0); hist_eof(sh.hist_ptr); sfsync(sfstderr); } if(sh_isoption(SH_HISTORY)) sh_onstate(SH_HISTORY); job.waitall = job.curpgid = 0; error_info.flags |= ERROR_INTERACTIVE; t = (Shnode_t*)sh_parse(iop,0); if(!sh_isstate(SH_INTERACTIVE) && !sh_isoption(SH_CFLAG)) error_info.flags &= ~ERROR_INTERACTIVE; sh.readscript = 0; if(sh_isstate(SH_INTERACTIVE) && sh.hist_ptr) hist_flush(sh.hist_ptr); sh_offstate(SH_HISTORY); if(t) { execflags = sh_state(SH_ERREXIT)|sh_state(SH_INTERACTIVE); /* The last command may not have to fork */ if(!sh_isstate(SH_PROFILE) && !sh_isstate(SH_INTERACTIVE) && (fno<0 || !(sh.fdstatus[fno]&(IOTTY|IONOSEEK))) && !sfreserve(iop,0,0)) { execflags |= sh_state(SH_NOFORK); } sh.st.execbrk = 0; sh_exec(t,execflags); if(sh.forked) { sh_offstate(SH_INTERACTIVE); goto done; } /* This is for sh -t */ if(sh_isoption(SH_TFLAG) && !sh_isstate(SH_PROFILE)) tdone++; } } done: sh_popcontext(&buff); if(sh_isstate(SH_INTERACTIVE)) { if(isatty(0) && !sh_isoption(SH_CFLAG)) sfputc(sfstderr,'\n'); job_close(); } if(jmpval == SH_JMPSCRIPT) siglongjmp(*sh.jmplist,jmpval); else if(jmpval == SH_JMPEXIT) sh_done(0); if(fno>0) sh_close(fno); if(sh.st.filename) free((void*)sh.st.filename); sh.st.filename = 0; } /* prints out messages if files in list have been modified since last call */ static void chkmail(char *files) { register char *cp,*sp,*qp; register char save; struct argnod *arglist=0; int offset = staktell(); char *savstak=stakptr(0); struct stat statb; if(*(cp=files) == 0) return; sp = cp; do { /* skip to : or end of string saving first '?' */ for(qp=0;*sp && *sp != ':';sp++) if((*sp == '?' || *sp=='%') && qp == 0) qp = sp; save = *sp; *sp = 0; /* change '?' to end-of-string */ if(qp) *qp = 0; do { /* see if time has been modified since last checked * and the access time <= the modification time */ if(stat(cp,&statb) >= 0 && statb.st_mtime >= mailtime && statb.st_atime <= statb.st_mtime) { /* check for directory */ if(!arglist && S_ISDIR(statb.st_mode)) { /* generate list of directory entries */ path_complete(cp,"/*",&arglist); } else { /* * If the file has shrunk, * or if the size is zero * then don't print anything */ if(statb.st_size && ( statb.st_ino != lastmail.st_ino || statb.st_dev != lastmail.st_dev || statb.st_size > lastmail.st_size)) { /* save and restore $_ */ char *save = sh.lastarg; sh.lastarg = cp; errormsg(SH_DICT,0,sh_mactry(qp?qp+1:(char*)e_mailmsg)); sh.lastarg = save; } lastmail = statb; break; } } if(arglist) { cp = arglist->argval; arglist = arglist->argchn.ap; } else cp = 0; } while(cp); if(qp) *qp = '?'; *sp++ = save; cp = sp; } while(save); stakset(savstak,offset); } #undef EXECARGS #undef PSTAT #if defined(_hdr_execargs) && defined(pdp11) # include # define EXECARGS 1 #endif #if defined(_lib_pstat) && defined(_sys_pstat) # include # define PSTAT 1 #endif #if !defined(fixargs_disabled) /* * fix up command line for ps command * * This function is invoked when ksh needs to run a script without a * #!/hashbang/path. Instead of letting the kernel invoke a shell, ksh * exfile()s the script itself from sh_main(). In the forked child, it calls * fixargs() to set the argument list in the environment to the args of the * new script, so that 'ps' and /proc/PID/cmdline show the expected output. * * mode is 0 for initialization */ static void fixargs(char **argv, int mode) { # if EXECARGS if(mode==0) return; *execargs=(char *)argv; # elif PSTAT char *cp; int offset=0,size; static int command_len; char *buff; union pstun un; if(mode==0) { struct pst_static st; un.pst_static = &st; if(pstat(PSTAT_STATIC, un, sizeof(struct pst_static), 1, 0)<0) return; command_len = st.command_length; return; } stakseek(command_len+2); buff = stakseek(0); if(command_len==0) return; while((cp = *argv++) && offset < command_len) { if(offset + (size=strlen(cp)) >= command_len) size = command_len - offset; memcpy(buff+offset,cp,size); offset += size; buff[offset++] = ' '; } offset--; memset(&buff[offset], 0, command_len - offset + 1); un.pst_command = stakptr(0); pstat(PSTAT_SETCMD,un,0,0,0); # elif _lib_setproctitle # define CMDMAXLEN 255 char *cp; int offset=0,size; char buff[CMDMAXLEN + 1]; if(mode==0) return; while((cp = *argv++) && offset < CMDMAXLEN) { if(offset + (size=strlen(cp)) >= CMDMAXLEN) size = CMDMAXLEN - offset; memcpy(buff+offset,cp,size); offset += size; buff[offset++] = ' '; } buff[--offset] = '\0'; setproctitle("%s",buff); # undef CMDMAXLEN # else /* Generic version, works on at least Linux and macOS */ char *cp; int offset=0,size; static int buffsize; static char *buff; if(mode==0) { int i; buff = argv[0]; for(i=0; argv[i]; i++) buffsize += strlen(argv[i]) + 1; if(buffsize < 128 && buff + buffsize == *environ) { /* Move the environment to make space for a larger command line buffer */ for(i=0; environ[i]; i++) { buffsize += strlen(environ[i]) + 1;; environ[i] = sh_strdup(environ[i]); } } return; } while((cp = *argv++) && offset < buffsize) { if(offset + (size=strlen(cp)) >= buffsize) size = buffsize - offset; memcpy(buff+offset,cp,size); offset += size; buff[offset++] = ' '; } offset--; memset(&buff[offset], 0, buffsize - offset + 1); # endif } #endif /* !fixargs_disabled */