summaryrefslogtreecommitdiff
path: root/usr/src/lib/libshell/common/sh/io.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libshell/common/sh/io.c')
-rw-r--r--usr/src/lib/libshell/common/sh/io.c2215
1 files changed, 2215 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/sh/io.c b/usr/src/lib/libshell/common/sh/io.c
new file mode 100644
index 0000000000..a14a941942
--- /dev/null
+++ b/usr/src/lib/libshell/common/sh/io.c
@@ -0,0 +1,2215 @@
+/***********************************************************************
+* *
+* This software is part of the ast package *
+* Copyright (c) 1982-2007 AT&T Knowledge Ventures *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Knowledge Ventures *
+* *
+* A copy of the License is available at *
+* http://www.opensource.org/licenses/cpl1.0.txt *
+* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
+* *
+* Information and Software Systems Research *
+* AT&T Research *
+* Florham Park NJ *
+* *
+* David Korn <dgk@research.att.com> *
+* *
+***********************************************************************/
+#pragma prototyped
+
+/*
+ * Input/output file processing
+ *
+ * David Korn
+ * AT&T Labs
+ *
+ */
+
+#include "defs.h"
+#include <fcin.h>
+#include <ls.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <regex.h>
+#include "variables.h"
+#include "path.h"
+#include "io.h"
+#include "jobs.h"
+#include "shnodes.h"
+#include "history.h"
+#include "edit.h"
+#include "timeout.h"
+#include "FEATURE/externs"
+#include "FEATURE/dynamic"
+#include "FEATURE/poll"
+
+#ifdef FNDELAY
+# ifdef EAGAIN
+# if EAGAIN!=EWOULDBLOCK
+# undef EAGAIN
+# define EAGAIN EWOULDBLOCK
+# endif
+# else
+# define EAGAIN EWOULDBLOCK
+# endif /* EAGAIN */
+# ifndef O_NONBLOCK
+# define O_NONBLOCK FNDELAY
+# endif /* !O_NONBLOCK */
+#endif /* FNDELAY */
+
+#ifndef O_SERVICE
+# define O_SERVICE O_NOCTTY
+#endif
+
+#define RW_ALL (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH)
+
+static void *timeout;
+static int (*fdnotify)(int,int);
+
+#if defined(_lib_socket) && defined(_sys_socket) && defined(_hdr_netinet_in)
+# include <sys/socket.h>
+# include <netdb.h>
+# include <netinet/in.h>
+# if !defined(htons) && !_lib_htons
+# define htons(x) (x)
+# endif
+# if !defined(htonl) && !_lib_htonl
+# define htonl(x) (x)
+# endif
+# if _pipe_socketpair
+# if _socketpair_shutdown_mode
+# define pipe(v) ((socketpair(AF_UNIX,SOCK_STREAM,0,v)<0||shutdown((v)[0],1)<0||fchmod((v)[0],S_IRUSR)<0||shutdown((v)[1],0)<0||fchmod((v)[1],S_IWUSR)<0)?(-1):0)
+# else
+# define pipe(v) ((socketpair(AF_UNIX,SOCK_STREAM,0,v)<0||shutdown((v)[0],1)<0||shutdown((v)[1],0)<0)?(-1):0)
+# endif
+# endif
+
+#if !_lib_getaddrinfo
+
+#undef EAI_SYSTEM
+
+#define EAI_SYSTEM 1
+
+#undef addrinfo
+#undef getaddrinfo
+#undef freeaddrinfo
+
+#define addrinfo local_addrinfo
+#define getaddrinfo local_getaddrinfo
+#define freeaddrinfo local_freeaddrinfo
+
+struct addrinfo
+{
+ int ai_flags;
+ int ai_family;
+ int ai_socktype;
+ int ai_protocol;
+ socklen_t ai_addrlen;
+ struct sockaddr* ai_addr;
+ struct addrinfo* ai_next;
+};
+
+static int
+getaddrinfo(const char* node, const char* service, const struct addrinfo* hint, struct addrinfo **addr)
+{
+ unsigned long ip_addr = 0;
+ unsigned short ip_port = 0;
+ struct addrinfo* ap;
+ struct hostent* hp;
+ struct sockaddr_in* ip;
+ char* prot;
+ long n;
+
+ if (!(hp = gethostbyname(node)) || hp->h_addrtype!=AF_INET || hp->h_length>sizeof(struct in_addr))
+ {
+ errno = EADDRNOTAVAIL;
+ return EAI_SYSTEM;
+ }
+ ip_addr = (unsigned long)((struct in_addr*)hp->h_addr)->s_addr;
+ if ((n = strtol(service, &prot, 10)) > 0 && n <= USHRT_MAX && !*prot)
+ ip_port = htons((unsigned short)n);
+ else
+ {
+ struct servent* sp;
+ const char* protocol = 0;
+
+ if (hint)
+ switch (hint->ai_socktype)
+ {
+ case SOCK_STREAM:
+ switch (hint->ai_protocol)
+ {
+ case 0:
+ protocol = "tcp";
+ break;
+#ifdef IPPROTO_SCTP
+ case IPPROTO_SCTP:
+ protocol = "sctp";
+ break;
+#endif
+ }
+ break;
+ case SOCK_DGRAM:
+ protocol = "udp";
+ break;
+ }
+ if (!protocol)
+ {
+ errno = EPROTONOSUPPORT;
+ return 1;
+ }
+ if (sp = getservbyname(service, protocol))
+ ip_port = sp->s_port;
+ }
+ if (!ip_port)
+ {
+ errno = EADDRNOTAVAIL;
+ return EAI_SYSTEM;
+ }
+ if (!(ap = newof(0, struct addrinfo, 1, sizeof(struct sockaddr_in))))
+ return EAI_SYSTEM;
+ if (hint)
+ *ap = *hint;
+ ap->ai_family = hp->h_addrtype;
+ ap->ai_addrlen = sizeof(struct sockaddr_in);
+ ap->ai_addr = (struct sockaddr *)(ap+1);
+ ip = (struct sockaddr_in *)ap->ai_addr;
+ ip->sin_family = AF_INET;
+ ip->sin_port = ip_port;
+ ip->sin_addr.s_addr = ip_addr;
+ *addr = ap;
+ return 0;
+}
+
+static void
+freeaddrinfo(struct addrinfo* ap)
+{
+ if (ap)
+ free(ap);
+}
+
+#endif
+
+/*
+ * return <protocol>/<host>/<service> fd
+ */
+
+typedef int (*Inetintr_f)(struct addrinfo*, void*);
+
+static int
+inetopen(const char* path, int server, Inetintr_f onintr, void* handle)
+{
+ register char* s;
+ register char* t;
+ int fd;
+ int oerrno;
+ struct addrinfo hint;
+ struct addrinfo* addr;
+ struct addrinfo* p;
+
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_family = PF_UNSPEC;
+ switch (path[0])
+ {
+#ifdef IPPROTO_SCTP
+ case 's':
+ if (path[1]!='c' || path[2]!='t' || path[3]!='p' || path[4]!='/')
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ hint.ai_socktype = SOCK_STREAM;
+ hint.ai_protocol = IPPROTO_SCTP;
+ path += 5;
+ break;
+#endif
+ case 't':
+ if (path[1]!='c' || path[2]!='p' || path[3]!='/')
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ hint.ai_socktype = SOCK_STREAM;
+ path += 4;
+ break;
+ case 'u':
+ if (path[1]!='d' || path[2]!='p' || path[3]!='/')
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
+ hint.ai_socktype = SOCK_DGRAM;
+ path += 4;
+ break;
+ default:
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (!(s = strdup(path)))
+ return -1;
+ if (t = strchr(s, '/'))
+ {
+ *t++ = 0;
+ if (streq(s, "local"))
+ s = "localhost";
+ fd = getaddrinfo(s, t, &hint, &addr);
+ }
+ else
+ fd = -1;
+ free(s);
+ if (fd)
+ {
+ if (fd != EAI_SYSTEM)
+ errno = ENOTDIR;
+ return -1;
+ }
+ oerrno = errno;
+ errno = 0;
+ fd = -1;
+ for (p = addr; p; p = p->ai_next)
+ {
+ /*
+ * some api's don't take the hint
+ */
+
+ if (!p->ai_protocol)
+ p->ai_protocol = hint.ai_protocol;
+ if (!p->ai_socktype)
+ p->ai_socktype = hint.ai_socktype;
+ while ((fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) >= 0)
+ {
+ if (server && !bind(fd, p->ai_addr, p->ai_addrlen) && !listen(fd, 5) || !server && !connect(fd, p->ai_addr, p->ai_addrlen))
+ goto done;
+ close(fd);
+ fd = -1;
+ if (errno != EINTR || !onintr)
+ break;
+ if ((*onintr)(addr, handle))
+ return -1;
+ }
+ }
+ done:
+ freeaddrinfo(addr);
+ if (fd >= 0)
+ errno = oerrno;
+ return fd;
+}
+
+#else
+
+#undef O_SERVICE
+
+#endif
+
+struct fdsave
+{
+ int orig_fd; /* original file descriptor */
+ int save_fd; /* saved file descriptor */
+ int subshell; /* saved for subshell */
+};
+
+static int subexcept(Sfio_t*, int, void*, Sfdisc_t*);
+static int eval_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
+static int slowexcept(Sfio_t*, int, void*, Sfdisc_t*);
+static int pipeexcept(Sfio_t*, int, void*, Sfdisc_t*);
+static ssize_t piperead(Sfio_t*, void*, size_t, Sfdisc_t*);
+static ssize_t slowread(Sfio_t*, void*, size_t, Sfdisc_t*);
+static ssize_t subread(Sfio_t*, void*, size_t, Sfdisc_t*);
+static ssize_t tee_write(Sfio_t*,const void*,size_t,Sfdisc_t*);
+static int io_prompt(Sfio_t*,int);
+static int io_heredoc(register struct ionod*, const char*, int);
+static void sftrack(Sfio_t*,int,int);
+static const Sfdisc_t eval_disc = { NULL, NULL, NULL, eval_exceptf, NULL};
+static Sfdisc_t tee_disc = {NULL,tee_write,NULL,NULL,NULL};
+static Sfio_t *subopen(Sfio_t*, off_t, long);
+static const Sfdisc_t sub_disc = { subread, 0, 0, subexcept, 0 };
+
+struct subfile
+{
+ Sfdisc_t disc;
+ Sfio_t *oldsp;
+ off_t offset;
+ long size;
+ long left;
+};
+
+struct Eof
+{
+ Namfun_t hdr;
+ int fd;
+};
+
+static Sfdouble_t nget_cur_eof(register Namval_t* np, Namfun_t *fp)
+{
+ struct Eof *ep = (struct Eof*)fp;
+ Sfoff_t end, cur =lseek(ep->fd, (Sfoff_t)0, SEEK_CUR);
+ if(*np->nvname=='C')
+ return((Sfdouble_t)cur);
+ if(cur<0)
+ return((Sfdouble_t)-1);
+ end =lseek(ep->fd, (Sfoff_t)0, SEEK_END);
+ lseek(ep->fd, (Sfoff_t)0, SEEK_CUR);
+ return((Sfdouble_t)end);
+}
+
+static const Namdisc_t EOF_disc = { sizeof(struct Eof), 0, 0, nget_cur_eof};
+
+#define MATCH_BUFF (64*1024)
+struct Match
+{
+ Sfoff_t offset;
+ char *base;
+};
+
+static int matchf(void *handle, char *ptr, size_t size)
+{
+ struct Match *mp = (struct Match*)handle;
+ mp->offset += (ptr-mp->base);
+ return(1);
+}
+
+
+static struct fdsave *filemap;
+static short filemapsize;
+
+/* ======== input output and file copying ======== */
+
+void sh_ioinit(void)
+{
+ register int n;
+ filemapsize = 8;
+ filemap = (struct fdsave*)malloc(8*sizeof(struct fdsave));
+#if SHOPT_FASTPIPE
+ n = sh.lim.open_max+2;
+#else
+ n = sh.lim.open_max;
+#endif /* SHOPT_FASTPIPE */
+ sh.fdstatus = (unsigned char*)malloc((unsigned)n);
+ memset((char*)sh.fdstatus,0,n);
+ sh.fdptrs = (int**)malloc(n*sizeof(int*));
+ memset((char*)sh.fdptrs,0,n*sizeof(int*));
+ sh.sftable = (Sfio_t**)malloc(n*sizeof(Sfio_t*));
+ memset((char*)sh.sftable,0,n*sizeof(Sfio_t*));
+ sh.sftable[0] = sfstdin;
+ sh.sftable[1] = sfstdout;
+ sh.sftable[2] = sfstderr;
+ sfnotify(sftrack);
+ sh_iostream(0);
+ /* all write steams are in the same pool and share outbuff */
+ sh.outpool = sfopen(NIL(Sfio_t*),NIL(char*),"sw"); /* pool identifier */
+ sh.outbuff = (char*)malloc(IOBSIZE);
+ sh.errbuff = (char*)malloc(IOBSIZE/4);
+ sfsetbuf(sfstderr,sh.errbuff,IOBSIZE/4);
+ sfsetbuf(sfstdout,sh.outbuff,IOBSIZE);
+ sfpool(sfstdout,sh.outpool,SF_WRITE);
+ sfpool(sfstderr,sh.outpool,SF_WRITE);
+ sfset(sfstdout,SF_LINE,0);
+}
+
+/*
+ * create or initialize a stream corresponding to descriptor <fd>
+ * a buffer with room for a sentinal is allocated for a read stream.
+ * A discipline is inserted when read stream is a tty or a pipe
+ * For output streams, the buffer is set to sh.output and put into
+ * the sh.outpool synchronization pool
+ */
+Sfio_t *sh_iostream(register int fd)
+{
+ register Sfio_t *iop;
+ register int status = sh_iocheckfd(fd);
+ register int flags = SF_WRITE;
+ char *bp;
+#if SHOPT_FASTPIPE
+ if(fd>=sh.lim.open_max)
+ return(sh.sftable[fd]);
+#endif /* SHOPT_FASTPIPE */
+ if(status==IOCLOSE)
+ {
+ switch(fd)
+ {
+ case 0:
+ return(sfstdin);
+ case 1:
+ return(sfstdout);
+ case 2:
+ return(sfstderr);
+ }
+ return(NIL(Sfio_t*));
+ }
+ if(status&IOREAD)
+ {
+ if(!(bp = (char *)malloc(IOBSIZE+1)))
+ return(NIL(Sfio_t*));
+ flags |= SF_READ;
+ if(!(status&IOWRITE))
+ flags &= ~SF_WRITE;
+ }
+ else
+ bp = sh.outbuff;
+ if(status&IODUP)
+ flags |= SF_SHARE|SF_PUBLIC;
+ if((iop = sh.sftable[fd]) && sffileno(iop)>=0)
+ sfsetbuf(iop, bp, IOBSIZE);
+ else if(!(iop=sfnew((fd<=2?iop:0),bp,IOBSIZE,fd,flags)))
+ return(NIL(Sfio_t*));
+ if(status&IOREAD)
+ {
+ Sfdisc_t *dp;
+ sfset(iop,SF_MALLOC,1);
+ {
+ dp = newof(0,Sfdisc_t,1,0);
+ dp->exceptf = slowexcept;
+ if(status&IOTTY)
+ dp->readf = slowread;
+ else if(status&IONOSEEK)
+ {
+ dp->readf = piperead;
+ sfset(iop, SF_IOINTR,1);
+ }
+ else
+ dp->readf = 0;
+ dp->seekf = 0;
+ dp->writef = 0;
+ sfdisc(iop,dp);
+ }
+ }
+ else
+ sfpool(iop,sh.outpool,SF_WRITE);
+ sh.sftable[fd] = iop;
+ return(iop);
+}
+
+/*
+ * preserve the file descriptor or stream by moving it
+ */
+static void io_preserve(register Sfio_t *sp, register int f2)
+{
+ register int fd;
+ if(sp)
+ fd = sfsetfd(sp,10);
+ else
+ fd = sh_fcntl(f2,F_DUPFD,10);
+ if(f2==sh.infd)
+ sh.infd = fd;
+ if(fd<0)
+ errormsg(SH_DICT,ERROR_system(1),e_toomany);
+ if(sh.fdptrs[fd]=sh.fdptrs[f2])
+ {
+ if(f2==job.fd)
+ job.fd=fd;
+ *sh.fdptrs[fd] = fd;
+ sh.fdptrs[f2] = 0;
+ }
+ sh.sftable[fd] = sp;
+ sh.fdstatus[fd] = sh.fdstatus[f2];
+ if(fcntl(f2,F_GETFD,0)&1)
+ {
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ sh.fdstatus[fd] |= IOCLEX;
+ }
+ sh.sftable[f2] = 0;
+}
+
+/*
+ * Given a file descriptor <f1>, move it to a file descriptor number <f2>
+ * If <f2> is needed move it, otherwise it is closed first.
+ * The original stream <f1> is closed.
+ * The new file descriptor <f2> is returned;
+ */
+int sh_iorenumber(register int f1,register int f2)
+{
+ register Sfio_t *sp = sh.sftable[f2];
+ if(f1!=f2)
+ {
+ /* see whether file descriptor is in use */
+ if(sh_inuse(f2) || (f2>2 && sp))
+ {
+ if(!(sh.inuse_bits&(1<<f2)))
+ io_preserve(sp,f2);
+ sp = 0;
+ }
+ else if(f2==0)
+ sh.st.ioset = 1;
+ sh_close(f2);
+ if(f2<=2 && sp)
+ {
+ register Sfio_t *spnew = sh_iostream(f1);
+ sh.fdstatus[f2] = (sh.fdstatus[f1]&~IOCLEX);
+ sfsetfd(spnew,f2);
+ sfswap(spnew,sp);
+ sfset(sp,SF_SHARE|SF_PUBLIC,1);
+ }
+ else
+ {
+ sh.fdstatus[f2] = (sh.fdstatus[f1]&~IOCLEX);
+ if((f2 = sh_fcntl(f1,F_DUPFD, f2)) < 0)
+ errormsg(SH_DICT,ERROR_system(1),e_file+4);
+ else if(f2 <= 2)
+ sh_iostream(f2);
+ }
+ if(sp)
+ sh.sftable[f1] = 0;
+ sh_close(f1);
+ }
+ return(f2);
+}
+
+/*
+ * close a file descriptor and update stream table and attributes
+ */
+int sh_close(register int fd)
+{
+ register Sfio_t *sp;
+ register int r = 0;
+ if(fd<0)
+ return(-1);
+ if(!(sp=sh.sftable[fd]) || sfclose(sp) < 0)
+ {
+ if(fdnotify)
+ (*fdnotify)(fd,SH_FDCLOSE);
+ r=close(fd);
+ }
+ if(fd>2)
+ sh.sftable[fd] = 0;
+ sh.fdstatus[fd] = IOCLOSE;
+ if(sh.fdptrs[fd])
+ *sh.fdptrs[fd] = -1;
+ sh.fdptrs[fd] = 0;
+ if(fd < 10)
+ sh.inuse_bits &= ~(1<<fd);
+ return(r);
+}
+
+static int
+onintr(struct addrinfo* addr, void* handle)
+{
+ Shell_t* sh = (Shell_t*)handle;
+
+ if (sh->trapnote&SH_SIGSET)
+ {
+ freeaddrinfo(addr);
+ sh_exit(SH_EXITSIG);
+ return -1;
+ }
+ if (sh->trapnote)
+ sh_chktrap();
+ return 0;
+}
+
+/*
+ * Mimic open(2) with checks for pseudo /dev/ files.
+ */
+int sh_open(register const char *path, int flags, ...)
+{
+ register int fd = -1;
+ mode_t mode;
+ char *e;
+ va_list ap;
+ va_start(ap, flags);
+ mode = (flags & O_CREAT) ? va_arg(ap, int) : 0;
+ va_end(ap);
+ errno = 0;
+ if(*path==0)
+ {
+ errno = ENOENT;
+ return(-1);
+ }
+ if (path[0]=='/' && path[1]=='d' && path[2]=='e' && path[3]=='v' && path[4]=='/')
+ {
+ switch (path[5])
+ {
+ case 'f':
+ if (path[6]=='d' && path[7]=='/')
+ {
+ fd = (int)strtol(path+8, &e, 10);
+ if (*e)
+ fd = -1;
+ }
+ break;
+ case 's':
+ if (path[6]=='t' && path[7]=='d')
+ switch (path[8])
+ {
+ case 'e':
+ if (path[9]=='r' && path[10]=='r' && !path[11])
+ fd = 2;
+ break;
+ case 'i':
+ if (path[9]=='n' && !path[10])
+ fd = 0;
+ break;
+ case 'o':
+ if (path[9]=='u' && path[10]=='t' && !path[11])
+ fd = 1;
+ break;
+ }
+ }
+#ifdef O_SERVICE
+ if (fd < 0)
+ {
+ if ((fd = inetopen(path+5, !!(flags & O_SERVICE), onintr, &sh)) < 0 && errno != ENOTDIR)
+ return -1;
+ if (fd >= 0)
+ goto ok;
+ }
+#endif
+ }
+ if (fd >= 0)
+ {
+ if((mode=sh_iocheckfd(fd))==IOCLOSE)
+ return(-1);
+ flags &= O_ACCMODE;
+ if(!(mode&IOWRITE) && ((flags==O_WRONLY) || (flags==O_RDWR)))
+ return(-1);
+ if(!(mode&IOREAD) && ((flags==O_RDONLY) || (flags==O_RDWR)))
+ return(-1);
+ if((fd=dup(fd))<0)
+ return(-1);
+ }
+ else while((fd = open(path, flags, mode)) < 0)
+ if(errno!=EINTR || sh.trapnote)
+ return(-1);
+#ifdef O_SERVICE
+ ok:
+#endif
+ flags &= O_ACCMODE;
+ if(flags==O_WRONLY)
+ mode = IOWRITE;
+ else if(flags==O_RDWR)
+ mode = (IOREAD|IOWRITE);
+ else
+ mode = IOREAD;
+ sh.fdstatus[fd] = mode;
+ return(fd);
+}
+
+/*
+ * Open a file for reading
+ * On failure, print message.
+ */
+int sh_chkopen(register const char *name)
+{
+ register int fd = sh_open(name,O_RDONLY,0);
+ if(fd < 0)
+ errormsg(SH_DICT,ERROR_system(1),e_open,name);
+ return(fd);
+}
+
+/*
+ * move open file descriptor to a number > 2
+ */
+int sh_iomovefd(register int fdold)
+{
+ register int fdnew;
+ if(fdold<0 || fdold>2)
+ return(fdold);
+ fdnew = sh_iomovefd(dup(fdold));
+ sh.fdstatus[fdnew] = (sh.fdstatus[fdold]&~IOCLEX);
+ close(fdold);
+ sh.fdstatus[fdold] = IOCLOSE;
+ return(fdnew);
+}
+
+/*
+ * create a pipe and print message on failure
+ */
+int sh_pipe(register int pv[])
+{
+ int fd[2];
+ if(pipe(fd)<0 || (pv[0]=fd[0])<0 || (pv[1]=fd[1])<0)
+ errormsg(SH_DICT,ERROR_system(1),e_pipe);
+ pv[0] = sh_iomovefd(pv[0]);
+ pv[1] = sh_iomovefd(pv[1]);
+ sh.fdstatus[pv[0]] = IONOSEEK|IOREAD;
+ sh.fdstatus[pv[1]] = IONOSEEK|IOWRITE;
+ sh_subsavefd(pv[0]);
+ sh_subsavefd(pv[1]);
+ return(0);
+}
+
+static int pat_seek(void *handle, const char *str, size_t sz)
+{
+ char **bp = (char**)handle;
+ *bp = (char*)str;
+ return(-1);
+}
+
+static int pat_line(const regex_t* rp, const char *buff, register size_t n)
+{
+ register const char *cp=buff, *sp;
+ while(n>0)
+ {
+ for(sp=cp; n-->0 && *cp++ != '\n';);
+ if(regnexec(rp,sp,cp-sp, 0, (regmatch_t*)0, 0)==0)
+ return(sp-buff);
+ }
+ return(cp-buff);
+}
+
+static int io_patseek(regex_t *rp, Sfio_t* sp, int flags)
+{
+ char *cp, *match;
+ int r, fd=sffileno(sp), close_exec = sh.fdstatus[fd]&IOCLEX;
+ int was_share,s=(PIPE_BUF>SF_BUFSIZE?SF_BUFSIZE:PIPE_BUF);
+ size_t n,m;
+ sh.fdstatus[sffileno(sp)] |= IOCLEX;
+ if(fd==0)
+ was_share = sfset(sp,SF_SHARE,1);
+ while((cp=sfreserve(sp, -s, SF_LOCKR)) || (cp=sfreserve(sp,SF_UNBOUND, SF_LOCKR)))
+ {
+ m = n = sfvalue(sp);
+ while(n>0 && cp[n-1]!='\n')
+ n--;
+ if(n)
+ m = n;
+ r = regrexec(rp,cp,m,0,(regmatch_t*)0, 0, '\n', (void*)&match, pat_seek);
+ if(r<0)
+ m = match-cp;
+ else if(r==2)
+ {
+ if((m = pat_line(rp,cp,m)) < n)
+ r = -1;
+ }
+ if(m && (flags&IOCOPY))
+ sfwrite(sfstdout,cp,m);
+ sfread(sp,cp,m);
+ if(r<0)
+ break;
+ }
+ if(!close_exec)
+ sh.fdstatus[sffileno(sp)] &= ~IOCLEX;
+ if(fd==0 && !(was_share&SF_SHARE))
+ sfset(sp, SF_SHARE,0);
+ return(0);
+}
+
+static Sfoff_t file_offset(int fn, char *fname)
+{
+ Sfio_t *sp = sh.sftable[fn];
+ char *cp;
+ Sfoff_t off;
+ struct Eof endf;
+ Namval_t *mp = nv_open("EOF",sh.var_tree,0);
+ Namval_t *pp = nv_open("CUR",sh.var_tree,0);
+ memset(&endf,0,sizeof(struct Eof));
+ endf.fd = fn;
+ endf.hdr.disc = &EOF_disc;
+ endf.hdr.nofree = 1;
+ if(mp)
+ nv_stack(mp, &endf.hdr);
+ if(pp)
+ nv_stack(pp, &endf.hdr);
+ if(sp)
+ sfsync(sp);
+ off = sh_strnum(fname, &cp, 0);
+ if(mp)
+ nv_stack(mp, NiL);
+ if(pp)
+ nv_stack(pp, NiL);
+ return(*cp?(Sfoff_t)-1:off);
+}
+
+/*
+ * close a pipe
+ */
+void sh_pclose(register int pv[])
+{
+ if(pv[0]>=2)
+ sh_close(pv[0]);
+ if(pv[1]>=2)
+ sh_close(pv[1]);
+ pv[0] = pv[1] = -1;
+}
+
+/*
+ * I/O redirection
+ * flag = 0 if files are to be restored
+ * flag = 2 if files are to be closed on exec
+ * flag = 3 when called from $( < ...), just open file and return
+ * flag = SH_SHOWME for trace only
+ */
+int sh_redirect(struct ionod *iop, int flag)
+{
+ Sfoff_t off;
+ register char *fname;
+ register int fd, iof;
+ const char *message = e_open;
+ int o_mode; /* mode flag for open */
+ static char io_op[7]; /* used for -x trace info */
+ int clexec=0, fn, traceon;
+ int r, indx = sh.topfd;
+ char *after="", *trace = sh.st.trap[SH_DEBUGTRAP];
+ Namval_t *np=0;
+ if(flag==2)
+ clexec = 1;
+ if(iop)
+ traceon = sh_trace(NIL(char**),0);
+ for(;iop;iop=iop->ionxt)
+ {
+ iof=iop->iofile;
+ fn = (iof&IOUFD);
+ io_op[0] = '0'+(iof&IOUFD);
+ if(iof&IOPUT)
+ {
+ io_op[1] = '>';
+ o_mode = O_WRONLY|O_CREAT;
+ }
+ else
+ {
+ io_op[1] = '<';
+ o_mode = O_RDONLY|O_NONBLOCK;
+ }
+ io_op[2] = 0;
+ io_op[3] = 0;
+ io_op[4] = 0;
+ fname = iop->ioname;
+ if(!(iof&IORAW))
+ {
+ if(iof&IOLSEEK)
+ {
+ struct argnod *ap = (struct argnod*)stakalloc(ARGVAL+strlen(iop->ioname));
+ memset(ap, 0, ARGVAL);
+ ap->argflag = ARG_MAC;
+ strcpy(ap->argval,iop->ioname);
+ fname=sh_macpat(ap,(iof&IOARITH)?ARG_ARITH:ARG_EXP);
+ }
+ else
+ fname=sh_mactrim(fname,(!sh_isoption(SH_NOGLOB)&&sh_isoption(SH_INTERACTIVE))?2:0);
+ }
+ errno=0;
+ if(iop->iovname)
+ {
+ np = nv_open(iop->iovname,sh.var_tree,NV_NOASSIGN|NV_VARNAME);
+ if(nv_isattr(np,NV_RDONLY))
+ errormsg(SH_DICT,ERROR_exit(1),e_readonly, nv_name(np));
+ io_op[0] = '}';
+ if((iof&IOMOV) && *fname=='-')
+ fn = nv_getnum(np);
+ }
+ if(iof&IOLSEEK)
+ {
+ io_op[2] = '#';
+ if(iof&IOARITH)
+ {
+ strcpy(&io_op[3]," ((");
+ after = "))";
+ }
+ else if(iof&IOCOPY)
+ io_op[3] = '#';
+ goto traceit;
+ }
+ if(*fname)
+ {
+ if(iof&IODOC)
+ {
+ if(traceon)
+ sfputr(sfstderr,io_op,'<');
+ fd = io_heredoc(iop,fname,traceon);
+ if(traceon && (flag==SH_SHOWME))
+ sh_close(fd);
+ fname = 0;
+ }
+ else if(iof&IOMOV)
+ {
+ int dupfd,toclose= -1;
+ io_op[2] = '&';
+ if((fd=fname[0])>='0' && fd<='9')
+ {
+ char *number = fname;
+ dupfd = strtol(fname,&number,10);
+ if(*number=='-')
+ {
+ toclose = dupfd;
+ number++;
+ }
+ if(*number || dupfd > IOUFD)
+ {
+ message = e_file;
+ goto fail;
+ }
+ if(sh.subshell && dupfd==1)
+ {
+ sh_subtmpfile();
+ dupfd = sffileno(sfstdout);
+ }
+ else if(sh.sftable[dupfd])
+ sfsync(sh.sftable[dupfd]);
+ }
+ else if(fd=='-' && fname[1]==0)
+ {
+ fd= -1;
+ goto traceit;
+ }
+ else if(fd=='p' && fname[1]==0)
+ {
+ if(iof&IOPUT)
+ dupfd = sh.coutpipe;
+ else
+ dupfd = sh.cpipe[0];
+ if(flag)
+ toclose = dupfd;
+ }
+ else
+ {
+ message = e_file;
+ goto fail;
+ }
+ if(flag==SH_SHOWME)
+ goto traceit;
+ if((fd=sh_fcntl(dupfd,F_DUPFD,3))<0)
+ goto fail;
+ sh_iocheckfd(dupfd);
+ sh.fdstatus[fd] = (sh.fdstatus[dupfd]&~IOCLEX);
+ if(toclose<0 && sh.fdstatus[fd]&IOREAD)
+ sh.fdstatus[fd] |= IODUP;
+ else if(dupfd==sh.cpipe[0])
+ sh_pclose(sh.cpipe);
+ else if(toclose>=0)
+ {
+ if(flag==0)
+ sh_iosave(toclose,indx); /* save file descriptor */
+ sh_close(toclose);
+ }
+ }
+ else if(iof&IORDW)
+ {
+ if(sh_isoption(SH_RESTRICTED))
+ errormsg(SH_DICT,ERROR_exit(1),e_restricted,fname);
+ io_op[2] = '>';
+ o_mode = O_RDWR|O_CREAT;
+ goto openit;
+ }
+ else if(!(iof&IOPUT))
+ {
+ if(flag==SH_SHOWME)
+ goto traceit;
+ fd=sh_chkopen(fname);
+ }
+ else if(sh_isoption(SH_RESTRICTED))
+ errormsg(SH_DICT,ERROR_exit(1),e_restricted,fname);
+ else
+ {
+ if(iof&IOAPP)
+ {
+ io_op[2] = '>';
+ o_mode |= O_APPEND;
+ }
+ else
+ {
+ o_mode |= O_TRUNC;
+ if(iof&IOCLOB)
+ io_op[2] = '|';
+ else if(sh_isoption(SH_NOCLOBBER))
+ {
+ struct stat sb;
+ if(stat(fname,&sb)>=0)
+ {
+#if SHOPT_FS_3D
+ if(S_ISREG(sb.st_mode)&&
+ (!sh.lim.fs3d || iview(&sb)==0))
+#else
+ if(S_ISREG(sb.st_mode))
+#endif /* SHOPT_FS_3D */
+ {
+ errno = EEXIST;
+ errormsg(SH_DICT,ERROR_system(1),e_exists,fname);
+ }
+ }
+ else
+ o_mode |= O_EXCL;
+ }
+ }
+ openit:
+ if(flag!=SH_SHOWME)
+ {
+ if((fd=sh_open(fname,o_mode,RW_ALL)) <0)
+ errormsg(SH_DICT,ERROR_system(1),((o_mode&O_CREAT)?e_create:e_open),fname);
+ }
+ }
+ traceit:
+ if(traceon && fname)
+ {
+ if(np)
+ sfprintf(sfstderr,"{%s",nv_name(np));
+ sfprintf(sfstderr,"%s %s%s%c",io_op,fname,after,iop->ionxt?' ':'\n');
+ }
+ if(flag==SH_SHOWME)
+ return(indx);
+ if(trace && fname)
+ {
+ char *argv[7], **av=argv;
+ av[3] = io_op;
+ av[4] = fname;
+ av[5] = 0;
+ av[6] = 0;
+ if(iof&IOARITH)
+ av[5] = after;
+ if(np)
+ {
+ av[0] = "{";
+ av[1] = nv_name(np);
+ av[2] = "}";
+ }
+ else
+ av +=3;
+ sh_debug(trace,(char*)0,(char*)0,av,ARG_NOGLOB);
+ }
+ if(iof&IOLSEEK)
+ {
+ Sfio_t *sp = sh.sftable[fn];
+ r = sh.fdstatus[fn];
+ if(!(r&(IOSEEK|IONOSEEK)))
+ r = sh_iocheckfd(fn);
+ sfsprintf(io_op,sizeof(io_op),"%d\0",fn);
+ if(r==IOCLOSE)
+ {
+ fname = io_op;
+ message = e_file;
+ goto fail;
+ }
+ if(iof&IOARITH)
+ {
+ if(r&IONOSEEK)
+ {
+ fname = io_op;
+ message = e_notseek;
+ goto fail;
+ }
+ message = e_badseek;
+ if((off = file_offset(fn,fname))<0)
+ goto fail;
+ if(sp)
+ r=sfseek(sp, off, SEEK_SET);
+ else
+ r=lseek(fn, off, SEEK_SET);
+ }
+ else
+ {
+ regex_t *rp;
+ extern const char e_notimp[];
+ if(!(r&IOREAD))
+ {
+ message = e_noread;
+ goto fail;
+ }
+ if(!(rp = regcache(fname, REG_SHELL|REG_NOSUB|REG_NEWLINE|REG_AUGMENTED|REG_FIRST|REG_LEFT|REG_RIGHT, &r)))
+ {
+ message = e_badpattern;
+ goto fail;
+ }
+ if(!sp)
+ sp = sh_iostream(fn);
+ r=io_patseek(rp,sp,iof);
+ if(sp && flag==3)
+ {
+ /* close stream but not fn */
+ sfsetfd(sp,-1);
+ sfclose(sp);
+ }
+ }
+ if(r<0)
+ goto fail;
+ if(flag==3)
+ return(fn);
+ continue;
+ }
+ if(!np)
+ {
+ if(flag==0)
+ {
+ if(fd==fn)
+ {
+ if((r=sh_fcntl(fd,F_DUPFD,10)) > 0)
+ {
+ fd = r;
+ sh_close(fn);
+ }
+ }
+ sh_iosave(fn,indx);
+ }
+ else if(sh_subsavefd(fn))
+ sh_iosave(fn,indx|IOSUBSHELL);
+ }
+ if(fd<0)
+ {
+ if(sh_inuse(fn) || fn==sh.infd)
+ {
+ if(fn>9 || !(sh.inuse_bits&(1<<fn)))
+ io_preserve(sh.sftable[fn],fn);
+ }
+ sh_close(fn);
+ }
+ if(flag==3)
+ return(fd);
+ if(fd>=0)
+ {
+ if(np)
+ {
+ int32_t v;
+ fn = fd;
+ if(fd<10)
+ {
+ if((fn=fcntl(fd,F_DUPFD,10)) < 0)
+ goto fail;
+ sh.fdstatus[fn] = sh.fdstatus[fd];
+ sh_close(fd);
+ fd = fn;
+ }
+ nv_unset(np);
+ nv_onattr(np,NV_INT32);
+ v = fn;
+ nv_putval(np,(char*)&v, NV_INT32);
+ sh_iocheckfd(fd);
+ }
+ else
+ {
+ fd = sh_iorenumber(sh_iomovefd(fd),fn);
+ if(fn>2 && fn<10)
+ sh.inuse_bits |= (1<<fn);
+ }
+ }
+ if(fd >2 && clexec)
+ {
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ sh.fdstatus[fd] |= IOCLEX;
+ }
+ }
+ else
+ goto fail;
+ }
+ return(indx);
+fail:
+ errormsg(SH_DICT,ERROR_system(1),message,fname);
+ /* NOTREACHED */
+ return(0);
+}
+/*
+ * Create a tmp file for the here-document
+ */
+static int io_heredoc(register struct ionod *iop, const char *name, int traceon)
+{
+ register Sfio_t *infile = 0, *outfile;
+ register int fd;
+ if(!(iop->iofile&IOSTRG) && (!sh.heredocs || iop->iosize==0))
+ return(sh_open(e_devnull,O_RDONLY));
+ /* create an unnamed temporary file */
+ if(!(outfile=sftmp(0)))
+ errormsg(SH_DICT,ERROR_system(1),e_tmpcreate);
+ if(iop->iofile&IOSTRG)
+ {
+ if(traceon)
+ sfprintf(sfstderr,"< %s\n",name);
+ sfputr(outfile,name,'\n');
+ }
+ else
+ {
+ infile = subopen(sh.heredocs,iop->iooffset,iop->iosize);
+ if(traceon)
+ {
+ char *cp = sh_fmtq(iop->iodelim);
+ fd = (*cp=='$' || *cp=='\'')?' ':'\\';
+ sfprintf(sfstderr," %c%s\n",fd,cp);
+ sfdisc(outfile,&tee_disc);
+ }
+ if(iop->iofile&IOQUOTE)
+ {
+ /* This is a quoted here-document, not expansion */
+ sfmove(infile,outfile,SF_UNBOUND,-1);
+ sfclose(infile);
+ }
+ else
+ {
+ char *lastpath = sh.lastpath;
+ sh_machere(infile,outfile,iop->ioname);
+ sh.lastpath = lastpath;
+ if(infile)
+ sfclose(infile);
+ }
+ }
+ /* close stream outfile, but save file descriptor */
+ fd = sffileno(outfile);
+ sfsetfd(outfile,-1);
+ sfclose(outfile);
+ if(traceon && !(iop->iofile&IOSTRG))
+ sfputr(sfstderr,iop->ioname,'\n');
+ lseek(fd,(off_t)0,SEEK_SET);
+ sh.fdstatus[fd] = IOREAD;
+ return(fd);
+}
+
+/*
+ * This write discipline also writes the output on standard error
+ * This is used when tracing here-documents
+ */
+static ssize_t tee_write(Sfio_t *iop,const void *buff,size_t n,Sfdisc_t *unused)
+{
+ NOT_USED(unused);
+ sfwrite(sfstderr,buff,n);
+ return(write(sffileno(iop),buff,n));
+}
+
+/*
+ * copy file <origfd> into a save place
+ * The saved file is set close-on-exec
+ * if <origfd> < 0, then -origfd is saved, but not duped so that it
+ * will be closed with sh_iorestore.
+ */
+void sh_iosave(register int origfd, int oldtop)
+{
+/*@
+ assume oldtop>=0 && oldtop<sh.lim.open_max;
+@*/
+
+ register int savefd;
+ int flag = (oldtop&IOSUBSHELL);
+ oldtop &= ~IOSUBSHELL;
+ /* see if already saved, only save once */
+ for(savefd=sh.topfd; --savefd>=oldtop; )
+ {
+ if(filemap[savefd].orig_fd == origfd)
+ return;
+ }
+ /* make sure table is large enough */
+ if(sh.topfd >= filemapsize)
+ {
+ filemapsize += 8;
+ if(!(filemap = (struct fdsave*)realloc(filemap,filemapsize*sizeof(struct fdsave))))
+ errormsg(SH_DICT,ERROR_exit(4),e_nospace);
+
+ }
+#if SHOPT_DEVFD
+ if(origfd <0)
+ {
+ savefd = origfd;
+ origfd = -origfd;
+ }
+ else
+#endif /* SHOPT_DEVFD */
+ {
+ if((savefd = sh_fcntl(origfd, F_DUPFD, 10)) < 0 && errno!=EBADF)
+ errormsg(SH_DICT,ERROR_system(1),e_toomany);
+ }
+ filemap[sh.topfd].subshell = flag;
+ filemap[sh.topfd].orig_fd = origfd;
+ filemap[sh.topfd++].save_fd = savefd;
+ if(savefd >=0)
+ {
+ register Sfio_t* sp = sh.sftable[origfd];
+ /* make saved file close-on-exec */
+ sh_fcntl(savefd,F_SETFD,FD_CLOEXEC);
+ if(origfd==job.fd)
+ job.fd = savefd;
+ sh.fdstatus[savefd] = sh.fdstatus[origfd];
+ sh.fdptrs[savefd] = &filemap[sh.topfd-1].save_fd;
+ if(!(sh.sftable[savefd]=sp))
+ return;
+ sfsync(sp);
+ if(origfd <=2)
+ {
+ /* copy standard stream to new stream */
+ sp = sfswap(sp,NIL(Sfio_t*));
+ sh.sftable[savefd] = sp;
+ }
+ else
+ sh.sftable[origfd] = 0;
+ }
+}
+
+/*
+ * close all saved file descriptors
+ */
+void sh_iounsave(void)
+{
+ register int fd, savefd, newfd;
+ for(newfd=fd=0; fd < sh.topfd; fd++)
+ {
+ if((savefd = filemap[fd].save_fd)< 0)
+ filemap[newfd++] = filemap[fd];
+ else
+ {
+ sh.sftable[savefd] = 0;
+ sh_close(savefd);
+ }
+ }
+ sh.topfd = newfd;
+}
+
+/*
+ * restore saved file descriptors from <last> on
+ */
+void sh_iorestore(int last, int jmpval)
+{
+ register int origfd, savefd, fd;
+ int flag = (last&IOSUBSHELL);
+ last &= ~IOSUBSHELL;
+ for (fd = sh.topfd - 1; fd >= last; fd--)
+ {
+ if(!flag && filemap[fd].subshell)
+ continue;
+ if(jmpval==SH_JMPSCRIPT)
+ {
+ if ((savefd = filemap[fd].save_fd) >= 0)
+ {
+ sh.sftable[savefd] = 0;
+ sh_close(savefd);
+ }
+ continue;
+ }
+ origfd = filemap[fd].orig_fd;
+ sh_close(origfd);
+ if ((savefd = filemap[fd].save_fd) >= 0)
+ {
+ sh_fcntl(savefd, F_DUPFD, origfd);
+ if(savefd==job.fd)
+ job.fd=origfd;
+ sh.fdstatus[origfd] = sh.fdstatus[savefd];
+ /* turn off close-on-exec if flag if necessary */
+ if(sh.fdstatus[origfd]&IOCLEX)
+ fcntl(origfd,F_SETFD,FD_CLOEXEC);
+ if(origfd<=2)
+ {
+ sfswap(sh.sftable[savefd],sh.sftable[origfd]);
+ if(origfd==0)
+ sh.st.ioset = 0;
+ }
+ else
+ sh.sftable[origfd] = sh.sftable[savefd];
+ sh.sftable[savefd] = 0;
+ sh_close(savefd);
+ }
+ else
+ sh.fdstatus[origfd] = IOCLOSE;
+ }
+ if(!flag)
+ {
+ /* keep file descriptors for subshell restore */
+ for (fd = last ; fd < sh.topfd; fd++)
+ {
+ if(filemap[fd].subshell)
+ filemap[last++] = filemap[fd];
+ }
+ }
+ if(last < sh.topfd)
+ sh.topfd = last;
+}
+
+/*
+ * returns access information on open file <fd>
+ * returns -1 for failure, 0 for success
+ * <mode> is the same as for access()
+ */
+int sh_ioaccess(int fd,register int mode)
+{
+ register int flags;
+ if(mode==X_OK)
+ return(-1);
+ if((flags=sh_iocheckfd(fd))!=IOCLOSE)
+ {
+ if(mode==F_OK)
+ return(0);
+ if(mode==R_OK && (flags&IOREAD))
+ return(0);
+ if(mode==W_OK && (flags&IOWRITE))
+ return(0);
+ }
+ return(-1);
+}
+
+/*
+ * Handle interrupts for slow streams
+ */
+static int slowexcept(register Sfio_t *iop,int type,void *data,Sfdisc_t *handle)
+{
+ register int n,fno;
+ NOT_USED(handle);
+ if(type==SF_DPOP || type==SF_FINAL)
+ free((void*)handle);
+ if(type!=SF_READ)
+ return(0);
+ if((sh.trapnote&(SH_SIGSET|SH_SIGTRAP)) && errno!=EIO && errno!=ENXIO)
+ errno = EINTR;
+ fno = sffileno(iop);
+ if((n=sfvalue(iop))<=0)
+ {
+#ifndef FNDELAY
+# ifdef O_NDELAY
+ if(errno==0 && (n=fcntl(fno,F_GETFL,0))&O_NDELAY)
+ {
+ n &= ~O_NDELAY;
+ fcntl(fno, F_SETFL, n);
+ return(1);
+ }
+# endif /* O_NDELAY */
+#endif /* !FNDELAY */
+#ifdef O_NONBLOCK
+ if(errno==EAGAIN)
+ {
+ n = fcntl(fno,F_GETFL,0);
+ n &= ~O_NONBLOCK;
+ fcntl(fno, F_SETFL, n);
+ return(1);
+ }
+#endif /* O_NONBLOCK */
+ if(errno!=EINTR)
+ return(0);
+ n=1;
+ }
+ errno = 0;
+ if(sh.trapnote&SH_SIGSET)
+ {
+ if(isatty(fno))
+ sfputc(sfstderr,'\n');
+ sh_exit(SH_EXITSIG);
+ }
+ if(sh.trapnote&SH_SIGTRAP)
+ sh_chktrap();
+ return(n);
+}
+
+/*
+ * called when slowread times out
+ */
+static void time_grace(void *handle)
+{
+ NOT_USED(handle);
+ timeout = 0;
+ if(sh_isstate(SH_GRACE))
+ {
+ sh_offstate(SH_GRACE);
+ if(!sh_isstate(SH_INTERACTIVE))
+ return;
+ ((struct checkpt*)sh.jmplist)->mode = SH_JMPEXIT;
+ errormsg(SH_DICT,2,e_timeout);
+ sh.trapnote |= SH_SIGSET;
+ return;
+ }
+ errormsg(SH_DICT,0,e_timewarn);
+ sh_onstate(SH_GRACE);
+ sigrelease(SIGALRM);
+ sh.trapnote |= SH_SIGTRAP;
+}
+
+static ssize_t piperead(Sfio_t *iop,void *buff,register size_t size,Sfdisc_t *handle)
+{
+ int fd = sffileno(iop);
+ NOT_USED(handle);
+ if(sh.trapnote)
+ {
+ errno = EINTR;
+ return(-1);
+ }
+ if(sh_isstate(SH_INTERACTIVE) && io_prompt(iop,sh.nextprompt)<0 && errno==EIO)
+ return(0);
+ if(!(sh.fdstatus[sffileno(iop)]&IOCLEX) && (sfset(iop,0,0)&SF_SHARE))
+ size = ed_read(sh.ed_context, fd, (char*)buff, size,0);
+ else
+ size = sfrd(iop,buff,size,handle);
+ return(size);
+}
+/*
+ * This is the read discipline that is applied to slow devices
+ * This routine takes care of prompting for input
+ */
+static ssize_t slowread(Sfio_t *iop,void *buff,register size_t size,Sfdisc_t *handle)
+{
+ int (*readf)(void*, int, char*, int, int);
+ int reedit=0, rsize;
+#if SHOPT_HISTEXPAND
+ char *xp=0;
+#endif
+ NOT_USED(handle);
+# if SHOPT_ESH
+ if(sh_isoption(SH_EMACS) || sh_isoption(SH_GMACS))
+ readf = ed_emacsread;
+ else
+# endif /* SHOPT_ESH */
+# if SHOPT_VSH
+# if SHOPT_RAWONLY
+ if(sh_isoption(SH_VI) || ((SHOPT_RAWONLY-0) && mbwide()))
+# else
+ if(sh_isoption(SH_VI))
+# endif
+ readf = ed_viread;
+ else
+# endif /* SHOPT_VSH */
+ readf = ed_read;
+ if(sh.trapnote)
+ {
+ errno = EINTR;
+ return(-1);
+ }
+ while(1)
+ {
+ if(io_prompt(iop,sh.nextprompt)<0 && errno==EIO)
+ return(0);
+ if(sh.timeout)
+ timeout = (void*)sh_timeradd(sh_isstate(SH_GRACE)?1000L*TGRACE:1000L*sh.timeout,0,time_grace,NIL(void*));
+ rsize = (*readf)(sh.ed_context, sffileno(iop), (char*)buff, size, reedit);
+ if(timeout)
+ timerdel(timeout);
+ timeout=0;
+#if SHOPT_HISTEXPAND
+ if(rsize && *(char*)buff != '\n' && sh.nextprompt==1 && sh_isoption(SH_HISTEXPAND))
+ {
+ int r;
+ ((char*)buff)[rsize] = '\0';
+ if(xp)
+ {
+ free(xp);
+ xp = 0;
+ }
+ r = hist_expand(buff, &xp);
+ if((r & (HIST_EVENT|HIST_PRINT)) && !(r & HIST_ERROR) && xp)
+ {
+ strlcpy(buff, xp, size);
+ rsize = strlen(buff);
+ if(!sh_isoption(SH_HISTVERIFY) || readf==ed_read)
+ {
+ sfputr(sfstderr, xp, -1);
+ break;
+ }
+ reedit = rsize - 1;
+ continue;
+ }
+ if((r & HIST_ERROR) && sh_isoption(SH_HISTREEDIT))
+ {
+ reedit = rsize - 1;
+ continue;
+ }
+ if(r & (HIST_ERROR|HIST_PRINT))
+ {
+ *(char*)buff = '\n';
+ rsize = 1;
+ }
+ }
+#endif
+ break;
+ }
+ return(rsize);
+}
+
+/*
+ * check and return the attributes for a file descriptor
+ */
+
+int sh_iocheckfd(register int fd)
+{
+ register int flags, n;
+ if((n=sh.fdstatus[fd])&IOCLOSE)
+ return(n);
+ if(!(n&(IOREAD|IOWRITE)))
+ {
+#ifdef F_GETFL
+ if((flags=fcntl(fd,F_GETFL,0)) < 0)
+ return(sh.fdstatus[fd]=IOCLOSE);
+ if((flags&O_ACCMODE)!=O_WRONLY)
+ n |= IOREAD;
+ if((flags&O_ACCMODE)!=O_RDONLY)
+ n |= IOWRITE;
+#else
+ struct stat statb;
+ if((flags = fstat(fd,&statb))< 0)
+ return(sh.fdstatus[fd]=IOCLOSE);
+ n |= (IOREAD|IOWRITE);
+ if(read(fd,"",0) < 0)
+ n &= ~IOREAD;
+#endif /* F_GETFL */
+ }
+ if(!(n&(IOSEEK|IONOSEEK)))
+ {
+ struct stat statb;
+ /* /dev/null check is a workaround for select bug */
+ static ino_t null_ino;
+ static dev_t null_dev;
+ if(null_ino==0 && stat(e_devnull,&statb) >=0)
+ {
+ null_ino = statb.st_ino;
+ null_dev = statb.st_dev;
+ }
+ if(tty_check(fd))
+ n |= IOTTY;
+ if(lseek(fd,NIL(off_t),SEEK_CUR)<0)
+ {
+ n |= IONOSEEK;
+#ifdef S_ISSOCK
+ if((fstat(fd,&statb)>=0) && S_ISSOCK(statb.st_mode))
+ n |= IOREAD|IOWRITE;
+#endif /* S_ISSOCK */
+ }
+ else if((fstat(fd,&statb)>=0) && (
+ S_ISFIFO(statb.st_mode) ||
+#ifdef S_ISSOCK
+ S_ISSOCK(statb.st_mode) ||
+#endif /* S_ISSOCK */
+ /* The following is for sockets on the sgi */
+ (statb.st_ino==0 && (statb.st_mode & ~(S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH|S_IXUSR|S_IXGRP|S_IXOTH|S_ISUID|S_ISGID))==0) ||
+ (S_ISCHR(statb.st_mode) && (statb.st_ino!=null_ino || statb.st_dev!=null_dev))
+ ))
+ n |= IONOSEEK;
+ else
+ n |= IOSEEK;
+ }
+ sh.fdstatus[fd] = n;
+ return(n);
+}
+
+/*
+ * Display prompt PS<flag> on standard error
+ */
+
+static int io_prompt(Sfio_t *iop,register int flag)
+{
+ register char *cp;
+ char buff[1];
+ char *endprompt;
+ static short cmdno;
+ int sfflags;
+ if(flag<3 && !sh_isstate(SH_INTERACTIVE))
+ flag = 0;
+ if(flag==2 && sfpkrd(sffileno(iop),buff,1,'\n',0,1) >= 0)
+ flag = 0;
+ if(flag==0)
+ return(sfsync(sfstderr));
+ sfflags = sfset(sfstderr,SF_SHARE|SF_PUBLIC|SF_READ,0);
+ if(!(sh.prompt=(char*)sfreserve(sfstderr,0,0)))
+ sh.prompt = "";
+ switch(flag)
+ {
+ case 1:
+ {
+ register int c;
+#if defined(TIOCLBIC) && defined(LFLUSHO)
+ if(!sh_isoption(SH_VI) && !sh_isoption(SH_EMACS) && !sh_isoption(SH_GMACS))
+ {
+ /*
+ * re-enable output in case the user has
+ * disabled it. Not needed with edit mode
+ */
+ int mode = LFLUSHO;
+ ioctl(sffileno(sfstderr),TIOCLBIC,&mode);
+ }
+#endif /* TIOCLBIC */
+ cp = sh_mactry(nv_getval(nv_scoped(PS1NOD)));
+ for(;c= *cp;cp++)
+ {
+ if(c==HIST_CHAR)
+ {
+ /* look at next character */
+ c = *++cp;
+ /* print out line number if not !! */
+ if(c!= HIST_CHAR)
+ {
+ sfprintf(sfstderr,"%d", sh.hist_ptr?(int)sh.hist_ptr->histind:++cmdno);
+ }
+ if(c==0)
+ goto done;
+ }
+ sfputc(sfstderr,c);
+ }
+ goto done;
+ }
+ case 2:
+ cp = nv_getval(nv_scoped(PS2NOD));
+ break;
+ case 3:
+ cp = nv_getval(nv_scoped(PS3NOD));
+ break;
+ default:
+ goto done;
+ }
+ if(cp)
+ sfputr(sfstderr,cp,-1);
+done:
+ if(*sh.prompt && (endprompt=(char*)sfreserve(sfstderr,0,0)))
+ *endprompt = 0;
+ sfset(sfstderr,sfflags&SF_READ|SF_SHARE|SF_PUBLIC,1);
+ return(sfsync(sfstderr));
+}
+
+/*
+ * This discipline is inserted on write pipes to prevent SIGPIPE
+ * from causing an infinite loop
+ */
+static int pipeexcept(Sfio_t* iop, int mode, void *data, Sfdisc_t* handle)
+{
+ NOT_USED(iop);
+ if(mode==SF_DPOP || mode==SF_FINAL)
+ free((void*)handle);
+ else if(mode==SF_WRITE && errno==EINTR && sh.lastsig==SIGPIPE)
+ return(-1);
+ return(0);
+}
+
+/*
+ * keep track of each stream that is opened and closed
+ */
+static void sftrack(Sfio_t* sp,int flag, int newfd)
+{
+ register int fd = sffileno(sp);
+ register struct checkpt *pp;
+ register int mode;
+ if(flag==SF_SETFD || flag==SF_CLOSING)
+ {
+ if(newfd<0)
+ flag = SF_CLOSING;
+ if(fdnotify)
+ (*fdnotify)(sffileno(sp),flag==SF_CLOSING?-1:newfd);
+ }
+#ifdef DEBUG
+ if(flag==SF_READ || flag==SF_WRITE)
+ {
+ char *z = fmtbase((long)getpid(),0,0);
+ write(ERRIO,z,strlen(z));
+ write(ERRIO,": ",2);
+ write(ERRIO,"attempt to ",11);
+ if(flag==SF_READ)
+ write(ERRIO,"read from",9);
+ else
+ write(ERRIO,"write to",8);
+ write(ERRIO," locked stream\n",15);
+ return;
+ }
+#endif
+ if((unsigned)fd >= sh.lim.open_max)
+ return;
+ if(sh_isstate(SH_NOTRACK))
+ return;
+ mode = sfset(sp,0,0);
+ if(sp==sh.heredocs && fd < 10 && flag==SF_NEW)
+ {
+ fd = sfsetfd(sp,10);
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ }
+ if(fd < 3)
+ return;
+ if(flag==SF_NEW)
+ {
+ if(!sh.sftable[fd] && sh.fdstatus[fd]==IOCLOSE)
+ {
+ sh.sftable[fd] = sp;
+ flag = (mode&SF_WRITE)?IOWRITE:0;
+ if(mode&SF_READ)
+ flag |= IOREAD;
+ sh.fdstatus[fd] = flag;
+#if 0
+ if(flag==IOWRITE)
+ sfpool(sp,sh.outpool,SF_WRITE);
+ else
+#else
+ if(flag!=IOWRITE)
+#endif
+ sh_iostream(fd);
+ }
+ if((pp=(struct checkpt*)sh.jmplist) && pp->mode==SH_JMPCMD)
+ {
+ struct openlist *item;
+ /*
+ * record open file descriptors so they can
+ * be closed in case a longjmp prevents
+ * built-ins from cleanup
+ */
+ item = new_of(struct openlist, 0);
+ item->strm = sp;
+ item->next = pp->olist;
+ pp->olist = item;
+ }
+ if(fdnotify)
+ (*fdnotify)(-1,sffileno(sp));
+ }
+ else if(flag==SF_CLOSING || (flag==SF_SETFD && newfd<=2))
+ {
+ sh.sftable[fd] = 0;
+ sh.fdstatus[fd]=IOCLOSE;
+ if(pp=(struct checkpt*)sh.jmplist)
+ {
+ struct openlist *item;
+ for(item=pp->olist; item; item=item->next)
+ {
+ if(item->strm == sp)
+ {
+ item->strm = 0;
+ break;
+ }
+ }
+ }
+ }
+}
+
+struct eval
+{
+ Sfdisc_t disc;
+ char **argv;
+ short slen;
+ char addspace;
+};
+
+/*
+ * Create a stream consisting of a space separated argv[] list
+ */
+
+Sfio_t *sh_sfeval(register char *argv[])
+{
+ register Sfio_t *iop;
+ register char *cp;
+ if(argv[1])
+ cp = "";
+ else
+ cp = argv[0];
+ iop = sfopen(NIL(Sfio_t*),(char*)cp,"s");
+ if(argv[1])
+ {
+ register struct eval *ep;
+ if(!(ep = new_of(struct eval,0)))
+ return(NIL(Sfio_t*));
+ ep->disc = eval_disc;
+ ep->argv = argv;
+ ep->slen = -1;
+ ep->addspace = 0;
+ sfdisc(iop,&ep->disc);
+ }
+ return(iop);
+}
+
+/*
+ * This code gets called whenever an end of string is found with eval
+ */
+
+static int eval_exceptf(Sfio_t *iop,int type, void *data, Sfdisc_t *handle)
+{
+ register struct eval *ep = (struct eval*)handle;
+ register char *cp;
+ register int len;
+
+ /* no more to do */
+ if(type!=SF_READ || !(cp = ep->argv[0]))
+ {
+ if(type==SF_CLOSING)
+ sfdisc(iop,SF_POPDISC);
+ else if(ep && (type==SF_DPOP || type==SF_FINAL))
+ free((void*)ep);
+ return(0);
+ }
+
+ if(!ep->addspace)
+ {
+ /* get the length of this string */
+ ep->slen = len = strlen(cp);
+ /* move to next string */
+ ep->argv++;
+ }
+ else /* insert space between arguments */
+ {
+ len = 1;
+ cp = " ";
+ }
+ /* insert the new string */
+ sfsetbuf(iop,cp,len);
+ ep->addspace = !ep->addspace;
+ return(1);
+}
+
+/*
+ * This routine returns a stream pointer to a segment of length <size> from
+ * the stream <sp> starting at offset <offset>
+ * The stream can be read with the normal stream operations
+ */
+
+static Sfio_t *subopen(Sfio_t* sp, off_t offset, long size)
+{
+ register struct subfile *disp;
+ if(sfseek(sp,offset,SEEK_SET) <0)
+ return(NIL(Sfio_t*));
+ if(!(disp = (struct subfile*)malloc(sizeof(struct subfile)+IOBSIZE+1)))
+ return(NIL(Sfio_t*));
+ disp->disc = sub_disc;
+ disp->oldsp = sp;
+ disp->offset = offset;
+ disp->size = disp->left = size;
+ sp = sfnew(NIL(Sfio_t*),(char*)(disp+1),IOBSIZE,sh.lim.open_max,SF_READ);
+ sfdisc(sp,&disp->disc);
+ return(sp);
+}
+
+/*
+ * read function for subfile discipline
+ */
+static ssize_t subread(Sfio_t* sp,void* buff,register size_t size,Sfdisc_t* handle)
+{
+ register struct subfile *disp = (struct subfile*)handle;
+ NOT_USED(sp);
+ if(disp->left == 0)
+ return(0);
+ if(size > disp->left)
+ size = disp->left;
+ disp->left -= size;
+ return(sfread(disp->oldsp,buff,size));
+}
+
+/*
+ * exception handler for subfile discipline
+ */
+static int subexcept(Sfio_t* sp,register int mode, void *data, Sfdisc_t* handle)
+{
+ register struct subfile *disp = (struct subfile*)handle;
+ if(mode==SF_CLOSING)
+ {
+ sfdisc(sp,SF_POPDISC);
+ return(0);
+ }
+ else if(disp && (mode==SF_DPOP || mode==SF_FINAL))
+ {
+ free((void*)disp);
+ return(0);
+ }
+#ifdef SF_ATEXIT
+ else if (mode==SF_ATEXIT)
+ {
+ sfdisc(sp, SF_POPDISC);
+ return(0);
+ }
+#endif
+ else if(mode==SF_READ)
+ return(0);
+ return(-1);
+}
+
+#define NROW 15 /* number of rows before going to multi-columns */
+#define LBLSIZ 3 /* size of label field and interfield spacing */
+/*
+ * print a list of arguments in columns
+ */
+void sh_menu(Sfio_t *outfile,int argn,char *argv[])
+{
+ register int i,j;
+ register char **arg;
+ int nrow, ncol=1, ndigits=1;
+ int fldsize, wsize = ed_window();
+ char *cp = nv_getval(nv_scoped(LINES));
+ nrow = (cp?1+2*((int)strtol(cp, (char**)0, 10)/3):NROW);
+ for(i=argn;i >= 10;i /= 10)
+ ndigits++;
+ if(argn < nrow)
+ {
+ nrow = argn;
+ goto skip;
+ }
+ i = 0;
+ for(arg=argv; *arg;arg++)
+ {
+ if((j=strlen(*arg)) > i)
+ i = j;
+ }
+ i += (ndigits+LBLSIZ);
+ if(i < wsize)
+ ncol = wsize/i;
+ if(argn > nrow*ncol)
+ {
+ nrow = 1 + (argn-1)/ncol;
+ }
+ else
+ {
+ ncol = 1 + (argn-1)/nrow;
+ nrow = 1 + (argn-1)/ncol;
+ }
+skip:
+ fldsize = (wsize/ncol)-(ndigits+LBLSIZ);
+ for(i=0;i<nrow;i++)
+ {
+ if(sh.trapnote&SH_SIGSET)
+ return;
+ j = i;
+ while(1)
+ {
+ arg = argv+j;
+ sfprintf(outfile,"%*d) %s",ndigits,j+1,*arg);
+ j += nrow;
+ if(j >= argn)
+ break;
+ sfnputc(outfile,' ',fldsize-strlen(*arg));
+ }
+ sfputc(outfile,'\n');
+ }
+}
+
+#undef read
+/*
+ * shell version of read() for user added builtins
+ */
+ssize_t sh_read(register int fd, void* buff, size_t n)
+{
+ register Sfio_t *sp;
+ if(sp=sh.sftable[fd])
+ return(sfread(sp,buff,n));
+ else
+ return(read(fd,buff,n));
+}
+
+#undef write
+/*
+ * shell version of write() for user added builtins
+ */
+ssize_t sh_write(register int fd, const void* buff, size_t n)
+{
+ register Sfio_t *sp;
+ if(sp=sh.sftable[fd])
+ return(sfwrite(sp,buff,n));
+ else
+ return(write(fd,buff,n));
+}
+
+#undef lseek
+/*
+ * shell version of lseek() for user added builtins
+ */
+off_t sh_seek(register int fd, off_t offset, int whence)
+{
+ register Sfio_t *sp;
+ if((sp=sh.sftable[fd]) && (sfset(sp,0,0)&(SF_READ|SF_WRITE)))
+ return(sfseek(sp,offset,whence));
+ else
+ return(lseek(fd,offset,whence));
+}
+
+#undef dup
+int sh_dup(register int old)
+{
+ register int fd = dup(old);
+ if(fd>=0)
+ {
+ if(sh.fdstatus[old] == IOCLOSE)
+ sh.fdstatus[old] = 0;
+ sh.fdstatus[fd] = (sh.fdstatus[old]&~IOCLEX);
+ if(fdnotify)
+ (*fdnotify)(old,fd);
+ }
+ return(fd);
+}
+
+#undef fcntl
+int sh_fcntl(register int fd, int op, ...)
+{
+ int newfd, arg;
+ va_list ap;
+ va_start(ap, op);
+ arg = va_arg(ap, int) ;
+ va_end(ap);
+ newfd = fcntl(fd,op,arg);
+ if(newfd>=0) switch(op)
+ {
+ case F_DUPFD:
+ if(sh.fdstatus[fd] == IOCLOSE)
+ sh.fdstatus[fd] = 0;
+ sh.fdstatus[newfd] = (sh.fdstatus[fd]&~IOCLEX);
+ if(fdnotify)
+ (*fdnotify)(fd,newfd);
+ break;
+ case F_SETFD:
+ if(sh.fdstatus[fd] == IOCLOSE)
+ sh.fdstatus[fd] = 0;
+ if(arg&FD_CLOEXEC)
+ sh.fdstatus[fd] |= IOCLEX;
+ else
+ sh.fdstatus[fd] &= ~IOCLEX;
+ }
+ return(newfd);
+}
+
+#undef umask
+mode_t sh_umask(mode_t m)
+{
+ sh.mask = m;
+ return(umask(m));
+}
+
+/*
+ * give file descriptor <fd> and <mode>, return an iostream pointer
+ * <mode> must be SF_READ or SF_WRITE
+ * <fd> must be a non-negative number ofr SH_IOCOPROCESS or SH_IOHISTFILE.
+ * returns NULL on failure and may set errno.
+ */
+
+Sfio_t *sh_iogetiop(int fd, int mode)
+{
+ int n;
+ Sfio_t *iop=0;
+ if(mode!=SF_READ && mode!=SF_WRITE)
+ {
+ errno = EINVAL;
+ return(iop);
+ }
+ switch(fd)
+ {
+ case SH_IOHISTFILE:
+ if(!sh_histinit())
+ return(iop);
+ fd = sffileno(sh.hist_ptr->histfp);
+ break;
+ case SH_IOCOPROCESS:
+ if(mode==SF_WRITE)
+ fd = sh.coutpipe;
+ else
+ fd = sh.cpipe[0];
+ break;
+ default:
+ if(fd<0 || fd >= sh.lim.open_max)
+ fd = -1;
+ }
+ if(fd<0)
+ {
+ errno = EBADF;
+ return(iop);
+ }
+ if(!(n=sh.fdstatus[fd]))
+ n = sh_iocheckfd(fd);
+ if(mode==SF_WRITE && !(n&IOWRITE))
+ return(iop);
+ if(mode==SF_READ && !(n&IOREAD))
+ return(iop);
+ if(!(iop = sh.sftable[fd]))
+ iop=sh_iostream(fd);
+ return(iop);
+}
+
+typedef int (*Notify_f)(int,int);
+
+Notify_f sh_fdnotify(Notify_f notify)
+{
+ Notify_f old;
+ old = fdnotify;
+ fdnotify = notify;
+ return(old);
+}
+
+Sfio_t *sh_fd2sfio(int fd)
+{
+ register int status;
+ Sfio_t *sp = sh.sftable[fd];
+ if(!sp && (status = sh_iocheckfd(fd))!=IOCLOSE)
+ {
+ register int flags=0;
+ if(status&IOREAD)
+ flags |= SF_READ;
+ if(status&IOWRITE)
+ flags |= SF_WRITE;
+ sp = sfnew(NULL, NULL, -1, fd,flags);
+ sh.sftable[fd] = sp;
+ }
+ return(sp);
+}
+
+Sfio_t *sh_pathopen(const char *cp)
+{
+ int n;
+#ifdef PATH_BFPATH
+ if((n=path_open(cp,path_get(cp))) < 0)
+ n = path_open(cp,(Pathcomp_t*)0);
+#else
+ if((n=path_open(cp,path_get(cp))) < 0)
+ n = path_open(cp,"");
+#endif
+ if(n < 0)
+ errormsg(SH_DICT,ERROR_system(1),e_open,cp);
+ return(sh_iostream(n));
+}