/***********************************************************************
*                                                                      *
*               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 <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
/*
 *   History file manipulation routines
 *
 *   David Korn
 *   AT&T Labs
 *
 */

/*
 * Each command in the history file starts on an even byte and 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
 * than 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 */

#if SHOPT_AUDIT
#   define _HIST_AUDIT	Sfio_t	*auditfp; \
			char	*tty; \
			int	auditmask; 
#else
#   define _HIST_AUDIT 
#endif

#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; \
	_HIST_AUDIT \
	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	<ls.h>
#include	"defs.h"
#include	"variables.h"
#include	"path.h"
#include	"builtins.h"
#include	"io.h"
#include	"history.h"

#ifndef O_BINARY
#   define O_BINARY	0
#endif /* O_BINARY */

int	_Hist = 0;
static void	hist_marker(char*,long);
static History_t* 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);
static ssize_t	hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
static int	hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);

static int	histinit;
static mode_t	histmode;
static History_t *hist_ptr;

#if SHOPT_ACCTFILE
    static int	acctfd;
    static char *logname;
#   include <pwd.h>
    
    static int  acctinit(History_t *hp)
    {
	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 = sh_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 = sh_fcntl(acctfd, F_dupfd_cloexec, 10)) >= 0)
		{
			sh_close(acctfd);
			acctfd = n;
		}
	}
	if(acctfd < 0)
	{
		acctfd = 0;
		return(0);
	}
	if(sh_isdevfd(acctfile))
	{
		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 */

#if SHOPT_AUDIT
static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len)
{
	char	*cp, *last;
	int	id1, id2, r=0, n, fd;
	if((fd=open(name, O_RDONLY,O_cloexec)) < 0)
		return(0);
	if((n = read(fd, logbuf,len-1)) < 0)
		goto done;
	while(logbuf[n-1]=='\n')
		n--;
	logbuf[n] = 0;
	if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' ')))
		goto done;
	*cp = 0;
	do
	{
		cp++;
		id1 = id2 = strtol(cp,&last,10);
		if(*last=='-')
			id1 = strtol(last+1,&last,10);
		if(sh.euserid >=id1 && sh.euserid <= id2)
			r |= 1;
		if(sh.userid >=id1 && sh.userid <= id2)
			r |= 2;
		cp = last;
	}
	while(*cp==';' ||  *cp==' ');
done:
	sh_close(fd);
	return(r);
	
}
#endif /* SHOPT_AUDIT */

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);
	}
retry:
	cp = path_relative(histname);
	if(!histinit)
		histmode = S_IRUSR|S_IWUSR;
	if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT|O_cloexec,histmode))>=0)
	{
		hsize=lseek(fd,(off_t)0,SEEK_END);
	}
	if((unsigned)fd < 10)
	{
		int n;
		if((n=sh_fcntl(fd,F_dupfd_cloexec,10))>=0)
		{
			sh_close(fd);
			fd=n;
		}
	}
	/* make sure that file has history file format */
	if(hsize && hist_check(fd))
	{
		sh_close(fd);
		hsize = 0;
		if(unlink(cp)>=0)
			goto retry;
		fd = -1;
	}
	if(fd < 0)
	{
		/* don't allow root a history_file in /tmp */
		if(sh.userid)
		{
			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|O_cloexec);
		}
	}
	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 );
	hp = new_of(History_t,(--histmask)*sizeof(off_t));
	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 = sh_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);
		histinit = 1;
		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",sh.current_pid,hsize);
		sfsync(sfstderr);
#endif /* DEBUG */
		hp = hist_trim(hp,(int)hp->histind-maxlines);
	}
	sfdisc(hp->histfp,&hp->histdisc);
	(HISTCUR)->nvalue.lp = (&hp->histind);
	sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname);
#if SHOPT_ACCTFILE
	if(sh_isstate(SH_INTERACTIVE))
		acctinit(hp);
#endif /* SHOPT_ACCTFILE */
#if SHOPT_AUDIT
	{
		char buff[SF_BUFSIZE];
		hp->auditfp = 0;
		if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff))))
		{
			if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT|O_cloexec,S_IRUSR|S_IWUSR))>=0 && fd < 10)
			{
				int n;
				if((n = sh_fcntl(fd,F_dupfd_cloexec, 10)) >= 0)
				{
					sh_close(fd);
					fd = n;
				}
			}
			if(fd>=0)
			{
				fcntl(fd,F_SETFD,FD_CLOEXEC);
				hp->tty = sh_strdup(isatty(2)?ttyname(2):"notty");
				hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE);
			}
		}
	}
#endif
	return(1);
}

/*
 * close the history file and free the space
 */

