summaryrefslogtreecommitdiff
path: root/usr/src/lib/libshell/common/edit/history.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libshell/common/edit/history.c')
-rw-r--r--usr/src/lib/libshell/common/edit/history.c1109
1 files changed, 1109 insertions, 0 deletions
diff --git a/usr/src/lib/libshell/common/edit/history.c b/usr/src/lib/libshell/common/edit/history.c
new file mode 100644
index 0000000000..f98140fc0f
--- /dev/null
+++ b/usr/src/lib/libshell/common/edit/history.c
@@ -0,0 +1,1109 @@
+/***********************************************************************
+* *
+* 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
+/*
+ * History file manipulation routines
+ *
+ * David Korn
+ * AT&T Labs
+ *
+ */
+
+/*
+ * Each command in the history file starts on an even byte is null terminated.
+ * The first byte must contain the special character HIST_UNDO and the second
+ * byte is the version number. The sequence HIST_UNDO 0, following a command,
+ * nullifies the previous command. A six byte sequence starting with
+ * HIST_CMDNO is used to store the command number so that it is not necessary
+ * to read the file from beginning to end to get to the last block of
+ * commands. This format of this sequence is different in version 1
+ * then in version 0. Version 1 allows commands to use the full 8 bit
+ * character set. It can understand version 0 format files.
+ */
+
+
+#define HIST_MAX (sizeof(int)*HIST_BSIZE)
+#define HIST_BIG (0100000-1024) /* 1K less than maximum short */
+#define HIST_LINE 32 /* typical length for history line */
+#define HIST_MARKSZ 6
+#define HIST_RECENT 600
+#define HIST_UNDO 0201 /* invalidate previous command */
+#define HIST_CMDNO 0202 /* next 3 bytes give command number */
+#define HIST_BSIZE 4096 /* size of history file buffer */
+#define HIST_DFLT 512 /* default size of history list */
+
+#define _HIST_PRIVATE \
+ off_t histcnt; /* offset into history file */\
+ off_t histmarker; /* offset of last command marker */ \
+ int histflush; /* set if flushed outside of hflush() */\
+ int histmask; /* power of two mask for histcnt */ \
+ char histbuff[HIST_BSIZE+1]; /* history file buffer */ \
+ int histwfail; \
+ off_t histcmds[2]; /* offset for recent commands, must be last */
+
+#define hist_ind(hp,c) ((int)((c)&(hp)->histmask))
+
+#include <ast.h>
+#include <sfio.h>
+#include "FEATURE/time"
+#include <error.h>
+#include <ctype.h>
+#include <ls.h>
+#if KSHELL
+# include "defs.h"
+# include "variables.h"
+# include "path.h"
+# include "builtins.h"
+# include "io.h"
+#endif /* KSHELL */
+#include "history.h"
+
+#if !KSHELL
+# define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x)))
+# define NIL(type) ((type)0)
+# define path_relative(x) (x)
+# ifdef __STDC__
+# define nv_getval(s) getenv(#s)
+# else
+# define nv_getval(s) getenv("s")
+# endif /* __STDC__ */
+# define e_unknown "unknown"
+# define sh_translate(x) (x)
+ char login_sh = 0;
+ char hist_fname[] = "/.history";
+#endif /* KSHELL */
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif /* O_BINARY */
+
+int _Hist = 0;
+static void hist_marker(char*,long);
+static void hist_trim(History_t*, int);
+static int hist_nearend(History_t*,Sfio_t*, off_t);
+static int hist_check(int);
+static int hist_clean(int);
+#ifdef SF_BUFCONST
+ static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
+ static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
+#else
+ static int hist_write(Sfio_t*, const void*, int, Sfdisc_t*);
+ static int hist_exceptf(Sfio_t*, int, Sfdisc_t*);
+#endif
+
+
+static int histinit;
+static mode_t histmode;
+static History_t *wasopen;
+static History_t *hist_ptr;
+
+#if SHOPT_ACCTFILE
+ static int acctfd;
+ static char *logname;
+# include <pwd.h>
+
+ int acctinit(void)
+ {
+ register char *cp, *acctfile;
+ Namval_t *np = nv_search("ACCTFILE",sh.var_tree,0);
+
+ if(!np || !(acctfile=nv_getval(np)))
+ return(0);
+ if(!(cp = getlogin()))
+ {
+ struct passwd *userinfo = getpwuid(getuid());
+ if(userinfo)
+ cp = userinfo->pw_name;
+ else
+ cp = "unknown";
+ }
+ logname = strdup(cp);
+
+ if((acctfd=sh_open(acctfile,
+ O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 &&
+ (unsigned)acctfd < 10)
+ {
+ int n;
+ if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0)
+ {
+ close(acctfd);
+ acctfd = n;
+ }
+ }
+ if(acctfd < 0)
+ {
+ acctfd = 0;
+ return(0);
+ }
+ if(strmatch(acctfile,e_devfdNN))
+ {
+ char newfile[16];
+ sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd);
+ nv_putval(np,newfile,NV_RDONLY);
+ }
+ else
+ fcntl(acctfd,F_SETFD,FD_CLOEXEC);
+ return(1);
+ }
+#endif /* SHOPT_ACCTFILE */
+
+static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
+static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
+
+static void hist_touch(void *handle)
+{
+ touch((char*)handle, (time_t)0, (time_t)0, 0);
+}
+
+/*
+ * open the history file
+ * if HISTNAME is not given and userid==0 then no history file.
+ * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is
+ * cleaned up.
+ * hist_open() returns 1, if history file is open
+ */
+int sh_histinit(void)
+{
+ register int fd;
+ register History_t *hp;
+ register char *histname;
+ char *fname=0;
+ int histmask, maxlines, hist_start=0;
+ register char *cp;
+ register off_t hsize = 0;
+
+ if(sh.hist_ptr=hist_ptr)
+ return(1);
+ if(!(histname = nv_getval(HISTFILE)))
+ {
+ int offset = staktell();
+ if(cp=nv_getval(HOME))
+ stakputs(cp);
+ stakputs(hist_fname);
+ stakputc(0);
+ stakseek(offset);
+ histname = stakptr(offset);
+ }
+#ifdef future
+ if(hp=wasopen)
+ {
+ /* reuse history file if same name */
+ wasopen = 0;
+ sh.hist_ptr = hist_ptr = hp;
+ if(strcmp(histname,hp->histname)==0)
+ return(1);
+ else
+ hist_free();
+ }
+#endif
+retry:
+ cp = path_relative(histname);
+ if(!histinit)
+ histmode = S_IRUSR|S_IWUSR;
+ if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0)
+ {
+ hsize=lseek(fd,(off_t)0,SEEK_END);
+ }
+ if((unsigned)fd <=2)
+ {
+ int n;
+ if((n=fcntl(fd,F_DUPFD,10))>=0)
+ {
+ close(fd);
+ fd=n;
+ }
+ }
+ /* make sure that file has history file format */
+ if(hsize && hist_check(fd))
+ {
+ close(fd);
+ hsize = 0;
+ if(unlink(cp)>=0)
+ goto retry;
+ fd = -1;
+ }
+ if(fd < 0)
+ {
+#if KSHELL
+ /* don't allow root a history_file in /tmp */
+ if(sh.userid)
+#endif /* KSHELL */
+ {
+ if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*))))
+ return(0);
+ fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
+ }
+ }
+ if(fd<0)
+ return(0);
+ /* set the file to close-on-exec */
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ if(cp=nv_getval(HISTSIZE))
+ maxlines = (unsigned)strtol(cp, (char**)0, 10);
+ else
+ maxlines = HIST_DFLT;
+ for(histmask=16;histmask <= maxlines; histmask <<=1 );
+ if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t))))
+ {
+ close(fd);
+ return(0);
+ }
+ sh.hist_ptr = hist_ptr = hp;
+ hp->histsize = maxlines;
+ hp->histmask = histmask;
+ hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE);
+ memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1));
+ hp->histind = 1;
+ hp->histcmds[1] = 2;
+ hp->histcnt = 2;
+ hp->histname = strdup(histname);
+ hp->histdisc = hist_disc;
+ if(hsize==0)
+ {
+ /* put special characters at front of file */
+ sfwrite(hp->histfp,(char*)hist_stamp,2);
+ sfsync(hp->histfp);
+ }
+ /* initialize history list */
+ else
+ {
+ int first,last;
+ off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE;
+ hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size);
+ hist_eof(hp); /* this sets histind to last command */
+ if((hist_start = (last=(int)hp->histind)-maxlines) <=0)
+ hist_start = 1;
+ mark = hp->histmarker;
+ while(first > hist_start)
+ {
+ size += size;
+ first = hist_nearend(hp,hp->histfp,hsize-size);
+ hp->histind = first;
+ }
+ histinit = hist_start;
+ hist_eof(hp);
+ if(!histinit)
+ {
+ sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET);
+ hp->histind = last;
+ hp->histmarker = mark;
+ }
+ histinit = 0;
+ }
+ if(fname)
+ {
+ unlink(fname);
+ free((void*)fname);
+ }
+ if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX)
+ {
+#ifdef DEBUG
+ sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize);
+ sfsync(sfstderr);
+#endif /* DEBUG */
+ hist_trim(hp,(int)hp->histind-maxlines);
+ }
+ sfdisc(hp->histfp,&hp->histdisc);
+#if KSHELL
+ (HISTCUR)->nvalue.lp = (&hp->histind);
+#endif /* KSHELL */
+ sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname);
+#if SHOPT_ACCTFILE
+ if(sh_isstate(SH_INTERACTIVE))
+ acctinit();
+#endif /* SHOPT_ACCTFILE */
+ return(1);
+}
+
+/*
+ * close the history file and free the space
+ */
+
+void hist_close(register History_t *hp)
+{
+ sfclose(hp->histfp);
+ free((char*)hp);
+ hist_ptr = 0;
+ sh.hist_ptr = 0;
+#if SHOPT_ACCTFILE
+ if(acctfd)
+ {
+ close(acctfd);
+ acctfd = 0;
+ }
+#endif /* SHOPT_ACCTFILE */
+}
+
+/*
+ * check history file format to see if it begins with special byte
+ */
+static int hist_check(register int fd)
+{
+ unsigned char magic[2];
+ lseek(fd,(off_t)0,SEEK_SET);
+ if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO))
+ return(1);
+ return(0);
+}
+
+/*
+ * clean out history file OK if not modified in HIST_RECENT seconds
+ */
+static int hist_clean(int fd)
+{
+ struct stat statb;
+ return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT);
+}
+
+/*
+ * Copy the last <n> commands to a new file and make this the history file
+ */
+
+static void hist_trim(History_t *hp, int n)
+{
+ register char *cp;
+ register int incmd=1, c=0;
+ register History_t *hist_new, *hist_old = hp;
+ char *buff, *endbuff, *tmpname=0;
+ off_t oldp,newp;
+ struct stat statb;
+ unlink(hist_old->histname);
+ if(access(hist_old->histname,F_OK) >= 0)
+ {
+ /* The unlink can fail on windows 95 */
+ int fd;
+ char *last, *name=hist_old->histname;
+ close(sffileno(hist_old->histfp));
+ tmpname = (char*)malloc(strlen(name)+14);
+ if(last = strrchr(name,'/'))
+ {
+ *last = 0;
+ pathtmp(tmpname,name,"hist",NIL(int*));
+ *last = '/';
+ }
+ else
+ pathtmp(tmpname,".","hist",NIL(int*));
+ if(rename(name,tmpname) < 0)
+ tmpname = name;
+ fd = open(tmpname,O_RDONLY);
+ sfsetfd(hist_old->histfp,fd);
+ if(tmpname==name)
+ tmpname = 0;
+ }
+ hp = hist_ptr = 0;
+ if(fstat(sffileno(hist_old->histfp),&statb)>=0)
+ {
+ histinit = 1;
+ histmode = statb.st_mode;
+ }
+ if(!sh_histinit())
+ {
+ /* use the old history file */
+ hist_ptr = hist_old;
+ return;
+ }
+ hist_new = hist_ptr;
+ hist_ptr = hist_old;
+ if(--n < 0)
+ n = 0;
+ newp = hist_seek(hist_old,++n);
+ while(1)
+ {
+ if(!incmd)
+ {
+ c = hist_ind(hist_new,++hist_new->histind);
+ hist_new->histcmds[c] = hist_new->histcnt;
+ if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
+ {
+ char locbuff[HIST_MARKSZ];
+ hist_marker(locbuff,hist_new->histind);
+ sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
+ hist_new->histcnt += HIST_MARKSZ;
+ hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
+ }
+ oldp = newp;
+ newp = hist_seek(hist_old,++n);
+ if(newp <=oldp)
+ break;
+ }
+ if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
+ break;
+ *(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0;
+ /* copy to null byte */
+ incmd = 0;
+ while(*cp++);
+ if(cp > endbuff)
+ incmd = 1;
+ else if(*cp==0)
+ cp++;
+ if(cp > endbuff)
+ cp = endbuff;
+ c = cp-buff;
+ hist_new->histcnt += c;
+ sfwrite(hist_new->histfp,buff,c);
+ }
+ hist_ptr = hist_new;
+ hist_cancel(hist_ptr);
+ sfclose(hist_old->histfp);
+ if(tmpname)
+ {
+ unlink(tmpname);
+ free(tmpname);
+ }
+ free((char*)hist_old);
+}
+
+/*
+ * position history file at size and find next command number
+ */
+static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size)
+{
+ register unsigned char *cp, *endbuff;
+ register int n, incmd=1;
+ unsigned char *buff, marker[4];
+ if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
+ goto begin;
+ /* skip to marker command and return the number */
+ /* numbering commands occur after a null and begin with HIST_CMDNO */
+ while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR))
+ {
+ n = sfvalue(iop);
+ *(endbuff=cp+n) = 0;
+ while(1)
+ {
+ /* check for marker */
+ if(!incmd && *cp++==HIST_CMDNO && *cp==0)
+ {
+ n = cp+1 - buff;
+ incmd = -1;
+ break;
+ }
+ incmd = 0;
+ while(*cp++);
+ if(cp>endbuff)
+ {
+ incmd = 1;
+ break;
+ }
+ if(*cp==0 && ++cp>endbuff)
+ break;
+ }
+ size += n;
+ sfread(iop,(char*)buff,n);
+ if(incmd < 0)
+ {
+ if((n=sfread(iop,(char*)marker,4))==4)
+ {
+ n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
+ if(n < size/2)
+ {
+ hp->histmarker = hp->histcnt = size+4;
+ return(n);
+ }
+ n=4;
+ }
+ if(n >0)
+ size += n;
+ incmd = 0;
+ }
+ }
+begin:
+ sfseek(iop,(off_t)2,SEEK_SET);
+ hp->histmarker = hp->histcnt = 2L;
+ return(1);
+}
+
+/*
+ * This routine reads the history file from the present position
+ * to the end-of-file and puts the information in the in-core
+ * history table
+ * Note that HIST_CMDNO is only recognized at the beginning of a command
+ * and that HIST_UNDO as the first character of a command is skipped
+ * unless it is followed by 0. If followed by 0 then it cancels
+ * the previous command.
+ */
+
+void hist_eof(register History_t *hp)
+{
+ register char *cp,*first,*endbuff;
+ register int incmd = 0;
+ register off_t count = hp->histcnt;
+ int n,skip=0;
+ sfseek(hp->histfp,count,SEEK_SET);
+ while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
+ {
+ n = sfvalue(hp->histfp);
+ *(endbuff = cp+n) = 0;
+ first = cp += skip;
+ while(1)
+ {
+ while(!incmd)
+ {
+ if(cp>first)
+ {
+ count += (cp-first);
+ n = hist_ind(hp, ++hp->histind);
+#ifdef future
+ if(count==hp->histcmds[n])
+ {
+ sfprintf(sfstderr,"count match n=%d\n",n);
+ if(histinit)
+ {
+ histinit = 0;
+ return;
+ }
+ }
+ else if(n>=histinit)
+#endif
+ hp->histcmds[n] = count;
+ first = cp;
+ }
+ switch(*((unsigned char*)(cp++)))
+ {
+ case HIST_CMDNO:
+ if(*cp==0)
+ {
+ hp->histmarker=count+2;
+ cp += (HIST_MARKSZ-1);
+ hp->histind--;
+#ifdef future
+ if(cp <= endbuff)
+ {
+ unsigned char *marker = (unsigned char*)(cp-4);
+ int n = ((marker[0]<<16)
+|(marker[1]<<8)|marker[2]);
+ if((n<count/2) && n != (hp->histind+1))
+ errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n);
+ }
+#endif
+ }
+ break;
+ case HIST_UNDO:
+ if(*cp==0)
+ {
+ cp+=1;
+ hp->histind-=2;
+ }
+ break;
+ default:
+ cp--;
+ incmd = 1;
+ }
+ if(cp > endbuff)
+ {
+ cp++;
+ goto refill;
+ }
+ }
+ first = cp;
+ while(*cp++);
+ if(cp > endbuff)
+ break;
+ incmd = 0;
+ while(*cp==0)
+ {
+ if(++cp > endbuff)
+ goto refill;
+ }
+ }
+ refill:
+ count += (--cp-first);
+ skip = (cp-endbuff);
+ if(!incmd && !skip)
+ hp->histcmds[hist_ind(hp,++hp->histind)] = count;
+ }
+ hp->histcnt = count;
+}
+
+/*
+ * This routine will cause the previous command to be cancelled
+ */
+
+void hist_cancel(register History_t *hp)
+{
+ register int c;
+ if(!hp)
+ return;
+ sfputc(hp->histfp,HIST_UNDO);
+ sfputc(hp->histfp,0);
+ sfsync(hp->histfp);
+ hp->histcnt += 2;
+ c = hist_ind(hp,--hp->histind);
+ hp->histcmds[c] = hp->histcnt;
+}
+
+/*
+ * flush the current history command
+ */
+
+void hist_flush(register History_t *hp)
+{
+ register char *buff;
+ if(hp)
+ {
+ if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
+ {
+ hp->histflush = sfvalue(hp->histfp)+1;
+ sfwrite(hp->histfp,buff,0);
+ }
+ else
+ hp->histflush=0;
+ if(sfsync(hp->histfp)<0)
+ {
+ hist_close(hp);
+ if(!sh_histinit())
+ sh_offoption(SH_HISTORY);
+ }
+ hp->histflush = 0;
+ }
+}
+
+/*
+ * This is the write discipline for the history file
+ * When called from hist_flush(), trailing newlines are deleted and
+ * a zero byte. Line sequencing is added as required
+ */
+
+#ifdef SF_BUFCONST
+static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
+#else
+static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
+#endif
+{
+ register History_t *hp = (History_t*)handle;
+ register char *bufptr = ((char*)buff)+insize;
+ register int c,size = insize;
+ register off_t cur;
+ int saved=0;
+ char saveptr[HIST_MARKSZ];
+ if(!hp->histflush)
+ return(write(sffileno(iop),(char*)buff,size));
+ if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
+ {
+ errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
+ return(-1);
+ }
+ hp->histcnt = cur;
+ /* remove whitespace from end of commands */
+ while(--bufptr >= (char*)buff)
+ {
+ c= *bufptr;
+ if(!isspace(c))
+ {
+ if(c=='\\' && *(bufptr+1)!='\n')
+ bufptr++;
+ break;
+ }
+ }
+ /* don't count empty lines */
+ if(++bufptr <= (char*)buff)
+ return(insize);
+ *bufptr++ = '\n';
+ *bufptr++ = 0;
+ size = bufptr - (char*)buff;
+#if SHOPT_ACCTFILE
+ if(acctfd)
+ {
+ int timechars, offset;
+ offset = staktell();
+ stakputs(buff);
+ stakseek(staktell() - 1);
+ timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
+ lseek(acctfd, (off_t)0, SEEK_END);
+ write(acctfd, stakptr(offset), size - 2 + timechars);
+ stakseek(offset);
+
+ }
+#endif /* SHOPT_ACCTFILE */
+ if(size&01)
+ {
+ size++;
+ *bufptr++ = 0;
+ }
+ hp->histcnt += size;
+ c = hist_ind(hp,++hp->histind);
+ hp->histcmds[c] = hp->histcnt;
+ if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
+ {
+ memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
+ saved=1;
+ hp->histcnt += HIST_MARKSZ;
+ hist_marker(bufptr,hp->histind);
+ hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
+ size += HIST_MARKSZ;
+ }
+ errno = 0;
+ size = write(sffileno(iop),(char*)buff,size);
+ if(saved)
+ memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
+ if(size>=0)
+ {
+ hp->histwfail = 0;
+ return(insize);
+ }
+ return(-1);
+}
+
+/*
+ * Put history sequence number <n> into buffer <buff>
+ * The buffer must be large enough to hold HIST_MARKSZ chars
+ */
+
+static void hist_marker(register char *buff,register long cmdno)
+{
+ *buff++ = HIST_CMDNO;
+ *buff++ = 0;
+ *buff++ = (cmdno>>16);
+ *buff++ = (cmdno>>8);
+ *buff++ = cmdno;
+ *buff++ = 0;
+}
+
+/*
+ * return byte offset in history file for command <n>
+ */
+off_t hist_tell(register History_t *hp, int n)
+{
+ return(hp->histcmds[hist_ind(hp,n)]);
+}
+
+/*
+ * seek to the position of command <n>
+ */
+off_t hist_seek(register History_t *hp, int n)
+{
+ return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
+}
+
+/*
+ * write the command starting at offset <offset> onto file <outfile>.
+ * if character <last> appears before newline it is deleted
+ * each new-line character is replaced with string <nl>.
+ */
+
+void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
+{
+ register int oldc=0;
+ register int c;
+ if(offset<0 || !hp)
+ {
+ sfputr(outfile,sh_translate(e_unknown),'\n');
+ return;
+ }
+ sfseek(hp->histfp,offset,SEEK_SET);
+ while((c = sfgetc(hp->histfp)) != EOF)
+ {
+ if(c && oldc=='\n')
+ sfputr(outfile,nl,-1);
+ else if(last && (c==0 || (c=='\n' && oldc==last)))
+ return;
+ else if(oldc)
+ sfputc(outfile,oldc);
+ oldc = c;
+ if(c==0)
+ return;
+ }
+ return;
+}
+
+/*
+ * find index for last line with given string
+ * If flag==0 then line must begin with string
+ * direction < 1 for backwards search
+*/
+
+Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
+{
+ register int index2;
+ off_t offset;
+ int *coffset=0;
+ Histloc_t location;
+ location.hist_command = -1;
+ location.hist_char = 0;
+ location.hist_line = 0;
+ if(!hp)
+ return(location);
+ /* leading ^ means beginning of line unless escaped */
+ if(flag)
+ {
+ index2 = *string;
+ if(index2=='\\')
+ string++;
+ else if(index2=='^')
+ {
+ flag=0;
+ string++;
+ }
+ }
+ if(flag)
+ coffset = &location.hist_char;
+ index2 = (int)hp->histind;
+ if(direction<0)
+ {
+ index2 -= hp->histsize;
+ if(index2<1)
+ index2 = 1;
+ if(index1 <= index2)
+ return(location);
+ }
+ else if(index1 >= index2)
+ return(location);
+ while(index1!=index2)
+ {
+ direction>0?++index1:--index1;
+ offset = hist_tell(hp,index1);
+ if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
+ {
+ location.hist_command = index1;
+ return(location);
+ }
+#if KSHELL
+ /* allow a search to be aborted */
+ if(sh.trapnote&SH_SIGSET)
+ break;
+#endif /* KSHELL */
+ }
+ return(location);
+}
+
+/*
+ * search for <string> in history file starting at location <offset>
+ * If coffset==0 then line must begin with string
+ * returns the line number of the match if successful, otherwise -1
+ */
+
+int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
+{
+ register unsigned char *first, *cp;
+ register int m,n,c=1,line=0;
+#if SHOPT_MULTIBYTE
+ mbinit();
+#endif /* SHOPT_MULTIBYTE */
+ sfseek(hp->histfp,offset,SEEK_SET);
+ if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
+ return(-1);
+ m = sfvalue(hp->histfp);
+ n = strlen(string);
+ while(m > n)
+ {
+ if(*cp==*string && memcmp(cp,string,n)==0)
+ {
+ if(coffset)
+ *coffset = (cp-first);
+ return(line);
+ }
+ if(!coffset)
+ break;
+ if(*cp=='\n')
+ line++;
+#if SHOPT_MULTIBYTE
+ if((c=mbsize(cp)) < 0)
+ c = 1;
+#endif /* SHOPT_MULTIBYTE */
+ cp += c;
+ m -= c;
+ }
+ return(-1);
+}
+
+
+#if SHOPT_ESH || SHOPT_VSH
+/*
+ * copy command <command> from history file to s1
+ * at most <size> characters copied
+ * if s1==0 the number of lines for the command is returned
+ * line=linenumber for emacs copy and only this line of command will be copied
+ * line < 0 for full command copy
+ * -1 returned if there is no history file
+ */
+
+int hist_copy(char *s1,int size,int command,int line)
+{
+ register int c;
+ register History_t *hp = sh_getinterp()->hist_ptr;
+ register int count = 0;
+ register char *s1max = s1+size;
+ if(!hp)
+ return(-1);
+ hist_seek(hp,command);
+ while ((c = sfgetc(hp->histfp)) && c!=EOF)
+ {
+ if(c=='\n')
+ {
+ if(count++ ==line)
+ break;
+ else if(line >= 0)
+ continue;
+ }
+ if(s1 && (line<0 || line==count))
+ {
+ if(s1 >= s1max)
+ {
+ *--s1 = 0;
+ break;
+ }
+ *s1++ = c;
+ }
+
+ }
+ sfseek(hp->histfp,(off_t)0,SEEK_END);
+ if(s1==0)
+ return(count);
+ if(count && (c= *(s1-1)) == '\n')
+ s1--;
+ *s1 = '\0';
+ return(count);
+}
+
+/*
+ * return word number <word> from command number <command>
+ */
+
+char *hist_word(char *string,int size,int word)
+{
+ register int c;
+ register char *s1 = string;
+ register unsigned char *cp = (unsigned char*)s1;
+ register int flag = 0;
+ History_t *hp = hist_ptr;
+ if(!hp)
+#if KSHELL
+ {
+ strncpy(string,sh.lastarg,size);
+ return(string);
+ }
+#else
+ return(NIL(char*));
+#endif /* KSHELL */
+ hist_copy(string,size,(int)hp->histind-1,-1);
+ for(;c = *cp;cp++)
+ {
+ c = isspace(c);
+ if(c && flag)
+ {
+ *cp = 0;
+ if(--word==0)
+ break;
+ flag = 0;
+ }
+ else if(c==0 && flag==0)
+ {
+ s1 = (char*)cp;
+ flag++;
+ }
+ }
+ *cp = 0;
+ if(s1 != string)
+ strcpy(string,s1);
+ return(string);
+}
+
+#endif /* SHOPT_ESH */
+
+#if SHOPT_ESH
+/*
+ * given the current command and line number,
+ * and number of lines back or foward,
+ * compute the new command and line number.
+ */
+
+Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
+{
+ Histloc_t next;
+ line += lines;
+ if(!hp)
+ {
+ command = -1;
+ goto done;
+ }
+ if(lines > 0)
+ {
+ register int count;
+ while(command <= hp->histind)
+ {
+ count = hist_copy(NIL(char*),0, command,-1);
+ if(count > line)
+ goto done;
+ line -= count;
+ command++;
+ }
+ }
+ else
+ {
+ register int least = (int)hp->histind-hp->histsize;
+ while(1)
+ {
+ if(line >=0)
+ goto done;
+ if(--command < least)
+ break;
+ line += hist_copy(NIL(char*),0, command,-1);
+ }
+ command = -1;
+ }
+done:
+ next.hist_line = line;
+ next.hist_command = command;
+ return(next);
+}
+#endif /* SHOPT_ESH */
+
+
+/*
+ * Handle history file exceptions
+ */
+#ifdef SF_BUFCONST
+static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
+#else
+static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
+#endif
+{
+ register int newfd,oldfd;
+ History_t *hp = (History_t*)handle;
+ if(type==SF_WRITE)
+ {
+ if(errno==ENOSPC || hp->histwfail++ >= 10)
+ return(0);
+ /* write failure could be NFS problem, try to re-open */
+ close(oldfd=sffileno(fp));
+ if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
+ {
+ if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
+ return(-1);
+ fcntl(oldfd,F_SETFD,FD_CLOEXEC);
+ close(newfd);
+ if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
+ {
+ register int index = hp->histind;
+ lseek(oldfd,(off_t)2,SEEK_SET);
+ hp->histcnt = 2;
+ hp->histind = 1;
+ hp->histcmds[1] = 2;
+ hist_eof(hp);
+ hp->histmarker = hp->histcnt;
+ hp->histind = index;
+ }
+ return(1);
+ }
+ errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
+ return(-1);
+ }
+ return(0);
+}