xref: /onnv-gate/usr/src/lib/libshell/common/edit/history.c (revision 12068:08a39a083754)
14887Schin /***********************************************************************
24887Schin *                                                                      *
34887Schin *               This software is part of the ast package               *
4*12068SRoger.Faulkner@Oracle.COM *          Copyright (c) 1982-2010 AT&T Intellectual Property          *
54887Schin *                      and is licensed under the                       *
64887Schin *                  Common Public License, Version 1.0                  *
78462SApril.Chin@Sun.COM *                    by AT&T Intellectual Property                     *
84887Schin *                                                                      *
94887Schin *                A copy of the License is available at                 *
104887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
114887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
124887Schin *                                                                      *
134887Schin *              Information and Software Systems Research               *
144887Schin *                            AT&T Research                             *
154887Schin *                           Florham Park NJ                            *
164887Schin *                                                                      *
174887Schin *                  David Korn <dgk@research.att.com>                   *
184887Schin *                                                                      *
194887Schin ***********************************************************************/
204887Schin #pragma prototyped
214887Schin /*
224887Schin  *   History file manipulation routines
234887Schin  *
244887Schin  *   David Korn
254887Schin  *   AT&T Labs
264887Schin  *
274887Schin  */
284887Schin 
294887Schin /*
304887Schin  * Each command in the history file starts on an even byte is null terminated.
314887Schin  * The first byte must contain the special character HIST_UNDO and the second
324887Schin  * byte is the version number.  The sequence HIST_UNDO 0, following a command,
334887Schin  * nullifies the previous command. A six byte sequence starting with
344887Schin  * HIST_CMDNO is used to store the command number so that it is not necessary
354887Schin  * to read the file from beginning to end to get to the last block of
364887Schin  * commands.  This format of this sequence is different in version 1
374887Schin  * then in version 0.  Version 1 allows commands to use the full 8 bit
384887Schin  * character set.  It can understand version 0 format files.
394887Schin  */
404887Schin 
414887Schin 
424887Schin #define HIST_MAX	(sizeof(int)*HIST_BSIZE)
434887Schin #define HIST_BIG	(0100000-1024)	/* 1K less than maximum short */
444887Schin #define HIST_LINE	32		/* typical length for history line */
454887Schin #define HIST_MARKSZ	6
464887Schin #define HIST_RECENT	600
474887Schin #define HIST_UNDO	0201		/* invalidate previous command */
484887Schin #define HIST_CMDNO	0202		/* next 3 bytes give command number */
494887Schin #define HIST_BSIZE	4096		/* size of history file buffer */
504887Schin #define HIST_DFLT	512		/* default size of history list */
514887Schin 
528462SApril.Chin@Sun.COM #if SHOPT_AUDIT
538462SApril.Chin@Sun.COM #   define _HIST_AUDIT	Sfio_t	*auditfp; \
548462SApril.Chin@Sun.COM 			char	*tty; \
558462SApril.Chin@Sun.COM 			int	auditmask;
568462SApril.Chin@Sun.COM #else
578462SApril.Chin@Sun.COM #   define _HIST_AUDIT
588462SApril.Chin@Sun.COM #endif
598462SApril.Chin@Sun.COM 
604887Schin #define _HIST_PRIVATE \
618462SApril.Chin@Sun.COM 	void	*histshell; \
624887Schin 	off_t	histcnt;	/* offset into history file */\
634887Schin 	off_t	histmarker;	/* offset of last command marker */ \
644887Schin 	int	histflush;	/* set if flushed outside of hflush() */\
654887Schin 	int	histmask;	/* power of two mask for histcnt */ \
664887Schin 	char	histbuff[HIST_BSIZE+1];	/* history file buffer */ \
674887Schin 	int	histwfail; \
688462SApril.Chin@Sun.COM 	_HIST_AUDIT \
694887Schin 	off_t	histcmds[2];	/* offset for recent commands, must be last */
704887Schin 
714887Schin #define hist_ind(hp,c)	((int)((c)&(hp)->histmask))
724887Schin 
734887Schin #include	<ast.h>
744887Schin #include	<sfio.h>
754887Schin #include	"FEATURE/time"
764887Schin #include	<error.h>
774887Schin #include	<ls.h>
784887Schin #if KSHELL
794887Schin #   include	"defs.h"
804887Schin #   include	"variables.h"
814887Schin #   include	"path.h"
824887Schin #   include	"builtins.h"
834887Schin #   include	"io.h"
8410898Sroland.mainz@nrubsig.org #else
8510898Sroland.mainz@nrubsig.org #   include	<ctype.h>
864887Schin #endif	/* KSHELL */
874887Schin #include	"history.h"
884887Schin 
894887Schin #if !KSHELL
904887Schin #   define new_of(type,x)	((type*)malloc((unsigned)sizeof(type)+(x)))
914887Schin #   define NIL(type)		((type)0)
924887Schin #   define path_relative(x)	(x)
934887Schin #   ifdef __STDC__
944887Schin #	define nv_getval(s)	getenv(#s)
954887Schin #   else
964887Schin #	define nv_getval(s)	getenv("s")
974887Schin #   endif /* __STDC__ */
984887Schin #   define e_unknown	 	"unknown"
994887Schin #   define sh_translate(x)	(x)
1004887Schin     char login_sh =		0;
1014887Schin     char hist_fname[] =		"/.history";
1024887Schin #endif	/* KSHELL */
1034887Schin 
1044887Schin #ifndef O_BINARY
1054887Schin #   define O_BINARY	0
1064887Schin #endif /* O_BINARY */
1074887Schin 
1084887Schin int	_Hist = 0;
1094887Schin static void	hist_marker(char*,long);
1108462SApril.Chin@Sun.COM static History_t* hist_trim(History_t*, int);
1114887Schin static int	hist_nearend(History_t*,Sfio_t*, off_t);
1124887Schin static int	hist_check(int);
1134887Schin static int	hist_clean(int);
1144887Schin #ifdef SF_BUFCONST
1154887Schin     static ssize_t  hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
1164887Schin     static int      hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
1174887Schin #else
1184887Schin     static int	hist_write(Sfio_t*, const void*, int, Sfdisc_t*);
1194887Schin     static int	hist_exceptf(Sfio_t*, int, Sfdisc_t*);
1204887Schin #endif
1214887Schin 
1224887Schin 
1234887Schin static int	histinit;
1244887Schin static mode_t	histmode;
1254887Schin static History_t *wasopen;
1264887Schin static History_t *hist_ptr;
1274887Schin 
1284887Schin #if SHOPT_ACCTFILE
1294887Schin     static int	acctfd;
1304887Schin     static char *logname;
1314887Schin #   include <pwd.h>
1324887Schin 
acctinit(History_t * hp)1338462SApril.Chin@Sun.COM     static int  acctinit(History_t *hp)
1344887Schin     {
1354887Schin 	register char *cp, *acctfile;
1368462SApril.Chin@Sun.COM 	Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0);
1374887Schin 
1384887Schin 	if(!np || !(acctfile=nv_getval(np)))
1394887Schin 		return(0);
1404887Schin 	if(!(cp = getlogin()))
1414887Schin 	{
1424887Schin 		struct passwd *userinfo = getpwuid(getuid());
1434887Schin 		if(userinfo)
1444887Schin 			cp = userinfo->pw_name;
1454887Schin 		else
1464887Schin 			cp = "unknown";
1474887Schin 	}
1484887Schin 	logname = strdup(cp);
1494887Schin 	if((acctfd=sh_open(acctfile,
1504887Schin 		O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 &&
1514887Schin 	    (unsigned)acctfd < 10)
1524887Schin 	{
1534887Schin 		int n;
1544887Schin 		if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0)
1554887Schin 		{
1564887Schin 			close(acctfd);
1574887Schin 			acctfd = n;
1584887Schin 		}
1594887Schin 	}
1604887Schin 	if(acctfd < 0)
1614887Schin 	{
1624887Schin 		acctfd = 0;
1634887Schin 		return(0);
1644887Schin 	}
1654887Schin 	if(strmatch(acctfile,e_devfdNN))
1664887Schin 	{
1674887Schin 		char newfile[16];
1684887Schin 		sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd);
1694887Schin 		nv_putval(np,newfile,NV_RDONLY);
1704887Schin 	}
1714887Schin 	else
1724887Schin 		fcntl(acctfd,F_SETFD,FD_CLOEXEC);
1734887Schin 	return(1);
1744887Schin     }
1754887Schin #endif /* SHOPT_ACCTFILE */
1764887Schin 
1778462SApril.Chin@Sun.COM #if SHOPT_AUDIT
sh_checkaudit(History_t * hp,const char * name,char * logbuf,size_t len)1788462SApril.Chin@Sun.COM static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len)
1798462SApril.Chin@Sun.COM {
1808462SApril.Chin@Sun.COM 	Shell_t	*shp = (Shell_t*)hp->histshell;
1818462SApril.Chin@Sun.COM 	char	*buff, *cp, *last;
1828462SApril.Chin@Sun.COM 	int	id1, id2, r=0, n, fd;
1838462SApril.Chin@Sun.COM 	if((fd=open(name, O_RDONLY)) < 0)
1848462SApril.Chin@Sun.COM 		return(0);
1858462SApril.Chin@Sun.COM 	if((n = read(fd, logbuf,len-1)) < 0)
1868462SApril.Chin@Sun.COM 		goto done;
1878462SApril.Chin@Sun.COM 	while(logbuf[n-1]=='\n')
1888462SApril.Chin@Sun.COM 		n--;
1898462SApril.Chin@Sun.COM 	logbuf[n] = 0;
1908462SApril.Chin@Sun.COM 	if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' ')))
1918462SApril.Chin@Sun.COM 		goto done;
1928462SApril.Chin@Sun.COM 	*cp = 0;
1938462SApril.Chin@Sun.COM 	do
1948462SApril.Chin@Sun.COM 	{
1958462SApril.Chin@Sun.COM 		cp++;
1968462SApril.Chin@Sun.COM 		id1 = id2 = strtol(cp,&last,10);
1978462SApril.Chin@Sun.COM 		if(*last=='-')
1988462SApril.Chin@Sun.COM 			id1 = strtol(last+1,&last,10);
1998462SApril.Chin@Sun.COM 		if(shp->euserid >=id1 && shp->euserid <= id2)
2008462SApril.Chin@Sun.COM 			r |= 1;
2018462SApril.Chin@Sun.COM 		if(shp->userid >=id1 && shp->userid <= id2)
2028462SApril.Chin@Sun.COM 			r |= 2;
2038462SApril.Chin@Sun.COM 		cp = last;
2048462SApril.Chin@Sun.COM 	}
2058462SApril.Chin@Sun.COM 	while(*cp==';' ||  *cp==' ');
2068462SApril.Chin@Sun.COM done:
2078462SApril.Chin@Sun.COM 	close(fd);
2088462SApril.Chin@Sun.COM 	return(r);
2098462SApril.Chin@Sun.COM 
2108462SApril.Chin@Sun.COM }
2118462SApril.Chin@Sun.COM #endif /*SHOPT_AUDIT*/
2128462SApril.Chin@Sun.COM 
2134887Schin static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
2144887Schin static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
2154887Schin 
hist_touch(void * handle)2164887Schin static void hist_touch(void *handle)
2174887Schin {
2184887Schin 	touch((char*)handle, (time_t)0, (time_t)0, 0);
2194887Schin }
2204887Schin 
2214887Schin /*
2224887Schin  * open the history file
2234887Schin  * if HISTNAME is not given and userid==0 then no history file.
2244887Schin  * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is
2254887Schin  * cleaned up.
2264887Schin  * hist_open() returns 1, if history file is open
2274887Schin  */
sh_histinit(void * sh_context)2288462SApril.Chin@Sun.COM int  sh_histinit(void *sh_context)
2294887Schin {
2308462SApril.Chin@Sun.COM 	Shell_t *shp = (Shell_t*)sh_context;
2314887Schin 	register int fd;
2324887Schin 	register History_t *hp;
2334887Schin 	register char *histname;
2344887Schin 	char *fname=0;
2354887Schin 	int histmask, maxlines, hist_start=0;
2364887Schin 	register char *cp;
2374887Schin 	register off_t hsize = 0;
2384887Schin 
2398462SApril.Chin@Sun.COM 	if(shp->hist_ptr=hist_ptr)
2404887Schin 		return(1);
2414887Schin 	if(!(histname = nv_getval(HISTFILE)))
2424887Schin 	{
2434887Schin 		int offset = staktell();
2444887Schin 		if(cp=nv_getval(HOME))
2454887Schin 			stakputs(cp);
2464887Schin 		stakputs(hist_fname);
2474887Schin 		stakputc(0);
2484887Schin 		stakseek(offset);
2494887Schin 		histname = stakptr(offset);
2504887Schin 	}
2514887Schin #ifdef future
2524887Schin 	if(hp=wasopen)
2534887Schin 	{
2544887Schin 		/* reuse history file if same name */
2554887Schin 		wasopen = 0;
2568462SApril.Chin@Sun.COM 		shp->hist_ptr = hist_ptr = hp;
2574887Schin 		if(strcmp(histname,hp->histname)==0)
2584887Schin 			return(1);
2594887Schin 		else
2604887Schin 			hist_free();
2614887Schin 	}
2624887Schin #endif
2634887Schin retry:
2644887Schin 	cp = path_relative(histname);
2654887Schin 	if(!histinit)
2664887Schin 		histmode = S_IRUSR|S_IWUSR;
2674887Schin 	if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0)
2684887Schin 	{
2694887Schin 		hsize=lseek(fd,(off_t)0,SEEK_END);
2704887Schin 	}
2714887Schin 	if((unsigned)fd <=2)
2724887Schin 	{
2734887Schin 		int n;
2744887Schin 		if((n=fcntl(fd,F_DUPFD,10))>=0)
2754887Schin 		{
2764887Schin 			close(fd);
2774887Schin 			fd=n;
2784887Schin 		}
2794887Schin 	}
2804887Schin 	/* make sure that file has history file format */
2814887Schin 	if(hsize && hist_check(fd))
2824887Schin 	{
2834887Schin 		close(fd);
2844887Schin 		hsize = 0;
2854887Schin 		if(unlink(cp)>=0)
2864887Schin 			goto retry;
2874887Schin 		fd = -1;
2884887Schin 	}
2894887Schin 	if(fd < 0)
2904887Schin 	{
2914887Schin #if KSHELL
2924887Schin 		/* don't allow root a history_file in /tmp */
2938462SApril.Chin@Sun.COM 		if(shp->userid)
2944887Schin #endif	/* KSHELL */
2954887Schin 		{
2964887Schin 			if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*))))
2974887Schin 				return(0);
2984887Schin 			fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
2994887Schin 		}
3004887Schin 	}
3014887Schin 	if(fd<0)
3024887Schin 		return(0);
3034887Schin 	/* set the file to close-on-exec */
3044887Schin 	fcntl(fd,F_SETFD,FD_CLOEXEC);
3054887Schin 	if(cp=nv_getval(HISTSIZE))
3064887Schin 		maxlines = (unsigned)strtol(cp, (char**)0, 10);
3074887Schin 	else
3084887Schin 		maxlines = HIST_DFLT;
3094887Schin 	for(histmask=16;histmask <= maxlines; histmask <<=1 );
3104887Schin 	if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t))))
3114887Schin 	{
3124887Schin 		close(fd);
3134887Schin 		return(0);
3144887Schin 	}
3158462SApril.Chin@Sun.COM 	shp->hist_ptr = hist_ptr = hp;
3168462SApril.Chin@Sun.COM 	hp->histshell = (void*)shp;
3174887Schin 	hp->histsize = maxlines;
3184887Schin 	hp->histmask = histmask;
3194887Schin 	hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE);
3204887Schin 	memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1));
3214887Schin 	hp->histind = 1;
3224887Schin 	hp->histcmds[1] = 2;
3234887Schin 	hp->histcnt = 2;
3244887Schin 	hp->histname = strdup(histname);
3254887Schin 	hp->histdisc = hist_disc;
3264887Schin 	if(hsize==0)
3274887Schin 	{
3284887Schin 		/* put special characters at front of file */
3294887Schin 		sfwrite(hp->histfp,(char*)hist_stamp,2);
3304887Schin 		sfsync(hp->histfp);
3314887Schin 	}
3324887Schin 	/* initialize history list */
3334887Schin 	else
3344887Schin 	{
3354887Schin 		int first,last;
3364887Schin 		off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE;
3374887Schin 		hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size);
3384887Schin 		hist_eof(hp);	 /* this sets histind to last command */
3394887Schin 		if((hist_start = (last=(int)hp->histind)-maxlines) <=0)
3404887Schin 			hist_start = 1;
3414887Schin 		mark = hp->histmarker;
3424887Schin 		while(first > hist_start)
3434887Schin 		{
3444887Schin 			size += size;
3454887Schin 			first = hist_nearend(hp,hp->histfp,hsize-size);
3464887Schin 			hp->histind = first;
3474887Schin 		}
3484887Schin 		histinit = hist_start;
3494887Schin 		hist_eof(hp);
3504887Schin 		if(!histinit)
3514887Schin 		{
3524887Schin 			sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET);
3534887Schin 			hp->histind = last;
3544887Schin 			hp->histmarker = mark;
3554887Schin 		}
3564887Schin 		histinit = 0;
3574887Schin 	}
3584887Schin 	if(fname)
3594887Schin 	{
3604887Schin 		unlink(fname);
3614887Schin 		free((void*)fname);
3624887Schin 	}
3634887Schin 	if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX)
3644887Schin 	{
3654887Schin #ifdef DEBUG
3664887Schin 		sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize);
3674887Schin 		sfsync(sfstderr);
3684887Schin #endif /* DEBUG */
3698462SApril.Chin@Sun.COM 		hp = hist_trim(hp,(int)hp->histind-maxlines);
3704887Schin 	}
3714887Schin 	sfdisc(hp->histfp,&hp->histdisc);
3724887Schin #if KSHELL
3734887Schin 	(HISTCUR)->nvalue.lp = (&hp->histind);
3744887Schin #endif /* KSHELL */
3754887Schin 	sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname);
3764887Schin #if SHOPT_ACCTFILE
3774887Schin 	if(sh_isstate(SH_INTERACTIVE))
3788462SApril.Chin@Sun.COM 		acctinit(hp);
3794887Schin #endif /* SHOPT_ACCTFILE */
3808462SApril.Chin@Sun.COM #if SHOPT_AUDIT
3818462SApril.Chin@Sun.COM 	{
3828462SApril.Chin@Sun.COM 		char buff[SF_BUFSIZE];
3838462SApril.Chin@Sun.COM 		hp->auditfp = 0;
3848462SApril.Chin@Sun.COM 		if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff))))
3858462SApril.Chin@Sun.COM 		{
3868462SApril.Chin@Sun.COM 			if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10)
3878462SApril.Chin@Sun.COM 			{
3888462SApril.Chin@Sun.COM 				int n;
3898462SApril.Chin@Sun.COM 				if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0)
3908462SApril.Chin@Sun.COM 				{
3918462SApril.Chin@Sun.COM 					sh_close(fd);
3928462SApril.Chin@Sun.COM 					fd = n;
3938462SApril.Chin@Sun.COM 				}
3948462SApril.Chin@Sun.COM 			}
3958462SApril.Chin@Sun.COM 			if(fd>=0)
3968462SApril.Chin@Sun.COM 			{
3978462SApril.Chin@Sun.COM 				hp->tty = strdup(ttyname(2));
3988462SApril.Chin@Sun.COM 				hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE);
3998462SApril.Chin@Sun.COM 			}
4008462SApril.Chin@Sun.COM 		}
4018462SApril.Chin@Sun.COM 	}
4028462SApril.Chin@Sun.COM #endif
4034887Schin 	return(1);
4044887Schin }
4054887Schin 
4064887Schin /*
4074887Schin  * close the history file and free the space
4084887Schin  */
4094887Schin 
hist_close(register History_t * hp)4104887Schin void hist_close(register History_t *hp)
4114887Schin {
4128462SApril.Chin@Sun.COM 	Shell_t	*shp = (Shell_t*)hp->histshell;
4134887Schin 	sfclose(hp->histfp);
4148462SApril.Chin@Sun.COM #if SHOPT_AUDIT
4158462SApril.Chin@Sun.COM 	if(hp->auditfp)
4168462SApril.Chin@Sun.COM 	{
4178462SApril.Chin@Sun.COM 		if(hp->tty)
4188462SApril.Chin@Sun.COM 			free((void*)hp->tty);
4198462SApril.Chin@Sun.COM 		sfclose(hp->auditfp);
4208462SApril.Chin@Sun.COM 	}
4218462SApril.Chin@Sun.COM #endif /* SHOPT_AUDIT */
4224887Schin 	free((char*)hp);
4234887Schin 	hist_ptr = 0;
4248462SApril.Chin@Sun.COM 	shp->hist_ptr = 0;
4254887Schin #if SHOPT_ACCTFILE
4264887Schin 	if(acctfd)
4274887Schin 	{
4284887Schin 		close(acctfd);
4294887Schin 		acctfd = 0;
4304887Schin 	}
4314887Schin #endif /* SHOPT_ACCTFILE */
4324887Schin }
4334887Schin 
4344887Schin /*
4354887Schin  * check history file format to see if it begins with special byte
4364887Schin  */
hist_check(register int fd)4374887Schin static int hist_check(register int fd)
4384887Schin {
4394887Schin 	unsigned char magic[2];
4404887Schin 	lseek(fd,(off_t)0,SEEK_SET);
4414887Schin 	if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO))
4424887Schin 		return(1);
4434887Schin 	return(0);
4444887Schin }
4454887Schin 
4464887Schin /*
4474887Schin  * clean out history file OK if not modified in HIST_RECENT seconds
4484887Schin  */
hist_clean(int fd)4494887Schin static int hist_clean(int fd)
4504887Schin {
4514887Schin 	struct stat statb;
4524887Schin 	return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT);
4534887Schin }
4544887Schin 
4554887Schin /*
4564887Schin  * Copy the last <n> commands to a new file and make this the history file
4574887Schin  */
4584887Schin 
hist_trim(History_t * hp,int n)4598462SApril.Chin@Sun.COM static History_t* hist_trim(History_t *hp, int n)
4604887Schin {
4614887Schin 	register char *cp;
4624887Schin 	register int incmd=1, c=0;
4634887Schin 	register History_t *hist_new, *hist_old = hp;
4644887Schin 	char *buff, *endbuff, *tmpname=0;
4654887Schin 	off_t oldp,newp;
4664887Schin 	struct stat statb;
4674887Schin 	unlink(hist_old->histname);
4684887Schin 	if(access(hist_old->histname,F_OK) >= 0)
4694887Schin 	{
4704887Schin 		/* The unlink can fail on windows 95 */
4714887Schin 		int fd;
4724887Schin 		char *last, *name=hist_old->histname;
4734887Schin 		close(sffileno(hist_old->histfp));
4744887Schin 		tmpname = (char*)malloc(strlen(name)+14);
4754887Schin 		if(last = strrchr(name,'/'))
4764887Schin 		{
4774887Schin 			*last = 0;
4784887Schin 			pathtmp(tmpname,name,"hist",NIL(int*));
4794887Schin 			*last = '/';
4804887Schin 		}
4814887Schin 		else
4824887Schin 			pathtmp(tmpname,".","hist",NIL(int*));
4834887Schin 		if(rename(name,tmpname) < 0)
4844887Schin 			tmpname = name;
4854887Schin 		fd = open(tmpname,O_RDONLY);
4864887Schin 		sfsetfd(hist_old->histfp,fd);
4874887Schin 		if(tmpname==name)
4884887Schin 			tmpname = 0;
4894887Schin 	}
4908462SApril.Chin@Sun.COM 	hist_ptr = 0;
4914887Schin 	if(fstat(sffileno(hist_old->histfp),&statb)>=0)
4924887Schin 	{
4934887Schin 		histinit = 1;
4944887Schin 		histmode =  statb.st_mode;
4954887Schin 	}
4968462SApril.Chin@Sun.COM 	if(!sh_histinit(hp->histshell))
4974887Schin 	{
4984887Schin 		/* use the old history file */
4998462SApril.Chin@Sun.COM 		return hist_ptr = hist_old;
5004887Schin 	}
5014887Schin 	hist_new = hist_ptr;
5024887Schin 	hist_ptr = hist_old;
5034887Schin 	if(--n < 0)
5044887Schin 		n = 0;
5054887Schin 	newp = hist_seek(hist_old,++n);
5064887Schin 	while(1)
5074887Schin 	{
5084887Schin 		if(!incmd)
5094887Schin 		{
5104887Schin 			c = hist_ind(hist_new,++hist_new->histind);
5114887Schin 			hist_new->histcmds[c] = hist_new->histcnt;
5124887Schin 			if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
5134887Schin 			{
5144887Schin 				char locbuff[HIST_MARKSZ];
5154887Schin 				hist_marker(locbuff,hist_new->histind);
5164887Schin 				sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
5174887Schin 				hist_new->histcnt += HIST_MARKSZ;
5184887Schin 				hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
5194887Schin 			}
5204887Schin 			oldp = newp;
5214887Schin 			newp = hist_seek(hist_old,++n);
5224887Schin 			if(newp <=oldp)
5234887Schin 				break;
5244887Schin 		}
5254887Schin 		if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
5264887Schin 			break;
5274887Schin 		*(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0;
5284887Schin 		/* copy to null byte */
5294887Schin 		incmd = 0;
5304887Schin 		while(*cp++);
5314887Schin 		if(cp > endbuff)
5324887Schin 			incmd = 1;
5334887Schin 		else if(*cp==0)
5344887Schin 			cp++;
5354887Schin 		if(cp > endbuff)
5364887Schin 			cp = endbuff;
5374887Schin 		c = cp-buff;
5384887Schin 		hist_new->histcnt += c;
5394887Schin 		sfwrite(hist_new->histfp,buff,c);
5404887Schin 	}
5418462SApril.Chin@Sun.COM 	hist_cancel(hist_new);
5424887Schin 	sfclose(hist_old->histfp);
5434887Schin 	if(tmpname)
5444887Schin 	{
5454887Schin 		unlink(tmpname);
5464887Schin 		free(tmpname);
5474887Schin 	}
5484887Schin 	free((char*)hist_old);
5498462SApril.Chin@Sun.COM 	return hist_ptr = hist_new;
5504887Schin }
5514887Schin 
5524887Schin /*
5534887Schin  * position history file at size and find next command number
5544887Schin  */
hist_nearend(History_t * hp,Sfio_t * iop,register off_t size)5554887Schin static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size)
5564887Schin {
5574887Schin         register unsigned char *cp, *endbuff;
5584887Schin         register int n, incmd=1;
5594887Schin         unsigned char *buff, marker[4];
5604887Schin 	if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
5614887Schin 		goto begin;
5624887Schin 	/* skip to marker command and return the number */
5634887Schin 	/* numbering commands occur after a null and begin with HIST_CMDNO */
5644887Schin         while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR))
5654887Schin         {
5664887Schin 		n = sfvalue(iop);
5674887Schin                 *(endbuff=cp+n) = 0;
5684887Schin                 while(1)
5694887Schin                 {
5704887Schin 			/* check for marker */
5714887Schin                         if(!incmd && *cp++==HIST_CMDNO && *cp==0)
5724887Schin                         {
5734887Schin                                 n = cp+1 - buff;
5744887Schin                                 incmd = -1;
5754887Schin                                 break;
5764887Schin                         }
5774887Schin                         incmd = 0;
5784887Schin                         while(*cp++);
5794887Schin                         if(cp>endbuff)
5804887Schin                         {
5814887Schin                                 incmd = 1;
5824887Schin                                 break;
5834887Schin                         }
5844887Schin                         if(*cp==0 && ++cp>endbuff)
5854887Schin                                 break;
5864887Schin                 }
5874887Schin                 size += n;
5884887Schin 		sfread(iop,(char*)buff,n);
5894887Schin 		if(incmd < 0)
5904887Schin                 {
5914887Schin 			if((n=sfread(iop,(char*)marker,4))==4)
5924887Schin 			{
5934887Schin 				n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
5944887Schin 				if(n < size/2)
5954887Schin 				{
5964887Schin 					hp->histmarker = hp->histcnt = size+4;
5974887Schin 					return(n);
5984887Schin 				}
5994887Schin 				n=4;
6004887Schin 			}
6014887Schin 			if(n >0)
6024887Schin 				size += n;
6034887Schin 			incmd = 0;
6044887Schin 		}
6054887Schin 	}
6064887Schin begin:
6074887Schin 	sfseek(iop,(off_t)2,SEEK_SET);
6084887Schin 	hp->histmarker = hp->histcnt = 2L;
6094887Schin 	return(1);
6104887Schin }
6114887Schin 
6124887Schin /*
6134887Schin  * This routine reads the history file from the present position
6144887Schin  * to the end-of-file and puts the information in the in-core
6154887Schin  * history table
6164887Schin  * Note that HIST_CMDNO is only recognized at the beginning of a command
6174887Schin  * and that HIST_UNDO as the first character of a command is skipped
6184887Schin  * unless it is followed by 0.  If followed by 0 then it cancels
6194887Schin  * the previous command.
6204887Schin  */
6214887Schin 
hist_eof(register History_t * hp)6224887Schin void hist_eof(register History_t *hp)
6234887Schin {
6244887Schin 	register char *cp,*first,*endbuff;
6254887Schin 	register int incmd = 0;
6264887Schin 	register off_t count = hp->histcnt;
6274887Schin 	int n,skip=0;
6284887Schin 	sfseek(hp->histfp,count,SEEK_SET);
6294887Schin         while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
6304887Schin 	{
6314887Schin 		n = sfvalue(hp->histfp);
6324887Schin 		*(endbuff = cp+n) = 0;
6334887Schin 		first = cp += skip;
6344887Schin 		while(1)
6354887Schin 		{
6364887Schin 			while(!incmd)
6374887Schin 			{
6384887Schin 				if(cp>first)
6394887Schin 				{
6404887Schin 					count += (cp-first);
6414887Schin 					n = hist_ind(hp, ++hp->histind);
6424887Schin #ifdef future
6434887Schin 					if(count==hp->histcmds[n])
6444887Schin 					{
6454887Schin 	sfprintf(sfstderr,"count match n=%d\n",n);
6464887Schin 						if(histinit)
6474887Schin 						{
6484887Schin 							histinit = 0;
6494887Schin 							return;
6504887Schin 						}
6514887Schin 					}
6524887Schin 					else if(n>=histinit)
6534887Schin #endif
6544887Schin 						hp->histcmds[n] = count;
6554887Schin 					first = cp;
6564887Schin 				}
6574887Schin 				switch(*((unsigned char*)(cp++)))
6584887Schin 				{
6594887Schin 					case HIST_CMDNO:
6604887Schin 						if(*cp==0)
6614887Schin 						{
6624887Schin 							hp->histmarker=count+2;
6634887Schin 							cp += (HIST_MARKSZ-1);
6644887Schin 							hp->histind--;
6654887Schin #ifdef future
6664887Schin 							if(cp <= endbuff)
6674887Schin 							{
6684887Schin 								unsigned char *marker = (unsigned char*)(cp-4);
6694887Schin 								int n = ((marker[0]<<16)
6704887Schin |(marker[1]<<8)|marker[2]);
6714887Schin 								if((n<count/2) && n !=  (hp->histind+1))
6724887Schin 									errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n);
6734887Schin 							}
6744887Schin #endif
6754887Schin 						}
6764887Schin 						break;
6774887Schin 					case HIST_UNDO:
6784887Schin 						if(*cp==0)
6794887Schin 						{
6804887Schin 							cp+=1;
6814887Schin 							hp->histind-=2;
6824887Schin 						}
6834887Schin 						break;
6844887Schin 					default:
6854887Schin 						cp--;
6864887Schin 						incmd = 1;
6874887Schin 				}
6884887Schin 				if(cp > endbuff)
6894887Schin 				{
6904887Schin 					cp++;
6914887Schin 					goto refill;
6924887Schin 				}
6934887Schin 			}
6944887Schin 			first = cp;
6954887Schin 			while(*cp++);
6964887Schin 			if(cp > endbuff)
6974887Schin 				break;
6984887Schin 			incmd = 0;
6994887Schin 			while(*cp==0)
7004887Schin 			{
7014887Schin 				if(++cp > endbuff)
7024887Schin 					goto refill;
7034887Schin 			}
7044887Schin 		}
7054887Schin 	refill:
7064887Schin 		count += (--cp-first);
7074887Schin 		skip = (cp-endbuff);
7084887Schin 		if(!incmd && !skip)
7094887Schin 			hp->histcmds[hist_ind(hp,++hp->histind)] = count;
7104887Schin 	}
7114887Schin 	hp->histcnt = count;
7124887Schin }
7134887Schin 
7144887Schin /*
7154887Schin  * This routine will cause the previous command to be cancelled
7164887Schin  */
7174887Schin 
hist_cancel(register History_t * hp)7184887Schin void hist_cancel(register History_t *hp)
7194887Schin {
7204887Schin 	register int c;
7214887Schin 	if(!hp)
7224887Schin 		return;
7234887Schin 	sfputc(hp->histfp,HIST_UNDO);
7244887Schin 	sfputc(hp->histfp,0);
7254887Schin 	sfsync(hp->histfp);
7264887Schin 	hp->histcnt += 2;
7274887Schin 	c = hist_ind(hp,--hp->histind);
7284887Schin 	hp->histcmds[c] = hp->histcnt;
7294887Schin }
7304887Schin 
7314887Schin /*
7324887Schin  * flush the current history command
7334887Schin  */
7344887Schin 
hist_flush(register History_t * hp)7354887Schin void hist_flush(register History_t *hp)
7364887Schin {
7374887Schin 	register char *buff;
7384887Schin 	if(hp)
7394887Schin 	{
7404887Schin 		if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
7414887Schin 		{
7424887Schin 			hp->histflush = sfvalue(hp->histfp)+1;
7434887Schin 			sfwrite(hp->histfp,buff,0);
7444887Schin 		}
7454887Schin 		else
7464887Schin 			hp->histflush=0;
7474887Schin 		if(sfsync(hp->histfp)<0)
7484887Schin 		{
7494887Schin 			hist_close(hp);
7508462SApril.Chin@Sun.COM 			if(!sh_histinit(hp->histshell))
7514887Schin 				sh_offoption(SH_HISTORY);
7524887Schin 		}
7534887Schin 		hp->histflush = 0;
7544887Schin 	}
7554887Schin }
7564887Schin 
7574887Schin /*
7584887Schin  * This is the write discipline for the history file
7594887Schin  * When called from hist_flush(), trailing newlines are deleted and
7604887Schin  * a zero byte.  Line sequencing is added as required
7614887Schin  */
7624887Schin 
7634887Schin #ifdef SF_BUFCONST
hist_write(Sfio_t * iop,const void * buff,register size_t insize,Sfdisc_t * handle)7644887Schin static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
7654887Schin #else
7664887Schin static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
7674887Schin #endif
7684887Schin {
7694887Schin 	register History_t *hp = (History_t*)handle;
7704887Schin 	register char *bufptr = ((char*)buff)+insize;
7714887Schin 	register int c,size = insize;
7724887Schin 	register off_t cur;
7734887Schin 	int saved=0;
7744887Schin 	char saveptr[HIST_MARKSZ];
7754887Schin 	if(!hp->histflush)
7764887Schin 		return(write(sffileno(iop),(char*)buff,size));
7774887Schin 	if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
7784887Schin 	{
7794887Schin 		errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
7804887Schin 		return(-1);
7814887Schin 	}
7824887Schin 	hp->histcnt = cur;
7834887Schin 	/* remove whitespace from end of commands */
7844887Schin 	while(--bufptr >= (char*)buff)
7854887Schin 	{
7864887Schin 		c= *bufptr;
7874887Schin 		if(!isspace(c))
7884887Schin 		{
7894887Schin 			if(c=='\\' && *(bufptr+1)!='\n')
7904887Schin 				bufptr++;
7914887Schin 			break;
7924887Schin 		}
7934887Schin 	}
7944887Schin 	/* don't count empty lines */
7954887Schin 	if(++bufptr <= (char*)buff)
7964887Schin 		return(insize);
7974887Schin 	*bufptr++ = '\n';
7984887Schin 	*bufptr++ = 0;
7994887Schin 	size = bufptr - (char*)buff;
8008462SApril.Chin@Sun.COM #if	 SHOPT_AUDIT
8018462SApril.Chin@Sun.COM 	if(hp->auditfp)
8028462SApril.Chin@Sun.COM 	{
8038462SApril.Chin@Sun.COM 		Shell_t *shp = (Shell_t*)hp->histshell;
8048462SApril.Chin@Sun.COM 		time_t	t=time((time_t*)0);
8058462SApril.Chin@Sun.COM 		sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shp->euserid:shp->userid,t,hp->tty,size,buff,0);
8068462SApril.Chin@Sun.COM 		sfsync(hp->auditfp);
8078462SApril.Chin@Sun.COM 	}
8088462SApril.Chin@Sun.COM #endif	/* SHOPT_AUDIT */
8094887Schin #if	SHOPT_ACCTFILE
8104887Schin 	if(acctfd)
8114887Schin 	{
8124887Schin 		int timechars, offset;
8134887Schin 		offset = staktell();
8144887Schin 		stakputs(buff);
8154887Schin 		stakseek(staktell() - 1);
8164887Schin 		timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
8174887Schin 		lseek(acctfd, (off_t)0, SEEK_END);
8184887Schin 		write(acctfd, stakptr(offset), size - 2 + timechars);
8194887Schin 		stakseek(offset);
8204887Schin 
8214887Schin 	}
8224887Schin #endif /* SHOPT_ACCTFILE */
8234887Schin 	if(size&01)
8244887Schin 	{
8254887Schin 		size++;
8264887Schin 		*bufptr++ = 0;
8274887Schin 	}
8284887Schin 	hp->histcnt +=  size;
8294887Schin 	c = hist_ind(hp,++hp->histind);
8304887Schin 	hp->histcmds[c] = hp->histcnt;
8314887Schin 	if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
8324887Schin 	{
8334887Schin 		memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
8344887Schin 		saved=1;
8354887Schin 		hp->histcnt += HIST_MARKSZ;
8364887Schin 		hist_marker(bufptr,hp->histind);
8374887Schin 		hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
8384887Schin 		size += HIST_MARKSZ;
8394887Schin 	}
8404887Schin 	errno = 0;
8414887Schin 	size = write(sffileno(iop),(char*)buff,size);
8424887Schin 	if(saved)
8434887Schin 		memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
8444887Schin 	if(size>=0)
8454887Schin 	{
8464887Schin 		hp->histwfail = 0;
8474887Schin 		return(insize);
8484887Schin 	}
8494887Schin 	return(-1);
8504887Schin }
8514887Schin 
8524887Schin /*
8534887Schin  * Put history sequence number <n> into buffer <buff>
8544887Schin  * The buffer must be large enough to hold HIST_MARKSZ chars
8554887Schin  */
8564887Schin 
hist_marker(register char * buff,register long cmdno)8574887Schin static void hist_marker(register char *buff,register long cmdno)
8584887Schin {
8594887Schin 	*buff++ = HIST_CMDNO;
8604887Schin 	*buff++ = 0;
8614887Schin 	*buff++ = (cmdno>>16);
8624887Schin 	*buff++ = (cmdno>>8);
8634887Schin 	*buff++ = cmdno;
8644887Schin 	*buff++ = 0;
8654887Schin }
8664887Schin 
8674887Schin /*
8684887Schin  * return byte offset in history file for command <n>
8694887Schin  */
hist_tell(register History_t * hp,int n)8704887Schin off_t hist_tell(register History_t *hp, int n)
8714887Schin {
8724887Schin 	return(hp->histcmds[hist_ind(hp,n)]);
8734887Schin }
8744887Schin 
8754887Schin /*
8764887Schin  * seek to the position of command <n>
8774887Schin  */
hist_seek(register History_t * hp,int n)8784887Schin off_t hist_seek(register History_t *hp, int n)
8794887Schin {
8804887Schin 	return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
8814887Schin }
8824887Schin 
8834887Schin /*
8844887Schin  * write the command starting at offset <offset> onto file <outfile>.
8854887Schin  * if character <last> appears before newline it is deleted
8864887Schin  * each new-line character is replaced with string <nl>.
8874887Schin  */
8884887Schin 
hist_list(register History_t * hp,Sfio_t * outfile,off_t offset,int last,char * nl)8894887Schin void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
8904887Schin {
8914887Schin 	register int oldc=0;
8924887Schin 	register int c;
8934887Schin 	if(offset<0 || !hp)
8944887Schin 	{
8954887Schin 		sfputr(outfile,sh_translate(e_unknown),'\n');
8964887Schin 		return;
8974887Schin 	}
8984887Schin 	sfseek(hp->histfp,offset,SEEK_SET);
8994887Schin 	while((c = sfgetc(hp->histfp)) != EOF)
9004887Schin 	{
9014887Schin 		if(c && oldc=='\n')
9024887Schin 			sfputr(outfile,nl,-1);
9034887Schin 		else if(last && (c==0 || (c=='\n' && oldc==last)))
9044887Schin 			return;
9054887Schin 		else if(oldc)
9064887Schin 			sfputc(outfile,oldc);
9074887Schin 		oldc = c;
9084887Schin 		if(c==0)
9094887Schin 			return;
9104887Schin 	}
9114887Schin 	return;
9124887Schin }
9134887Schin 
9144887Schin /*
9154887Schin  * find index for last line with given string
9164887Schin  * If flag==0 then line must begin with string
9174887Schin  * direction < 1 for backwards search
9184887Schin */
9194887Schin 
hist_find(register History_t * hp,char * string,register int index1,int flag,int direction)9204887Schin Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
9214887Schin {
9224887Schin 	register int index2;
9234887Schin 	off_t offset;
9244887Schin 	int *coffset=0;
9254887Schin 	Histloc_t location;
9264887Schin 	location.hist_command = -1;
9274887Schin 	location.hist_char = 0;
9284887Schin 	location.hist_line = 0;
9294887Schin 	if(!hp)
9304887Schin 		return(location);
9314887Schin 	/* leading ^ means beginning of line unless escaped */
9324887Schin 	if(flag)
9334887Schin 	{
9344887Schin 		index2 = *string;
9354887Schin 		if(index2=='\\')
9364887Schin 			string++;
9374887Schin 		else if(index2=='^')
9384887Schin 		{
9394887Schin 			flag=0;
9404887Schin 			string++;
9414887Schin 		}
9424887Schin 	}
9434887Schin 	if(flag)
9444887Schin 		coffset = &location.hist_char;
9454887Schin 	index2 = (int)hp->histind;
9464887Schin 	if(direction<0)
9474887Schin 	{
9484887Schin 		index2 -= hp->histsize;
9494887Schin 		if(index2<1)
9504887Schin 			index2 = 1;
9514887Schin 		if(index1 <= index2)
9524887Schin 			return(location);
9534887Schin 	}
9544887Schin 	else if(index1 >= index2)
9554887Schin 		return(location);
9564887Schin 	while(index1!=index2)
9574887Schin 	{
9584887Schin 		direction>0?++index1:--index1;
9594887Schin 		offset = hist_tell(hp,index1);
9604887Schin 		if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
9614887Schin 		{
9624887Schin 			location.hist_command = index1;
9634887Schin 			return(location);
9644887Schin 		}
9654887Schin #if KSHELL
9664887Schin 		/* allow a search to be aborted */
9678462SApril.Chin@Sun.COM 		if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET)
9684887Schin 			break;
9694887Schin #endif /* KSHELL */
9704887Schin 	}
9714887Schin 	return(location);
9724887Schin }
9734887Schin 
9744887Schin /*
9754887Schin  * search for <string> in history file starting at location <offset>
9764887Schin  * If coffset==0 then line must begin with string
9774887Schin  * returns the line number of the match if successful, otherwise -1
9784887Schin  */
9794887Schin 
hist_match(register History_t * hp,off_t offset,char * string,int * coffset)9804887Schin int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
9814887Schin {
9824887Schin 	register unsigned char *first, *cp;
9834887Schin 	register int m,n,c=1,line=0;
9844887Schin #if SHOPT_MULTIBYTE
9854887Schin 	mbinit();
9864887Schin #endif /* SHOPT_MULTIBYTE */
9874887Schin 	sfseek(hp->histfp,offset,SEEK_SET);
9884887Schin 	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
9894887Schin 		return(-1);
9904887Schin 	m = sfvalue(hp->histfp);
9914887Schin 	n = strlen(string);
9924887Schin 	while(m > n)
9934887Schin 	{
9944887Schin 		if(*cp==*string && memcmp(cp,string,n)==0)
9954887Schin 		{
9964887Schin 			if(coffset)
9974887Schin 				*coffset = (cp-first);
9984887Schin 			return(line);
9994887Schin 		}
10004887Schin 		if(!coffset)
10014887Schin 			break;
10024887Schin 		if(*cp=='\n')
10034887Schin 			line++;
10044887Schin #if SHOPT_MULTIBYTE
10054887Schin 		if((c=mbsize(cp)) < 0)
10064887Schin 			c = 1;
10074887Schin #endif /* SHOPT_MULTIBYTE */
10084887Schin 		cp += c;
10094887Schin 		m -= c;
10104887Schin 	}
10114887Schin 	return(-1);
10124887Schin }
10134887Schin 
10144887Schin 
10154887Schin #if SHOPT_ESH || SHOPT_VSH
10164887Schin /*
10174887Schin  * copy command <command> from history file to s1
10184887Schin  * at most <size> characters copied
10194887Schin  * if s1==0 the number of lines for the command is returned
10204887Schin  * line=linenumber  for emacs copy and only this line of command will be copied
10214887Schin  * line < 0 for full command copy
10224887Schin  * -1 returned if there is no history file
10234887Schin  */
10244887Schin 
hist_copy(char * s1,int size,int command,int line)10254887Schin int hist_copy(char *s1,int size,int command,int line)
10264887Schin {
10274887Schin 	register int c;
10284887Schin 	register History_t *hp = sh_getinterp()->hist_ptr;
10294887Schin 	register int count = 0;
10304887Schin 	register char *s1max = s1+size;
10314887Schin 	if(!hp)
10324887Schin 		return(-1);
10334887Schin 	hist_seek(hp,command);
10344887Schin 	while ((c = sfgetc(hp->histfp)) && c!=EOF)
10354887Schin 	{
10364887Schin 		if(c=='\n')
10374887Schin 		{
10384887Schin 			if(count++ ==line)
10394887Schin 				break;
10404887Schin 			else if(line >= 0)
10414887Schin 				continue;
10424887Schin 		}
10434887Schin 		if(s1 && (line<0 || line==count))
10444887Schin 		{
10454887Schin 			if(s1 >= s1max)
10464887Schin 			{
10474887Schin 				*--s1 = 0;
10484887Schin 				break;
10494887Schin 			}
10504887Schin 			*s1++ = c;
10514887Schin 		}
10524887Schin 
10534887Schin 	}
10544887Schin 	sfseek(hp->histfp,(off_t)0,SEEK_END);
10554887Schin 	if(s1==0)
10564887Schin 		return(count);
10574887Schin 	if(count && (c= *(s1-1)) == '\n')
10584887Schin 		s1--;
10594887Schin 	*s1 = '\0';
10604887Schin 	return(count);
10614887Schin }
10624887Schin 
10634887Schin /*
10644887Schin  * return word number <word> from command number <command>
10654887Schin  */
10664887Schin 
hist_word(char * string,int size,int word)10674887Schin char *hist_word(char *string,int size,int word)
10684887Schin {
10694887Schin 	register int c;
10704887Schin 	register char *s1 = string;
10714887Schin 	register unsigned char *cp = (unsigned char*)s1;
10724887Schin 	register int flag = 0;
10734887Schin 	History_t *hp = hist_ptr;
10744887Schin 	if(!hp)
10754887Schin 		return(NIL(char*));
10764887Schin 	hist_copy(string,size,(int)hp->histind-1,-1);
10774887Schin 	for(;c = *cp;cp++)
10784887Schin 	{
10794887Schin 		c = isspace(c);
10804887Schin 		if(c && flag)
10814887Schin 		{
10824887Schin 			*cp = 0;
10834887Schin 			if(--word==0)
10844887Schin 				break;
10854887Schin 			flag = 0;
10864887Schin 		}
10874887Schin 		else if(c==0 && flag==0)
10884887Schin 		{
10894887Schin 			s1 = (char*)cp;
10904887Schin 			flag++;
10914887Schin 		}
10924887Schin 	}
10934887Schin 	*cp = 0;
10944887Schin 	if(s1 != string)
10954887Schin 		strcpy(string,s1);
10964887Schin 	return(string);
10974887Schin }
10984887Schin 
10994887Schin #endif	/* SHOPT_ESH */
11004887Schin 
11014887Schin #if SHOPT_ESH
11024887Schin /*
11034887Schin  * given the current command and line number,
11044887Schin  * and number of lines back or foward,
11054887Schin  * compute the new command and line number.
11064887Schin  */
11074887Schin 
hist_locate(History_t * hp,register int command,register int line,int lines)11084887Schin Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
11094887Schin {
11104887Schin 	Histloc_t next;
11114887Schin 	line += lines;
11124887Schin 	if(!hp)
11134887Schin 	{
11144887Schin 		command = -1;
11154887Schin 		goto done;
11164887Schin 	}
11174887Schin 	if(lines > 0)
11184887Schin 	{
11194887Schin 		register int count;
11204887Schin 		while(command <= hp->histind)
11214887Schin 		{
11224887Schin 			count = hist_copy(NIL(char*),0, command,-1);
11234887Schin 			if(count > line)
11244887Schin 				goto done;
11254887Schin 			line -= count;
11264887Schin 			command++;
11274887Schin 		}
11284887Schin 	}
11294887Schin 	else
11304887Schin 	{
11314887Schin 		register int least = (int)hp->histind-hp->histsize;
11324887Schin 		while(1)
11334887Schin 		{
11344887Schin 			if(line >=0)
11354887Schin 				goto done;
11364887Schin 			if(--command < least)
11374887Schin 				break;
11384887Schin 			line += hist_copy(NIL(char*),0, command,-1);
11394887Schin 		}
11404887Schin 		command = -1;
11414887Schin 	}
11424887Schin done:
11434887Schin 	next.hist_line = line;
11444887Schin 	next.hist_command = command;
11454887Schin 	return(next);
11464887Schin }
11474887Schin #endif	/* SHOPT_ESH */
11484887Schin 
11494887Schin 
11504887Schin /*
11514887Schin  * Handle history file exceptions
11524887Schin  */
11534887Schin #ifdef SF_BUFCONST
hist_exceptf(Sfio_t * fp,int type,void * data,Sfdisc_t * handle)11544887Schin static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
11554887Schin #else
11564887Schin static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
11574887Schin #endif
11584887Schin {
11594887Schin 	register int newfd,oldfd;
11604887Schin 	History_t *hp = (History_t*)handle;
11614887Schin 	if(type==SF_WRITE)
11624887Schin 	{
11634887Schin 		if(errno==ENOSPC || hp->histwfail++ >= 10)
11644887Schin 			return(0);
11654887Schin 		/* write failure could be NFS problem, try to re-open */
11664887Schin 		close(oldfd=sffileno(fp));
11674887Schin 		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
11684887Schin 		{
11694887Schin 			if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
11704887Schin 				return(-1);
11714887Schin 			fcntl(oldfd,F_SETFD,FD_CLOEXEC);
11724887Schin 			close(newfd);
11734887Schin 			if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
11744887Schin 			{
11754887Schin 				register int index = hp->histind;
11764887Schin 				lseek(oldfd,(off_t)2,SEEK_SET);
11774887Schin 				hp->histcnt = 2;
11784887Schin 				hp->histind = 1;
11794887Schin 				hp->histcmds[1] = 2;
11804887Schin 				hist_eof(hp);
11814887Schin 				hp->histmarker = hp->histcnt;
11824887Schin 				hp->histind = index;
11834887Schin 			}
11844887Schin 			return(1);
11854887Schin 		}
11864887Schin 		errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
11874887Schin 		return(-1);
11884887Schin 	}
11894887Schin 	return(0);
11904887Schin }
1191