void hist_close(register History_t *hp)
{
	sfclose(hp->histfp);
#if SHOPT_AUDIT
	if(hp->auditfp)
	{
		if(hp->tty)
			free((void*)hp->tty);
		sfclose(hp->auditfp);
	}
#endif /* SHOPT_AUDIT */
	free((char*)hp);
	hist_ptr = 0;
	sh.hist_ptr = 0;
#if SHOPT_ACCTFILE
	if(acctfd)
	{
		sh_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 History_t* 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;
		sh_close(sffileno(hist_old->histfp));
		tmpname = (char*)sh_malloc(strlen(name)+14);
		if(last = strrchr(name,'/'))
		{
			*last = 0;
			pathtmp(tmpname,name,"hist",NIL(int*));
			*last = '/';
		}
		else
			pathtmp(tmpname,e_dot,"hist",NIL(int*));
		if(rename(name,tmpname) < 0)
		{
			free(tmpname);
			tmpname = name;
		}
		fd = open(tmpname,O_RDONLY|O_cloexec);
		sfsetfd(hist_old->histfp,fd);
		if(tmpname==name)
			tmpname = 0;
	}
	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 */
		return hist_ptr = hist_old;
	}
	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_cancel(hist_new);
	sfclose(hist_old->histfp);
	if(tmpname)
	{
		unlink(tmpname);
		free(tmpname);
	}
	free((char*)hist_old);
	return hist_ptr = hist_new;
}

/*
 * 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 oldind,n,skip=0;
	off_t last = sfseek(hp->histfp,(off_t)0,SEEK_END);
	if(last < count)
	{
		last = -1;
		count = 2+HIST_MARKSZ;
		oldind = hp->histind;
		if((hp->histind -= hp->histsize) < 0)
			hp->histind = 1;
	}
again:
	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);
						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--;
							if(!histinit && (cp <= endbuff))
							{
								unsigned char *marker = (unsigned char*)(cp-4);
								hp->histind = ((marker[0]<<16)|(marker[1]<<8)|marker[2] -1);
							}
						}
						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;
	if(incmd && last)
	{
		sfputc(hp->histfp,0);
		hist_cancel(hp);
		count = 2;
		skip = 0;
		oldind -= hp->histind;
		hp->histind = hp->histind-hp->histsize + oldind +2;
		if(hp->histind<0)
			hp->histind = 1;
		if(last<0)
		{
			char	buff[HIST_MARKSZ];
			int	fd = open(hp->histname,O_RDWR|O_cloexec);
			if(fd>=0)
			{
				hist_marker(buff,hp->histind);
				write(fd,(char*)hist_stamp,2);
				write(fd,buff,HIST_MARKSZ);
				sh_close(fd);
			}
		}
		last = 0;
		goto again;
	}
}

/*
 * 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
 */

static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
{
	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_AUDIT
	if(hp->auditfp)
	{
		time_t	t=time((time_t*)0);
		sfprintf(hp->auditfp, "%u;%lu;%s;%*s%c",
			 sh_isoption(SH_PRIVILEGED) ? sh.euserid : sh.userid,
			 (unsigned long)t, hp->tty, size, buff, 0);
		sfsync(hp->auditfp);
	}
#endif	/* SHOPT_AUDIT */
#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)
{
	if(!(n >= hist_min(hp) && n < hist_max(hp)))
		return(-1);
	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);
		}
		/* allow a search to be aborted */
		if(sh.trapnote & SH_SIGSET)
			break;
	}
	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;
	mbinit();
	sfseek(hp->histfp,offset,SEEK_SET);
	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
		return(-1);
	m = sfvalue(hp->histfp);
	n = (int)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((c=mbsize(cp)) < 0)
			c = 1;
		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.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 int is_space;
	register int quoted;
	register char *s1 = string;
	register unsigned char *cp = (unsigned char*)s1;
	register int flag = 0;
	History_t *hp = hist_ptr;
	if(!hp)
		return(NIL(char*));
	hist_copy(string,size,(int)hp->histind-1,-1);
	for(quoted=0;c = *cp;cp++)
	{
		is_space = isspace(c) && !quoted;
		if(is_space && flag)
		{
			*cp = 0;
			if(--word==0)
				break;
			flag = 0;
		}
		else if(is_space==0 && flag==0)
		{
			s1 = (char*)cp;
			flag++;
		}
		if (c=='\'' && !quoted)
		{
			for(cp++;*cp && *cp != c;cp++)
				;
		}
		else if (c=='\"' && !quoted)
		{
			for(cp++;*cp && (*cp != c || quoted);cp++)
				quoted = *cp=='\\' ? !quoted : 0;
		}
		quoted = *cp=='\\' ? !quoted : 0;
	}
	*cp = 0;
	if(s1 != string)
		/* We can't use strcpy() because the two buffers may overlap. */
		memmove(string,s1,strlen(s1)+1);
	return(string);
}

#endif	/* SHOPT_ESH */

#if SHOPT_ESH
/*
 * given the current command and line number,
 * and number of lines back or forward,
 * 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
 */
static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
{
	register int newfd,oldfd;
	History_t *hp = (History_t*)handle;
	NOT_USED(data);
	if(type==SF_WRITE)
	{
		if(errno==ENOSPC || hp->histwfail++ >= 10)
			return(0);
		/* write failure could be NFS problem, try to reopen */
		sh_close(oldfd=sffileno(fp));
		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR|O_cloexec,S_IRUSR|S_IWUSR)) >= 0)
		{
			if(sh_fcntl(newfd, F_dupfd_cloexec, 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);
}
