1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2011 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
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	*buff, *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		hist_eof(hp);	 /* this sets histind to last command */
338		if((hist_start = (last=(int)hp->histind)-maxlines) <=0)
339			hist_start = 1;
340		mark = hp->histmarker;
341		while(first > hist_start)
342		{
343			size += size;
344			first = hist_nearend(hp,hp->histfp,hsize-size);
345			hp->histind = first;
346		}
347		histinit = hist_start;
348		hist_eof(hp);
349		if(!histinit)
350		{
351			sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET);
352			hp->histind = last;
353			hp->histmarker = mark;
354		}
355		histinit = 0;
356	}
357	if(fname)
358	{
359		unlink(fname);
360		free((void*)fname);
361	}
362	if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX)
363	{
364#ifdef DEBUG
365		sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize);
366		sfsync(sfstderr);
367#endif /* DEBUG */
368		hp = hist_trim(hp,(int)hp->histind-maxlines);
369	}
370	sfdisc(hp->histfp,&hp->histdisc);
371#if KSHELL
372	(HISTCUR)->nvalue.lp = (&hp->histind);
373#endif /* KSHELL */
374	sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname);
375#if SHOPT_ACCTFILE
376	if(sh_isstate(SH_INTERACTIVE))
377		acctinit(hp);
378#endif /* SHOPT_ACCTFILE */
379#if SHOPT_AUDIT
380	{
381		char buff[SF_BUFSIZE];
382		hp->auditfp = 0;
383		if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff))))
384		{
385			if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10)
386			{
387				int n;
388				if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0)
389				{
390					sh_close(fd);
391					fd = n;
392				}
393			}
394			if(fd>=0)
395			{
396				fcntl(fd,F_SETFD,FD_CLOEXEC);
397				hp->tty = strdup(ttyname(2));
398				hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE);
399			}
400		}
401	}
402#endif
403	return(1);
404}
405
406/*
407 * close the history file and free the space
408 */
409
410void hist_close(register History_t *hp)
411{
412	sfclose(hp->histfp);
413#if SHOPT_AUDIT
414	if(hp->auditfp)
415	{
416		if(hp->tty)
417			free((void*)hp->tty);
418		sfclose(hp->auditfp);
419	}
420#endif /* SHOPT_AUDIT */
421	free((char*)hp);
422	hist_ptr = 0;
423	shgd->hist_ptr = 0;
424#if SHOPT_ACCTFILE
425	if(acctfd)
426	{
427		close(acctfd);
428		acctfd = 0;
429	}
430#endif /* SHOPT_ACCTFILE */
431}
432
433/*
434 * check history file format to see if it begins with special byte
435 */
436static int hist_check(register int fd)
437{
438	unsigned char magic[2];
439	lseek(fd,(off_t)0,SEEK_SET);
440	if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO))
441		return(1);
442	return(0);
443}
444
445/*
446 * clean out history file OK if not modified in HIST_RECENT seconds
447 */
448static int hist_clean(int fd)
449{
450	struct stat statb;
451	return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT);
452}
453
454/*
455 * Copy the last <n> commands to a new file and make this the history file
456 */
457
458static History_t* hist_trim(History_t *hp, int n)
459{
460	register char *cp;
461	register int incmd=1, c=0;
462	register History_t *hist_new, *hist_old = hp;
463	char *buff, *endbuff, *tmpname=0;
464	off_t oldp,newp;
465	struct stat statb;
466	unlink(hist_old->histname);
467	if(access(hist_old->histname,F_OK) >= 0)
468	{
469		/* The unlink can fail on windows 95 */
470		int fd;
471		char *last, *name=hist_old->histname;
472		close(sffileno(hist_old->histfp));
473		tmpname = (char*)malloc(strlen(name)+14);
474		if(last = strrchr(name,'/'))
475		{
476			*last = 0;
477			pathtmp(tmpname,name,"hist",NIL(int*));
478			*last = '/';
479		}
480		else
481			pathtmp(tmpname,".","hist",NIL(int*));
482		if(rename(name,tmpname) < 0)
483			tmpname = name;
484		fd = open(tmpname,O_RDONLY);
485		sfsetfd(hist_old->histfp,fd);
486		if(tmpname==name)
487			tmpname = 0;
488	}
489	hist_ptr = 0;
490	if(fstat(sffileno(hist_old->histfp),&statb)>=0)
491	{
492		histinit = 1;
493		histmode =  statb.st_mode;
494	}
495	if(!sh_histinit(hp->histshell))
496	{
497		/* use the old history file */
498		return hist_ptr = hist_old;
499	}
500	hist_new = hist_ptr;
501	hist_ptr = hist_old;
502	if(--n < 0)
503		n = 0;
504	newp = hist_seek(hist_old,++n);
505	while(1)
506	{
507		if(!incmd)
508		{
509			c = hist_ind(hist_new,++hist_new->histind);
510			hist_new->histcmds[c] = hist_new->histcnt;
511			if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
512			{
513				char locbuff[HIST_MARKSZ];
514				hist_marker(locbuff,hist_new->histind);
515				sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
516				hist_new->histcnt += HIST_MARKSZ;
517				hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
518			}
519			oldp = newp;
520			newp = hist_seek(hist_old,++n);
521			if(newp <=oldp)
522				break;
523		}
524		if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
525			break;
526		*(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0;
527		/* copy to null byte */
528		incmd = 0;
529		while(*cp++);
530		if(cp > endbuff)
531			incmd = 1;
532		else if(*cp==0)
533			cp++;
534		if(cp > endbuff)
535			cp = endbuff;
536		c = cp-buff;
537		hist_new->histcnt += c;
538		sfwrite(hist_new->histfp,buff,c);
539	}
540	hist_cancel(hist_new);
541	sfclose(hist_old->histfp);
542	if(tmpname)
543	{
544		unlink(tmpname);
545		free(tmpname);
546	}
547	free((char*)hist_old);
548	return hist_ptr = hist_new;
549}
550
551/*
552 * position history file at size and find next command number
553 */
554static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size)
555{
556        register unsigned char *cp, *endbuff;
557        register int n, incmd=1;
558        unsigned char *buff, marker[4];
559	if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
560		goto begin;
561	/* skip to marker command and return the number */
562	/* numbering commands occur after a null and begin with HIST_CMDNO */
563        while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR))
564        {
565		n = sfvalue(iop);
566                *(endbuff=cp+n) = 0;
567                while(1)
568                {
569			/* check for marker */
570                        if(!incmd && *cp++==HIST_CMDNO && *cp==0)
571                        {
572                                n = cp+1 - buff;
573                                incmd = -1;
574                                break;
575                        }
576                        incmd = 0;
577                        while(*cp++);
578                        if(cp>endbuff)
579                        {
580                                incmd = 1;
581                                break;
582                        }
583                        if(*cp==0 && ++cp>endbuff)
584                                break;
585                }
586                size += n;
587		sfread(iop,(char*)buff,n);
588		if(incmd < 0)
589                {
590			if((n=sfread(iop,(char*)marker,4))==4)
591			{
592				n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
593				if(n < size/2)
594				{
595					hp->histmarker = hp->histcnt = size+4;
596					return(n);
597				}
598				n=4;
599			}
600			if(n >0)
601				size += n;
602			incmd = 0;
603		}
604	}
605begin:
606	sfseek(iop,(off_t)2,SEEK_SET);
607	hp->histmarker = hp->histcnt = 2L;
608	return(1);
609}
610
611/*
612 * This routine reads the history file from the present position
613 * to the end-of-file and puts the information in the in-core
614 * history table
615 * Note that HIST_CMDNO is only recognized at the beginning of a command
616 * and that HIST_UNDO as the first character of a command is skipped
617 * unless it is followed by 0.  If followed by 0 then it cancels
618 * the previous command.
619 */
620
621void hist_eof(register History_t *hp)
622{
623	register char *cp,*first,*endbuff;
624	register int incmd = 0;
625	register off_t count = hp->histcnt;
626	int oldind,n,skip=0;
627	off_t last = sfseek(hp->histfp,(off_t)0,SEEK_END);
628	if(last < count)
629	{
630		last = -1;
631		count = 2+HIST_MARKSZ;
632		oldind = hp->histind;
633		if((hp->histind -= hp->histsize) < 0)
634			hp->histind = 1;
635	}
636again:
637	sfseek(hp->histfp,count,SEEK_SET);
638        while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
639	{
640		n = sfvalue(hp->histfp);
641		*(endbuff = cp+n) = 0;
642		first = cp += skip;
643		while(1)
644		{
645			while(!incmd)
646			{
647				if(cp>first)
648				{
649					count += (cp-first);
650					n = hist_ind(hp, ++hp->histind);
651#ifdef future
652					if(count==hp->histcmds[n])
653					{
654	sfprintf(sfstderr,"count match n=%d\n",n);
655						if(histinit)
656						{
657							histinit = 0;
658							return;
659						}
660					}
661					else if(n>=histinit)
662#endif
663						hp->histcmds[n] = count;
664					first = cp;
665				}
666				switch(*((unsigned char*)(cp++)))
667				{
668					case HIST_CMDNO:
669						if(*cp==0)
670						{
671							hp->histmarker=count+2;
672							cp += (HIST_MARKSZ-1);
673							hp->histind--;
674							if(cp <= endbuff)
675							{
676								unsigned char *marker = (unsigned char*)(cp-4);
677								hp->histind = ((marker[0]<<16)|(marker[1]<<8)|marker[2]);
678							}
679						}
680						break;
681					case HIST_UNDO:
682						if(*cp==0)
683						{
684							cp+=1;
685							hp->histind-=2;
686						}
687						break;
688					default:
689						cp--;
690						incmd = 1;
691				}
692				if(cp > endbuff)
693				{
694					cp++;
695					goto refill;
696				}
697			}
698			first = cp;
699			while(*cp++);
700			if(cp > endbuff)
701				break;
702			incmd = 0;
703			while(*cp==0)
704			{
705				if(++cp > endbuff)
706					goto refill;
707			}
708		}
709	refill:
710		count += (--cp-first);
711		skip = (cp-endbuff);
712		if(!incmd && !skip)
713			hp->histcmds[hist_ind(hp,++hp->histind)] = count;
714	}
715	hp->histcnt = count;
716	if(incmd && last)
717	{
718		sfputc(hp->histfp,0);
719		hist_cancel(hp);
720		count = 2;
721		skip = 0;
722		oldind -= hp->histind;
723		hp->histind = hp->histind-hp->histsize + oldind +2;
724		if(hp->histind<0)
725			hp->histind = 1;
726		if(last<0)
727		{
728			char	buff[HIST_MARKSZ];
729			int	fd = open(hp->histname,O_RDWR);
730			if(fd>=0)
731			{
732				hist_marker(buff,hp->histind);
733				write(fd,(char*)hist_stamp,2);
734				write(fd,buff,HIST_MARKSZ);
735				close(fd);
736			}
737		}
738		last = 0;
739		goto again;
740	}
741}
742
743/*
744 * This routine will cause the previous command to be cancelled
745 */
746
747void hist_cancel(register History_t *hp)
748{
749	register int c;
750	if(!hp)
751		return;
752	sfputc(hp->histfp,HIST_UNDO);
753	sfputc(hp->histfp,0);
754	sfsync(hp->histfp);
755	hp->histcnt += 2;
756	c = hist_ind(hp,--hp->histind);
757	hp->histcmds[c] = hp->histcnt;
758}
759
760/*
761 * flush the current history command
762 */
763
764void hist_flush(register History_t *hp)
765{
766	register char *buff;
767	if(hp)
768	{
769		if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
770		{
771			hp->histflush = sfvalue(hp->histfp)+1;
772			sfwrite(hp->histfp,buff,0);
773		}
774		else
775			hp->histflush=0;
776		if(sfsync(hp->histfp)<0)
777		{
778			hist_close(hp);
779			if(!sh_histinit(hp->histshell))
780				sh_offoption(SH_HISTORY);
781		}
782		hp->histflush = 0;
783	}
784}
785
786/*
787 * This is the write discipline for the history file
788 * When called from hist_flush(), trailing newlines are deleted and
789 * a zero byte.  Line sequencing is added as required
790 */
791
792#ifdef SF_BUFCONST
793static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
794#else
795static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
796#endif
797{
798	register History_t *hp = (History_t*)handle;
799	register char *bufptr = ((char*)buff)+insize;
800	register int c,size = insize;
801	register off_t cur;
802	int saved=0;
803	char saveptr[HIST_MARKSZ];
804	if(!hp->histflush)
805		return(write(sffileno(iop),(char*)buff,size));
806	if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
807	{
808		errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
809		return(-1);
810	}
811	hp->histcnt = cur;
812	/* remove whitespace from end of commands */
813	while(--bufptr >= (char*)buff)
814	{
815		c= *bufptr;
816		if(!isspace(c))
817		{
818			if(c=='\\' && *(bufptr+1)!='\n')
819				bufptr++;
820			break;
821		}
822	}
823	/* don't count empty lines */
824	if(++bufptr <= (char*)buff)
825		return(insize);
826	*bufptr++ = '\n';
827	*bufptr++ = 0;
828	size = bufptr - (char*)buff;
829#if	 SHOPT_AUDIT
830	if(hp->auditfp)
831	{
832		time_t	t=time((time_t*)0);
833		sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shgd->euserid:shgd->userid,t,hp->tty,size,buff,0);
834		sfsync(hp->auditfp);
835	}
836#endif	/* SHOPT_AUDIT */
837#if	SHOPT_ACCTFILE
838	if(acctfd)
839	{
840		int timechars, offset;
841		offset = staktell();
842		stakputs(buff);
843		stakseek(staktell() - 1);
844		timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
845		lseek(acctfd, (off_t)0, SEEK_END);
846		write(acctfd, stakptr(offset), size - 2 + timechars);
847		stakseek(offset);
848
849	}
850#endif /* SHOPT_ACCTFILE */
851	if(size&01)
852	{
853		size++;
854		*bufptr++ = 0;
855	}
856	hp->histcnt +=  size;
857	c = hist_ind(hp,++hp->histind);
858	hp->histcmds[c] = hp->histcnt;
859	if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
860	{
861		memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
862		saved=1;
863		hp->histcnt += HIST_MARKSZ;
864		hist_marker(bufptr,hp->histind);
865		hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
866		size += HIST_MARKSZ;
867	}
868	errno = 0;
869	size = write(sffileno(iop),(char*)buff,size);
870	if(saved)
871		memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
872	if(size>=0)
873	{
874		hp->histwfail = 0;
875		return(insize);
876	}
877	return(-1);
878}
879
880/*
881 * Put history sequence number <n> into buffer <buff>
882 * The buffer must be large enough to hold HIST_MARKSZ chars
883 */
884
885static void hist_marker(register char *buff,register long cmdno)
886{
887	*buff++ = HIST_CMDNO;
888	*buff++ = 0;
889	*buff++ = (cmdno>>16);
890	*buff++ = (cmdno>>8);
891	*buff++ = cmdno;
892	*buff++ = 0;
893}
894
895/*
896 * return byte offset in history file for command <n>
897 */
898off_t hist_tell(register History_t *hp, int n)
899{
900	return(hp->histcmds[hist_ind(hp,n)]);
901}
902
903/*
904 * seek to the position of command <n>
905 */
906off_t hist_seek(register History_t *hp, int n)
907{
908	return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
909}
910
911/*
912 * write the command starting at offset <offset> onto file <outfile>.
913 * if character <last> appears before newline it is deleted
914 * each new-line character is replaced with string <nl>.
915 */
916
917void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
918{
919	register int oldc=0;
920	register int c;
921	if(offset<0 || !hp)
922	{
923		sfputr(outfile,sh_translate(e_unknown),'\n');
924		return;
925	}
926	sfseek(hp->histfp,offset,SEEK_SET);
927	while((c = sfgetc(hp->histfp)) != EOF)
928	{
929		if(c && oldc=='\n')
930			sfputr(outfile,nl,-1);
931		else if(last && (c==0 || (c=='\n' && oldc==last)))
932			return;
933		else if(oldc)
934			sfputc(outfile,oldc);
935		oldc = c;
936		if(c==0)
937			return;
938	}
939	return;
940}
941
942/*
943 * find index for last line with given string
944 * If flag==0 then line must begin with string
945 * direction < 1 for backwards search
946*/
947
948Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
949{
950	register int index2;
951	off_t offset;
952	int *coffset=0;
953	Histloc_t location;
954	location.hist_command = -1;
955	location.hist_char = 0;
956	location.hist_line = 0;
957	if(!hp)
958		return(location);
959	/* leading ^ means beginning of line unless escaped */
960	if(flag)
961	{
962		index2 = *string;
963		if(index2=='\\')
964			string++;
965		else if(index2=='^')
966		{
967			flag=0;
968			string++;
969		}
970	}
971	if(flag)
972		coffset = &location.hist_char;
973	index2 = (int)hp->histind;
974	if(direction<0)
975	{
976		index2 -= hp->histsize;
977		if(index2<1)
978			index2 = 1;
979		if(index1 <= index2)
980			return(location);
981	}
982	else if(index1 >= index2)
983		return(location);
984	while(index1!=index2)
985	{
986		direction>0?++index1:--index1;
987		offset = hist_tell(hp,index1);
988		if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
989		{
990			location.hist_command = index1;
991			return(location);
992		}
993#if KSHELL
994		/* allow a search to be aborted */
995		if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET)
996			break;
997#endif /* KSHELL */
998	}
999	return(location);
1000}
1001
1002/*
1003 * search for <string> in history file starting at location <offset>
1004 * If coffset==0 then line must begin with string
1005 * returns the line number of the match if successful, otherwise -1
1006 */
1007
1008int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
1009{
1010	register unsigned char *first, *cp;
1011	register int m,n,c=1,line=0;
1012#if SHOPT_MULTIBYTE
1013	mbinit();
1014#endif /* SHOPT_MULTIBYTE */
1015	sfseek(hp->histfp,offset,SEEK_SET);
1016	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
1017		return(-1);
1018	m = sfvalue(hp->histfp);
1019	n = strlen(string);
1020	while(m > n)
1021	{
1022		if(*cp==*string && memcmp(cp,string,n)==0)
1023		{
1024			if(coffset)
1025				*coffset = (cp-first);
1026			return(line);
1027		}
1028		if(!coffset)
1029			break;
1030		if(*cp=='\n')
1031			line++;
1032#if SHOPT_MULTIBYTE
1033		if((c=mbsize(cp)) < 0)
1034			c = 1;
1035#endif /* SHOPT_MULTIBYTE */
1036		cp += c;
1037		m -= c;
1038	}
1039	return(-1);
1040}
1041
1042
1043#if SHOPT_ESH || SHOPT_VSH
1044/*
1045 * copy command <command> from history file to s1
1046 * at most <size> characters copied
1047 * if s1==0 the number of lines for the command is returned
1048 * line=linenumber  for emacs copy and only this line of command will be copied
1049 * line < 0 for full command copy
1050 * -1 returned if there is no history file
1051 */
1052
1053int hist_copy(char *s1,int size,int command,int line)
1054{
1055	register int c;
1056	register History_t *hp = shgd->hist_ptr;
1057	register int count = 0;
1058	register char *s1max = s1+size;
1059	if(!hp)
1060		return(-1);
1061	hist_seek(hp,command);
1062	while ((c = sfgetc(hp->histfp)) && c!=EOF)
1063	{
1064		if(c=='\n')
1065		{
1066			if(count++ ==line)
1067				break;
1068			else if(line >= 0)
1069				continue;
1070		}
1071		if(s1 && (line<0 || line==count))
1072		{
1073			if(s1 >= s1max)
1074			{
1075				*--s1 = 0;
1076				break;
1077			}
1078			*s1++ = c;
1079		}
1080
1081	}
1082	sfseek(hp->histfp,(off_t)0,SEEK_END);
1083	if(s1==0)
1084		return(count);
1085	if(count && (c= *(s1-1)) == '\n')
1086		s1--;
1087	*s1 = '\0';
1088	return(count);
1089}
1090
1091/*
1092 * return word number <word> from command number <command>
1093 */
1094
1095char *hist_word(char *string,int size,int word)
1096{
1097	register int c;
1098	register char *s1 = string;
1099	register unsigned char *cp = (unsigned char*)s1;
1100	register int flag = 0;
1101	History_t *hp = hist_ptr;
1102	if(!hp)
1103		return(NIL(char*));
1104	hist_copy(string,size,(int)hp->histind-1,-1);
1105	for(;c = *cp;cp++)
1106	{
1107		c = isspace(c);
1108		if(c && flag)
1109		{
1110			*cp = 0;
1111			if(--word==0)
1112				break;
1113			flag = 0;
1114		}
1115		else if(c==0 && flag==0)
1116		{
1117			s1 = (char*)cp;
1118			flag++;
1119		}
1120	}
1121	*cp = 0;
1122	if(s1 != string)
1123		strcpy(string,s1);
1124	return(string);
1125}
1126
1127#endif	/* SHOPT_ESH */
1128
1129#if SHOPT_ESH
1130/*
1131 * given the current command and line number,
1132 * and number of lines back or foward,
1133 * compute the new command and line number.
1134 */
1135
1136Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
1137{
1138	Histloc_t next;
1139	line += lines;
1140	if(!hp)
1141	{
1142		command = -1;
1143		goto done;
1144	}
1145	if(lines > 0)
1146	{
1147		register int count;
1148		while(command <= hp->histind)
1149		{
1150			count = hist_copy(NIL(char*),0, command,-1);
1151			if(count > line)
1152				goto done;
1153			line -= count;
1154			command++;
1155		}
1156	}
1157	else
1158	{
1159		register int least = (int)hp->histind-hp->histsize;
1160		while(1)
1161		{
1162			if(line >=0)
1163				goto done;
1164			if(--command < least)
1165				break;
1166			line += hist_copy(NIL(char*),0, command,-1);
1167		}
1168		command = -1;
1169	}
1170done:
1171	next.hist_line = line;
1172	next.hist_command = command;
1173	return(next);
1174}
1175#endif	/* SHOPT_ESH */
1176
1177
1178/*
1179 * Handle history file exceptions
1180 */
1181#ifdef SF_BUFCONST
1182static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
1183#else
1184static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
1185#endif
1186{
1187	register int newfd,oldfd;
1188	History_t *hp = (History_t*)handle;
1189	if(type==SF_WRITE)
1190	{
1191		if(errno==ENOSPC || hp->histwfail++ >= 10)
1192			return(0);
1193		/* write failure could be NFS problem, try to re-open */
1194		close(oldfd=sffileno(fp));
1195		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
1196		{
1197			if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
1198				return(-1);
1199			fcntl(oldfd,F_SETFD,FD_CLOEXEC);
1200			close(newfd);
1201			if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
1202			{
1203				register int index = hp->histind;
1204				lseek(oldfd,(off_t)2,SEEK_SET);
1205				hp->histcnt = 2;
1206				hp->histind = 1;
1207				hp->histcmds[1] = 2;
1208				hist_eof(hp);
1209				hp->histmarker = hp->histcnt;
1210				hp->histind = index;
1211			}
1212			return(1);
1213		}
1214		errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
1215		return(-1);
1216	}
1217	return(0);
1218}
1219