1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/*
22 *   History file manipulation routines
23 *
24 *   David Korn
25 *   AT&T Labs
26 *
27 */
28
29/*
30 * Each command in the history file starts on an even byte is null terminated.
31 * The first byte must contain the special character HIST_UNDO and the second
32 * byte is the version number.  The sequence HIST_UNDO 0, following a command,
33 * nullifies the previous command. A six byte sequence starting with
34 * HIST_CMDNO is used to store the command number so that it is not necessary
35 * to read the file from beginning to end to get to the last block of
36 * commands.  This format of this sequence is different in version 1
37 * then in version 0.  Version 1 allows commands to use the full 8 bit
38 * character set.  It can understand version 0 format files.
39 */
40
41
42#define HIST_MAX	(sizeof(int)*HIST_BSIZE)
43#define HIST_BIG	(0100000-1024)	/* 1K less than maximum short */
44#define HIST_LINE	32		/* typical length for history line */
45#define HIST_MARKSZ	6
46#define HIST_RECENT	600
47#define HIST_UNDO	0201		/* invalidate previous command */
48#define HIST_CMDNO	0202		/* next 3 bytes give command number */
49#define HIST_BSIZE	4096		/* size of history file buffer */
50#define HIST_DFLT	512		/* default size of history list */
51
52#if SHOPT_AUDIT
53#   define _HIST_AUDIT	Sfio_t	*auditfp; \
54			char	*tty; \
55			int	auditmask;
56#else
57#   define _HIST_AUDIT
58#endif
59
60#define _HIST_PRIVATE \
61	void	*histshell; \
62	off_t	histcnt;	/* offset into history file */\
63	off_t	histmarker;	/* offset of last command marker */ \
64	int	histflush;	/* set if flushed outside of hflush() */\
65	int	histmask;	/* power of two mask for histcnt */ \
66	char	histbuff[HIST_BSIZE+1];	/* history file buffer */ \
67	int	histwfail; \
68	_HIST_AUDIT \
69	off_t	histcmds[2];	/* offset for recent commands, must be last */
70
71#define hist_ind(hp,c)	((int)((c)&(hp)->histmask))
72
73#include	<ast.h>
74#include	<sfio.h>
75#include	"FEATURE/time"
76#include	<error.h>
77#include	<ls.h>
78#if KSHELL
79#   include	"defs.h"
80#   include	"variables.h"
81#   include	"path.h"
82#   include	"builtins.h"
83#   include	"io.h"
84#else
85#   include	<ctype.h>
86#endif	/* KSHELL */
87#include	"history.h"
88
89#if !KSHELL
90#   define new_of(type,x)	((type*)malloc((unsigned)sizeof(type)+(x)))
91#   define NIL(type)		((type)0)
92#   define path_relative(s,x)	(s,x)
93#   ifdef __STDC__
94#	define nv_getval(s)	getenv(#s)
95#   else
96#	define nv_getval(s)	getenv("s")
97#   endif /* __STDC__ */
98#   define e_unknown	 	"unknown"
99#   define sh_translate(x)	(x)
100    char login_sh =		0;
101    char hist_fname[] =		"/.history";
102#endif	/* KSHELL */
103
104#ifndef O_BINARY
105#   define O_BINARY	0
106#endif /* O_BINARY */
107
108int	_Hist = 0;
109static void	hist_marker(char*,long);
110static History_t* hist_trim(History_t*, int);
111static int	hist_nearend(History_t*,Sfio_t*, off_t);
112static int	hist_check(int);
113static int	hist_clean(int);
114#ifdef SF_BUFCONST
115    static ssize_t  hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
116    static int      hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
117#else
118    static int	hist_write(Sfio_t*, const void*, int, Sfdisc_t*);
119    static int	hist_exceptf(Sfio_t*, int, Sfdisc_t*);
120#endif
121
122
123static int	histinit;
124static mode_t	histmode;
125static History_t *wasopen;
126static History_t *hist_ptr;
127
128#if SHOPT_ACCTFILE
129    static int	acctfd;
130    static char *logname;
131#   include <pwd.h>
132
133    static int  acctinit(History_t *hp)
134    {
135	register char *cp, *acctfile;
136	Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0);
137
138	if(!np || !(acctfile=nv_getval(np)))
139		return(0);
140	if(!(cp = getlogin()))
141	{
142		struct passwd *userinfo = getpwuid(getuid());
143		if(userinfo)
144			cp = userinfo->pw_name;
145		else
146			cp = "unknown";
147	}
148	logname = strdup(cp);
149	if((acctfd=sh_open(acctfile,
150		O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 &&
151	    (unsigned)acctfd < 10)
152	{
153		int n;
154		if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0)
155		{
156			close(acctfd);
157			acctfd = n;
158		}
159	}
160	if(acctfd < 0)
161	{
162		acctfd = 0;
163		return(0);
164	}
165	if(sh_isdevfd(acctfile))
166	{
167		char newfile[16];
168		sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd);
169		nv_putval(np,newfile,NV_RDONLY);
170	}
171	else
172		fcntl(acctfd,F_SETFD,FD_CLOEXEC);
173	return(1);
174    }
175#endif /* SHOPT_ACCTFILE */
176
177#if SHOPT_AUDIT
178static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len)
179{
180	char	*cp, *last;
181	int	id1, id2, r=0, n, fd;
182	if((fd=open(name, O_RDONLY)) < 0)
183		return(0);
184	if((n = read(fd, logbuf,len-1)) < 0)
185		goto done;
186	while(logbuf[n-1]=='\n')
187		n--;
188	logbuf[n] = 0;
189	if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' ')))
190		goto done;
191	*cp = 0;
192	do
193	{
194		cp++;
195		id1 = id2 = strtol(cp,&last,10);
196		if(*last=='-')
197			id1 = strtol(last+1,&last,10);
198		if(shgd->euserid >=id1 && shgd->euserid <= id2)
199			r |= 1;
200		if(shgd->userid >=id1 && shgd->userid <= id2)
201			r |= 2;
202		cp = last;
203	}
204	while(*cp==';' ||  *cp==' ');
205done:
206	close(fd);
207	return(r);
208
209}
210#endif /*SHOPT_AUDIT*/
211
212static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
213static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
214
215static void hist_touch(void *handle)
216{
217	touch((char*)handle, (time_t)0, (time_t)0, 0);
218}
219
220/*
221 * open the history file
222 * if HISTNAME is not given and userid==0 then no history file.
223 * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is
224 * cleaned up.
225 * hist_open() returns 1, if history file is open
226 */
227int  sh_histinit(void *sh_context)
228{
229	Shell_t *shp = (Shell_t*)sh_context;
230	register int fd;
231	register History_t *hp;
232	register char *histname;
233	char *fname=0;
234	int histmask, maxlines, hist_start=0;
235	register char *cp;
236	register off_t hsize = 0;
237
238	if(shgd->hist_ptr=hist_ptr)
239		return(1);
240	if(!(histname = nv_getval(HISTFILE)))
241	{
242		int offset = staktell();
243		if(cp=nv_getval(HOME))
244			stakputs(cp);
245		stakputs(hist_fname);
246		stakputc(0);
247		stakseek(offset);
248		histname = stakptr(offset);
249	}
250#ifdef future
251	if(hp=wasopen)
252	{
253		/* reuse history file if same name */
254		wasopen = 0;
255		shgd->hist_ptr = hist_ptr = hp;
256		if(strcmp(histname,hp->histname)==0)
257			return(1);
258		else
259			hist_free();
260	}
261#endif
262retry:
263	cp = path_relative(shp,histname);
264	if(!histinit)
265		histmode = S_IRUSR|S_IWUSR;
266	if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0)
267	{
268		hsize=lseek(fd,(off_t)0,SEEK_END);
269	}
270	if((unsigned)fd <=2)
271	{
272		int n;
273		if((n=fcntl(fd,F_DUPFD,10))>=0)
274		{
275			close(fd);
276			fd=n;
277		}
278	}
279	/* make sure that file has history file format */
280	if(hsize && hist_check(fd))
281	{
282		close(fd);
283		hsize = 0;
284		if(unlink(cp)>=0)
285			goto retry;
286		fd = -1;
287	}
288	if(fd < 0)
289	{
290#if KSHELL
291		/* don't allow root a history_file in /tmp */
292		if(shgd->userid)
293#endif	/* KSHELL */
294		{
295			if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*))))
296				return(0);
297			fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
298		}
299	}
300	if(fd<0)
301		return(0);
302	/* set the file to close-on-exec */
303	fcntl(fd,F_SETFD,FD_CLOEXEC);
304	if(cp=nv_getval(HISTSIZE))
305		maxlines = (unsigned)strtol(cp, (char**)0, 10);
306	else
307		maxlines = HIST_DFLT;
308	for(histmask=16;histmask <= maxlines; histmask <<=1 );
309	if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t))))
310	{
311		close(fd);
312		return(0);
313	}
314	shgd->hist_ptr = hist_ptr = hp;
315	hp->histshell = (void*)shp;
316	hp->histsize = maxlines;
317	hp->histmask = histmask;
318	hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE);
319	memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1));
320	hp->histind = 1;
321	hp->histcmds[1] = 2;
322	hp->histcnt = 2;
323	hp->histname = strdup(histname);
324	hp->histdisc = hist_disc;
325	if(hsize==0)
326	{
327		/* put special characters at front of file */
328		sfwrite(hp->histfp,(char*)hist_stamp,2);
329		sfsync(hp->histfp);
330	}
331	/* initialize history list */
332	else
333	{
334		int first,last;
335		off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE;
336		hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size);
337		histinit = 1;
338		hist_eof(hp);	 /* this sets histind to last command */
339		if((hist_start = (last=(int)hp->histind)-maxlines) <=0)
340			hist_start = 1;
341		mark = hp->histmarker;
342		while(first > hist_start)
343		{
344			size += size;
345			first = hist_nearend(hp,hp->histfp,hsize-size);
346			hp->histind = first;
347		}
348		histinit = hist_start;
349		hist_eof(hp);
350		if(!histinit)
351		{
352			sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET);
353			hp->histind = last;
354			hp->histmarker = mark;
355		}
356		histinit = 0;
357	}
358	if(fname)
359	{
360		unlink(fname);
361		free((void*)fname);
362	}
363	if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX)
364	{
365#ifdef DEBUG
366		sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize);
367		sfsync(sfstderr);
368#endif /* DEBUG */
369		hp = hist_trim(hp,(int)hp->histind-maxlines);
370	}
371	sfdisc(hp->histfp,&hp->histdisc);
372#if KSHELL
373	(HISTCUR)->nvalue.lp = (&hp->histind);
374#endif /* KSHELL */
375	sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname);
376#if SHOPT_ACCTFILE
377	if(sh_isstate(SH_INTERACTIVE))
378		acctinit(hp);
379#endif /* SHOPT_ACCTFILE */
380#if SHOPT_AUDIT
381	{
382		char buff[SF_BUFSIZE];
383		hp->auditfp = 0;
384		if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff))))
385		{
386			if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10)
387			{
388				int n;
389				if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0)
390				{
391					sh_close(fd);
392					fd = n;
393				}
394			}
395			if(fd>=0)
396			{
397				fcntl(fd,F_SETFD,FD_CLOEXEC);
398				hp->tty = strdup(ttyname(2));
399				hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE);
400			}
401		}
402	}
403#endif
404	return(1);
405}
406
407/*
408 * close the history file and free the space
409 */
410
411void hist_close(register History_t *hp)
412{
413	sfclose(hp->histfp);
414#if SHOPT_AUDIT
415	if(hp->auditfp)
416	{
417		if(hp->tty)
418			free((void*)hp->tty);
419		sfclose(hp->auditfp);
420	}
421#endif /* SHOPT_AUDIT */
422	free((char*)hp);
423	hist_ptr = 0;
424	shgd->hist_ptr = 0;
425#if SHOPT_ACCTFILE
426	if(acctfd)
427	{
428		close(acctfd);
429		acctfd = 0;
430	}
431#endif /* SHOPT_ACCTFILE */
432}
433
434/*
435 * check history file format to see if it begins with special byte
436 */
437static int hist_check(register int fd)
438{
439	unsigned char magic[2];
440	lseek(fd,(off_t)0,SEEK_SET);
441	if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO))
442		return(1);
443	return(0);
444}
445
446/*
447 * clean out history file OK if not modified in HIST_RECENT seconds
448 */
449static int hist_clean(int fd)
450{
451	struct stat statb;
452	return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT);
453}
454
455/*
456 * Copy the last <n> commands to a new file and make this the history file
457 */
458
459static History_t* hist_trim(History_t *hp, int n)
460{
461	register char *cp;
462	register int incmd=1, c=0;
463	register History_t *hist_new, *hist_old = hp;
464	char *buff, *endbuff, *tmpname=0;
465	off_t oldp,newp;
466	struct stat statb;
467	unlink(hist_old->histname);
468	if(access(hist_old->histname,F_OK) >= 0)
469	{
470		/* The unlink can fail on windows 95 */
471		int fd;
472		char *last, *name=hist_old->histname;
473		close(sffileno(hist_old->histfp));
474		tmpname = (char*)malloc(strlen(name)+14);
475		if(last = strrchr(name,'/'))
476		{
477			*last = 0;
478			pathtmp(tmpname,name,"hist",NIL(int*));
479			*last = '/';
480		}
481		else
482			pathtmp(tmpname,".","hist",NIL(int*));
483		if(rename(name,tmpname) < 0)
484		{
485			free(tmpname);
486			tmpname = name;
487		}
488		fd = open(tmpname,O_RDONLY);
489		sfsetfd(hist_old->histfp,fd);
490		if(tmpname==name)
491			tmpname = 0;
492	}
493	hist_ptr = 0;
494	if(fstat(sffileno(hist_old->histfp),&statb)>=0)
495	{
496		histinit = 1;
497		histmode =  statb.st_mode;
498	}
499	if(!sh_histinit(hp->histshell))
500	{
501		/* use the old history file */
502		return hist_ptr = hist_old;
503	}
504	hist_new = hist_ptr;
505	hist_ptr = hist_old;
506	if(--n < 0)
507		n = 0;
508	newp = hist_seek(hist_old,++n);
509	while(1)
510	{
511		if(!incmd)
512		{
513			c = hist_ind(hist_new,++hist_new->histind);
514			hist_new->histcmds[c] = hist_new->histcnt;
515			if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
516			{
517				char locbuff[HIST_MARKSZ];
518				hist_marker(locbuff,hist_new->histind);
519				sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
520				hist_new->histcnt += HIST_MARKSZ;
521				hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
522			}
523			oldp = newp;
524			newp = hist_seek(hist_old,++n);
525			if(newp <=oldp)
526				break;
527		}
528		if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
529			break;
530		*(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0;
531		/* copy to null byte */
532		incmd = 0;
533		while(*cp++);
534		if(cp > endbuff)
535			incmd = 1;
536		else if(*cp==0)
537			cp++;
538		if(cp > endbuff)
539			cp = endbuff;
540		c = cp-buff;
541		hist_new->histcnt += c;
542		sfwrite(hist_new->histfp,buff,c);
543	}
544	hist_cancel(hist_new);
545	sfclose(hist_old->histfp);
546	if(tmpname)
547	{
548		unlink(tmpname);
549		free(tmpname);
550	}
551	free((char*)hist_old);
552	return hist_ptr = hist_new;
553}
554
555/*
556 * position history file at size and find next command number
557 */
558static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size)
559{
560        register unsigned char *cp, *endbuff;
561        register int n, incmd=1;
562        unsigned char *buff, marker[4];
563	if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
564		goto begin;
565	/* skip to marker command and return the number */
566	/* numbering commands occur after a null and begin with HIST_CMDNO */
567        while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR))
568        {
569		n = sfvalue(iop);
570                *(endbuff=cp+n) = 0;
571                while(1)
572                {
573			/* check for marker */
574                        if(!incmd && *cp++==HIST_CMDNO && *cp==0)
575                        {
576                                n = cp+1 - buff;
577                                incmd = -1;
578                                break;
579                        }
580                        incmd = 0;
581                        while(*cp++);
582                        if(cp>endbuff)
583                        {
584                                incmd = 1;
585                                break;
586                        }
587                        if(*cp==0 && ++cp>endbuff)
588                                break;
589                }
590                size += n;
591		sfread(iop,(char*)buff,n);
592		if(incmd < 0)
593                {
594			if((n=sfread(iop,(char*)marker,4))==4)
595			{
596				n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
597				if(n < size/2)
598				{
599					hp->histmarker = hp->histcnt = size+4;
600					return(n);
601				}
602				n=4;
603			}
604			if(n >0)
605				size += n;
606			incmd = 0;
607		}
608	}
609begin:
610	sfseek(iop,(off_t)2,SEEK_SET);
611	hp->histmarker = hp->histcnt = 2L;
612	return(1);
613}
614
615/*
616 * This routine reads the history file from the present position
617 * to the end-of-file and puts the information in the in-core
618 * history table
619 * Note that HIST_CMDNO is only recognized at the beginning of a command
620 * and that HIST_UNDO as the first character of a command is skipped
621 * unless it is followed by 0.  If followed by 0 then it cancels
622 * the previous command.
623 */
624
625void hist_eof(register History_t *hp)
626{
627	register char *cp,*first,*endbuff;
628	register int incmd = 0;
629	register off_t count = hp->histcnt;
630	int oldind,n,skip=0;
631	off_t last = sfseek(hp->histfp,(off_t)0,SEEK_END);
632	if(last < count)
633	{
634		last = -1;
635		count = 2+HIST_MARKSZ;
636		oldind = hp->histind;
637		if((hp->histind -= hp->histsize) < 0)
638			hp->histind = 1;
639	}
640again:
641	sfseek(hp->histfp,count,SEEK_SET);
642        while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
643	{
644		n = sfvalue(hp->histfp);
645		*(endbuff = cp+n) = 0;
646		first = cp += skip;
647		while(1)
648		{
649			while(!incmd)
650			{
651				if(cp>first)
652				{
653					count += (cp-first);
654					n = hist_ind(hp, ++hp->histind);
655#ifdef future
656					if(count==hp->histcmds[n])
657					{
658	sfprintf(sfstderr,"count match n=%d\n",n);
659						if(histinit)
660						{
661							histinit = 0;
662							return;
663						}
664					}
665					else if(n>=histinit)
666#endif
667						hp->histcmds[n] = count;
668					first = cp;
669				}
670				switch(*((unsigned char*)(cp++)))
671				{
672					case HIST_CMDNO:
673						if(*cp==0)
674						{
675							hp->histmarker=count+2;
676							cp += (HIST_MARKSZ-1);
677							hp->histind--;
678							if(!histinit && (cp <= endbuff))
679							{
680								unsigned char *marker = (unsigned char*)(cp-4);
681								hp->histind = ((marker[0]<<16)|(marker[1]<<8)|marker[2] -1);
682							}
683						}
684						break;
685					case HIST_UNDO:
686						if(*cp==0)
687						{
688							cp+=1;
689							hp->histind-=2;
690						}
691						break;
692					default:
693						cp--;
694						incmd = 1;
695				}
696				if(cp > endbuff)
697				{
698					cp++;
699					goto refill;
700				}
701			}
702			first = cp;
703			while(*cp++);
704			if(cp > endbuff)
705				break;
706			incmd = 0;
707			while(*cp==0)
708			{
709				if(++cp > endbuff)
710					goto refill;
711			}
712		}
713	refill:
714		count += (--cp-first);
715		skip = (cp-endbuff);
716		if(!incmd && !skip)
717			hp->histcmds[hist_ind(hp,++hp->histind)] = count;
718	}
719	hp->histcnt = count;
720	if(incmd && last)
721	{
722		sfputc(hp->histfp,0);
723		hist_cancel(hp);
724		count = 2;
725		skip = 0;
726		oldind -= hp->histind;
727		hp->histind = hp->histind-hp->histsize + oldind +2;
728		if(hp->histind<0)
729			hp->histind = 1;
730		if(last<0)
731		{
732			char	buff[HIST_MARKSZ];
733			int	fd = open(hp->histname,O_RDWR);
734			if(fd>=0)
735			{
736				hist_marker(buff,hp->histind);
737				write(fd,(char*)hist_stamp,2);
738				write(fd,buff,HIST_MARKSZ);
739				close(fd);
740			}
741		}
742		last = 0;
743		goto again;
744	}
745}
746
747/*
748 * This routine will cause the previous command to be cancelled
749 */
750
751void hist_cancel(register History_t *hp)
752{
753	register int c;
754	if(!hp)
755		return;
756	sfputc(hp->histfp,HIST_UNDO);
757	sfputc(hp->histfp,0);
758	sfsync(hp->histfp);
759	hp->histcnt += 2;
760	c = hist_ind(hp,--hp->histind);
761	hp->histcmds[c] = hp->histcnt;
762}
763
764/*
765 * flush the current history command
766 */
767
768void hist_flush(register History_t *hp)
769{
770	register char *buff;
771	if(hp)
772	{
773		if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
774		{
775			hp->histflush = sfvalue(hp->histfp)+1;
776			sfwrite(hp->histfp,buff,0);
777		}
778		else
779			hp->histflush=0;
780		if(sfsync(hp->histfp)<0)
781		{
782			hist_close(hp);
783			if(!sh_histinit(hp->histshell))
784				sh_offoption(SH_HISTORY);
785		}
786		hp->histflush = 0;
787	}
788}
789
790/*
791 * This is the write discipline for the history file
792 * When called from hist_flush(), trailing newlines are deleted and
793 * a zero byte.  Line sequencing is added as required
794 */
795
796#ifdef SF_BUFCONST
797static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
798#else
799static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
800#endif
801{
802	register History_t *hp = (History_t*)handle;
803	register char *bufptr = ((char*)buff)+insize;
804	register int c,size = insize;
805	register off_t cur;
806	int saved=0;
807	char saveptr[HIST_MARKSZ];
808	if(!hp->histflush)
809		return(write(sffileno(iop),(char*)buff,size));
810	if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
811	{
812		errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
813		return(-1);
814	}
815	hp->histcnt = cur;
816	/* remove whitespace from end of commands */
817	while(--bufptr >= (char*)buff)
818	{
819		c= *bufptr;
820		if(!isspace(c))
821		{
822			if(c=='\\' && *(bufptr+1)!='\n')
823				bufptr++;
824			break;
825		}
826	}
827	/* don't count empty lines */
828	if(++bufptr <= (char*)buff)
829		return(insize);
830	*bufptr++ = '\n';
831	*bufptr++ = 0;
832	size = bufptr - (char*)buff;
833#if	 SHOPT_AUDIT
834	if(hp->auditfp)
835	{
836		time_t	t=time((time_t*)0);
837		sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shgd->euserid:shgd->userid,t,hp->tty,size,buff,0);
838		sfsync(hp->auditfp);
839	}
840#endif	/* SHOPT_AUDIT */
841#if	SHOPT_ACCTFILE
842	if(acctfd)
843	{
844		int timechars, offset;
845		offset = staktell();
846		stakputs(buff);
847		stakseek(staktell() - 1);
848		timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
849		lseek(acctfd, (off_t)0, SEEK_END);
850		write(acctfd, stakptr(offset), size - 2 + timechars);
851		stakseek(offset);
852
853	}
854#endif /* SHOPT_ACCTFILE */
855	if(size&01)
856	{
857		size++;
858		*bufptr++ = 0;
859	}
860	hp->histcnt +=  size;
861	c = hist_ind(hp,++hp->histind);
862	hp->histcmds[c] = hp->histcnt;
863	if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
864	{
865		memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
866		saved=1;
867		hp->histcnt += HIST_MARKSZ;
868		hist_marker(bufptr,hp->histind);
869		hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
870		size += HIST_MARKSZ;
871	}
872	errno = 0;
873	size = write(sffileno(iop),(char*)buff,size);
874	if(saved)
875		memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
876	if(size>=0)
877	{
878		hp->histwfail = 0;
879		return(insize);
880	}
881	return(-1);
882}
883
884/*
885 * Put history sequence number <n> into buffer <buff>
886 * The buffer must be large enough to hold HIST_MARKSZ chars
887 */
888
889static void hist_marker(register char *buff,register long cmdno)
890{
891	*buff++ = HIST_CMDNO;
892	*buff++ = 0;
893	*buff++ = (cmdno>>16);
894	*buff++ = (cmdno>>8);
895	*buff++ = cmdno;
896	*buff++ = 0;
897}
898
899/*
900 * return byte offset in history file for command <n>
901 */
902off_t hist_tell(register History_t *hp, int n)
903{
904	return(hp->histcmds[hist_ind(hp,n)]);
905}
906
907/*
908 * seek to the position of command <n>
909 */
910off_t hist_seek(register History_t *hp, int n)
911{
912	return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
913}
914
915/*
916 * write the command starting at offset <offset> onto file <outfile>.
917 * if character <last> appears before newline it is deleted
918 * each new-line character is replaced with string <nl>.
919 */
920
921void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
922{
923	register int oldc=0;
924	register int c;
925	if(offset<0 || !hp)
926	{
927		sfputr(outfile,sh_translate(e_unknown),'\n');
928		return;
929	}
930	sfseek(hp->histfp,offset,SEEK_SET);
931	while((c = sfgetc(hp->histfp)) != EOF)
932	{
933		if(c && oldc=='\n')
934			sfputr(outfile,nl,-1);
935		else if(last && (c==0 || (c=='\n' && oldc==last)))
936			return;
937		else if(oldc)
938			sfputc(outfile,oldc);
939		oldc = c;
940		if(c==0)
941			return;
942	}
943	return;
944}
945
946/*
947 * find index for last line with given string
948 * If flag==0 then line must begin with string
949 * direction < 1 for backwards search
950*/
951
952Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
953{
954	register int index2;
955	off_t offset;
956	int *coffset=0;
957	Histloc_t location;
958	location.hist_command = -1;
959	location.hist_char = 0;
960	location.hist_line = 0;
961	if(!hp)
962		return(location);
963	/* leading ^ means beginning of line unless escaped */
964	if(flag)
965	{
966		index2 = *string;
967		if(index2=='\\')
968			string++;
969		else if(index2=='^')
970		{
971			flag=0;
972			string++;
973		}
974	}
975	if(flag)
976		coffset = &location.hist_char;
977	index2 = (int)hp->histind;
978	if(direction<0)
979	{
980		index2 -= hp->histsize;
981		if(index2<1)
982			index2 = 1;
983		if(index1 <= index2)
984			return(location);
985	}
986	else if(index1 >= index2)
987		return(location);
988	while(index1!=index2)
989	{
990		direction>0?++index1:--index1;
991		offset = hist_tell(hp,index1);
992		if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
993		{
994			location.hist_command = index1;
995			return(location);
996		}
997#if KSHELL
998		/* allow a search to be aborted */
999		if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET)
1000			break;
1001#endif /* KSHELL */
1002	}
1003	return(location);
1004}
1005
1006/*
1007 * search for <string> in history file starting at location <offset>
1008 * If coffset==0 then line must begin with string
1009 * returns the line number of the match if successful, otherwise -1
1010 */
1011
1012int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
1013{
1014	register unsigned char *first, *cp;
1015	register int m,n,c=1,line=0;
1016#if SHOPT_MULTIBYTE
1017	mbinit();
1018#endif /* SHOPT_MULTIBYTE */
1019	sfseek(hp->histfp,offset,SEEK_SET);
1020	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
1021		return(-1);
1022	m = sfvalue(hp->histfp);
1023	n = strlen(string);
1024	while(m > n)
1025	{
1026		if(*cp==*string && memcmp(cp,string,n)==0)
1027		{
1028			if(coffset)
1029				*coffset = (cp-first);
1030			return(line);
1031		}
1032		if(!coffset)
1033			break;
1034		if(*cp=='\n')
1035			line++;
1036#if SHOPT_MULTIBYTE
1037		if((c=mbsize(cp)) < 0)
1038			c = 1;
1039#endif /* SHOPT_MULTIBYTE */
1040		cp += c;
1041		m -= c;
1042	}
1043	return(-1);
1044}
1045
1046
1047#if SHOPT_ESH || SHOPT_VSH
1048/*
1049 * copy command <command> from history file to s1
1050 * at most <size> characters copied
1051 * if s1==0 the number of lines for the command is returned
1052 * line=linenumber  for emacs copy and only this line of command will be copied
1053 * line < 0 for full command copy
1054 * -1 returned if there is no history file
1055 */
1056
1057int hist_copy(char *s1,int size,int command,int line)
1058{
1059	register int c;
1060	register History_t *hp = shgd->hist_ptr;
1061	register int count = 0;
1062	register char *s1max = s1+size;
1063	if(!hp)
1064		return(-1);
1065	hist_seek(hp,command);
1066	while ((c = sfgetc(hp->histfp)) && c!=EOF)
1067	{
1068		if(c=='\n')
1069		{
1070			if(count++ ==line)
1071				break;
1072			else if(line >= 0)
1073				continue;
1074		}
1075		if(s1 && (line<0 || line==count))
1076		{
1077			if(s1 >= s1max)
1078			{
1079				*--s1 = 0;
1080				break;
1081			}
1082			*s1++ = c;
1083		}
1084
1085	}
1086	sfseek(hp->histfp,(off_t)0,SEEK_END);
1087	if(s1==0)
1088		return(count);
1089	if(count && (c= *(s1-1)) == '\n')
1090		s1--;
1091	*s1 = '\0';
1092	return(count);
1093}
1094
1095/*
1096 * return word number <word> from command number <command>
1097 */
1098
1099char *hist_word(char *string,int size,int word)
1100{
1101	register int c;
1102	register char *s1 = string;
1103	register unsigned char *cp = (unsigned char*)s1;
1104	register int flag = 0;
1105	History_t *hp = hist_ptr;
1106	if(!hp)
1107		return(NIL(char*));
1108	hist_copy(string,size,(int)hp->histind-1,-1);
1109	for(;c = *cp;cp++)
1110	{
1111		c = isspace(c);
1112		if(c && flag)
1113		{
1114			*cp = 0;
1115			if(--word==0)
1116				break;
1117			flag = 0;
1118		}
1119		else if(c==0 && flag==0)
1120		{
1121			s1 = (char*)cp;
1122			flag++;
1123		}
1124	}
1125	*cp = 0;
1126	if(s1 != string)
1127		strcpy(string,s1);
1128	return(string);
1129}
1130
1131#endif	/* SHOPT_ESH */
1132
1133#if SHOPT_ESH
1134/*
1135 * given the current command and line number,
1136 * and number of lines back or foward,
1137 * compute the new command and line number.
1138 */
1139
1140Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
1141{
1142	Histloc_t next;
1143	line += lines;
1144	if(!hp)
1145	{
1146		command = -1;
1147		goto done;
1148	}
1149	if(lines > 0)
1150	{
1151		register int count;
1152		while(command <= hp->histind)
1153		{
1154			count = hist_copy(NIL(char*),0, command,-1);
1155			if(count > line)
1156				goto done;
1157			line -= count;
1158			command++;
1159		}
1160	}
1161	else
1162	{
1163		register int least = (int)hp->histind-hp->histsize;
1164		while(1)
1165		{
1166			if(line >=0)
1167				goto done;
1168			if(--command < least)
1169				break;
1170			line += hist_copy(NIL(char*),0, command,-1);
1171		}
1172		command = -1;
1173	}
1174done:
1175	next.hist_line = line;
1176	next.hist_command = command;
1177	return(next);
1178}
1179#endif	/* SHOPT_ESH */
1180
1181
1182/*
1183 * Handle history file exceptions
1184 */
1185#ifdef SF_BUFCONST
1186static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
1187#else
1188static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
1189#endif
1190{
1191	register int newfd,oldfd;
1192	History_t *hp = (History_t*)handle;
1193	if(type==SF_WRITE)
1194	{
1195		if(errno==ENOSPC || hp->histwfail++ >= 10)
1196			return(0);
1197		/* write failure could be NFS problem, try to re-open */
1198		close(oldfd=sffileno(fp));
1199		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
1200		{
1201			if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
1202				return(-1);
1203			fcntl(oldfd,F_SETFD,FD_CLOEXEC);
1204			close(newfd);
1205			if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
1206			{
1207				register int index = hp->histind;
1208				lseek(oldfd,(off_t)2,SEEK_SET);
1209				hp->histcnt = 2;
1210				hp->histind = 1;
1211				hp->histcmds[1] = 2;
1212				hist_eof(hp);
1213				hp->histmarker = hp->histcnt;
1214				hp->histind = index;
1215			}
1216			return(1);
1217		}
1218		errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
1219		return(-1);
1220	}
1221	return(0);
1222}
1223