xref: /titanic_44/usr/src/lib/libshell/common/edit/history.c (revision 2d6b5ea734bb47d251c82670646fde46af15fd69)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1982-2008 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	<ctype.h>
78 #include	<ls.h>
79 #if KSHELL
80 #   include	"defs.h"
81 #   include	"variables.h"
82 #   include	"path.h"
83 #   include	"builtins.h"
84 #   include	"io.h"
85 #endif	/* KSHELL */
86 #include	"history.h"
87 
88 #if !KSHELL
89 #   define new_of(type,x)	((type*)malloc((unsigned)sizeof(type)+(x)))
90 #   define NIL(type)		((type)0)
91 #   define path_relative(x)	(x)
92 #   ifdef __STDC__
93 #	define nv_getval(s)	getenv(#s)
94 #   else
95 #	define nv_getval(s)	getenv("s")
96 #   endif /* __STDC__ */
97 #   define e_unknown	 	"unknown"
98 #   define sh_translate(x)	(x)
99     char login_sh =		0;
100     char hist_fname[] =		"/.history";
101 #endif	/* KSHELL */
102 
103 #ifndef O_BINARY
104 #   define O_BINARY	0
105 #endif /* O_BINARY */
106 
107 int	_Hist = 0;
108 static void	hist_marker(char*,long);
109 static History_t* hist_trim(History_t*, int);
110 static int	hist_nearend(History_t*,Sfio_t*, off_t);
111 static int	hist_check(int);
112 static int	hist_clean(int);
113 #ifdef SF_BUFCONST
114     static ssize_t  hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
115     static int      hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
116 #else
117     static int	hist_write(Sfio_t*, const void*, int, Sfdisc_t*);
118     static int	hist_exceptf(Sfio_t*, int, Sfdisc_t*);
119 #endif
120 
121 
122 static int	histinit;
123 static mode_t	histmode;
124 static History_t *wasopen;
125 static History_t *hist_ptr;
126 
127 #if SHOPT_ACCTFILE
128     static int	acctfd;
129     static char *logname;
130 #   include <pwd.h>
131 
132     static int  acctinit(History_t *hp)
133     {
134 	register char *cp, *acctfile;
135 	Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0);
136 
137 	if(!np || !(acctfile=nv_getval(np)))
138 		return(0);
139 	if(!(cp = getlogin()))
140 	{
141 		struct passwd *userinfo = getpwuid(getuid());
142 		if(userinfo)
143 			cp = userinfo->pw_name;
144 		else
145 			cp = "unknown";
146 	}
147 	logname = strdup(cp);
148 	if((acctfd=sh_open(acctfile,
149 		O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 &&
150 	    (unsigned)acctfd < 10)
151 	{
152 		int n;
153 		if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0)
154 		{
155 			close(acctfd);
156 			acctfd = n;
157 		}
158 	}
159 	if(acctfd < 0)
160 	{
161 		acctfd = 0;
162 		return(0);
163 	}
164 	if(strmatch(acctfile,e_devfdNN))
165 	{
166 		char newfile[16];
167 		sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd);
168 		nv_putval(np,newfile,NV_RDONLY);
169 	}
170 	else
171 		fcntl(acctfd,F_SETFD,FD_CLOEXEC);
172 	return(1);
173     }
174 #endif /* SHOPT_ACCTFILE */
175 
176 #if SHOPT_AUDIT
177 static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len)
178 {
179 	Shell_t	*shp = (Shell_t*)hp->histshell;
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(shp->euserid >=id1 && shp->euserid <= id2)
199 			r |= 1;
200 		if(shp->userid >=id1 && shp->userid <= id2)
201 			r |= 2;
202 		cp = last;
203 	}
204 	while(*cp==';' ||  *cp==' ');
205 done:
206 	close(fd);
207 	return(r);
208 
209 }
210 #endif /*SHOPT_AUDIT*/
211 
212 static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
213 static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
214 
215 static 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  */
227 int  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(shp->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 		shp->hist_ptr = hist_ptr = hp;
256 		if(strcmp(histname,hp->histname)==0)
257 			return(1);
258 		else
259 			hist_free();
260 	}
261 #endif
262 retry:
263 	cp = path_relative(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(shp->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 	shp->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 				hp->tty = strdup(ttyname(2));
397 				hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE);
398 			}
399 		}
400 	}
401 #endif
402 	return(1);
403 }
404 
405 /*
406  * close the history file and free the space
407  */
408 
409 void hist_close(register History_t *hp)
410 {
411 	Shell_t	*shp = (Shell_t*)hp->histshell;
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 	shp->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  */
436 static 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  */
448 static 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 
458 static 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  */
554 static 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 	}
605 begin:
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 
621 void 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 n,skip=0;
627 	sfseek(hp->histfp,count,SEEK_SET);
628         while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
629 	{
630 		n = sfvalue(hp->histfp);
631 		*(endbuff = cp+n) = 0;
632 		first = cp += skip;
633 		while(1)
634 		{
635 			while(!incmd)
636 			{
637 				if(cp>first)
638 				{
639 					count += (cp-first);
640 					n = hist_ind(hp, ++hp->histind);
641 #ifdef future
642 					if(count==hp->histcmds[n])
643 					{
644 	sfprintf(sfstderr,"count match n=%d\n",n);
645 						if(histinit)
646 						{
647 							histinit = 0;
648 							return;
649 						}
650 					}
651 					else if(n>=histinit)
652 #endif
653 						hp->histcmds[n] = count;
654 					first = cp;
655 				}
656 				switch(*((unsigned char*)(cp++)))
657 				{
658 					case HIST_CMDNO:
659 						if(*cp==0)
660 						{
661 							hp->histmarker=count+2;
662 							cp += (HIST_MARKSZ-1);
663 							hp->histind--;
664 #ifdef future
665 							if(cp <= endbuff)
666 							{
667 								unsigned char *marker = (unsigned char*)(cp-4);
668 								int n = ((marker[0]<<16)
669 |(marker[1]<<8)|marker[2]);
670 								if((n<count/2) && n !=  (hp->histind+1))
671 									errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n);
672 							}
673 #endif
674 						}
675 						break;
676 					case HIST_UNDO:
677 						if(*cp==0)
678 						{
679 							cp+=1;
680 							hp->histind-=2;
681 						}
682 						break;
683 					default:
684 						cp--;
685 						incmd = 1;
686 				}
687 				if(cp > endbuff)
688 				{
689 					cp++;
690 					goto refill;
691 				}
692 			}
693 			first = cp;
694 			while(*cp++);
695 			if(cp > endbuff)
696 				break;
697 			incmd = 0;
698 			while(*cp==0)
699 			{
700 				if(++cp > endbuff)
701 					goto refill;
702 			}
703 		}
704 	refill:
705 		count += (--cp-first);
706 		skip = (cp-endbuff);
707 		if(!incmd && !skip)
708 			hp->histcmds[hist_ind(hp,++hp->histind)] = count;
709 	}
710 	hp->histcnt = count;
711 }
712 
713 /*
714  * This routine will cause the previous command to be cancelled
715  */
716 
717 void hist_cancel(register History_t *hp)
718 {
719 	register int c;
720 	if(!hp)
721 		return;
722 	sfputc(hp->histfp,HIST_UNDO);
723 	sfputc(hp->histfp,0);
724 	sfsync(hp->histfp);
725 	hp->histcnt += 2;
726 	c = hist_ind(hp,--hp->histind);
727 	hp->histcmds[c] = hp->histcnt;
728 }
729 
730 /*
731  * flush the current history command
732  */
733 
734 void hist_flush(register History_t *hp)
735 {
736 	register char *buff;
737 	if(hp)
738 	{
739 		if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
740 		{
741 			hp->histflush = sfvalue(hp->histfp)+1;
742 			sfwrite(hp->histfp,buff,0);
743 		}
744 		else
745 			hp->histflush=0;
746 		if(sfsync(hp->histfp)<0)
747 		{
748 			hist_close(hp);
749 			if(!sh_histinit(hp->histshell))
750 				sh_offoption(SH_HISTORY);
751 		}
752 		hp->histflush = 0;
753 	}
754 }
755 
756 /*
757  * This is the write discipline for the history file
758  * When called from hist_flush(), trailing newlines are deleted and
759  * a zero byte.  Line sequencing is added as required
760  */
761 
762 #ifdef SF_BUFCONST
763 static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
764 #else
765 static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
766 #endif
767 {
768 	register History_t *hp = (History_t*)handle;
769 	register char *bufptr = ((char*)buff)+insize;
770 	register int c,size = insize;
771 	register off_t cur;
772 	int saved=0;
773 	char saveptr[HIST_MARKSZ];
774 	if(!hp->histflush)
775 		return(write(sffileno(iop),(char*)buff,size));
776 	if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
777 	{
778 		errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
779 		return(-1);
780 	}
781 	hp->histcnt = cur;
782 	/* remove whitespace from end of commands */
783 	while(--bufptr >= (char*)buff)
784 	{
785 		c= *bufptr;
786 		if(!isspace(c))
787 		{
788 			if(c=='\\' && *(bufptr+1)!='\n')
789 				bufptr++;
790 			break;
791 		}
792 	}
793 	/* don't count empty lines */
794 	if(++bufptr <= (char*)buff)
795 		return(insize);
796 	*bufptr++ = '\n';
797 	*bufptr++ = 0;
798 	size = bufptr - (char*)buff;
799 #if	 SHOPT_AUDIT
800 	if(hp->auditfp)
801 	{
802 		Shell_t *shp = (Shell_t*)hp->histshell;
803 		time_t	t=time((time_t*)0);
804 		sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shp->euserid:shp->userid,t,hp->tty,size,buff,0);
805 		sfsync(hp->auditfp);
806 	}
807 #endif	/* SHOPT_AUDIT */
808 #if	SHOPT_ACCTFILE
809 	if(acctfd)
810 	{
811 		int timechars, offset;
812 		offset = staktell();
813 		stakputs(buff);
814 		stakseek(staktell() - 1);
815 		timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
816 		lseek(acctfd, (off_t)0, SEEK_END);
817 		write(acctfd, stakptr(offset), size - 2 + timechars);
818 		stakseek(offset);
819 
820 	}
821 #endif /* SHOPT_ACCTFILE */
822 	if(size&01)
823 	{
824 		size++;
825 		*bufptr++ = 0;
826 	}
827 	hp->histcnt +=  size;
828 	c = hist_ind(hp,++hp->histind);
829 	hp->histcmds[c] = hp->histcnt;
830 	if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
831 	{
832 		memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
833 		saved=1;
834 		hp->histcnt += HIST_MARKSZ;
835 		hist_marker(bufptr,hp->histind);
836 		hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
837 		size += HIST_MARKSZ;
838 	}
839 	errno = 0;
840 	size = write(sffileno(iop),(char*)buff,size);
841 	if(saved)
842 		memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
843 	if(size>=0)
844 	{
845 		hp->histwfail = 0;
846 		return(insize);
847 	}
848 	return(-1);
849 }
850 
851 /*
852  * Put history sequence number <n> into buffer <buff>
853  * The buffer must be large enough to hold HIST_MARKSZ chars
854  */
855 
856 static void hist_marker(register char *buff,register long cmdno)
857 {
858 	*buff++ = HIST_CMDNO;
859 	*buff++ = 0;
860 	*buff++ = (cmdno>>16);
861 	*buff++ = (cmdno>>8);
862 	*buff++ = cmdno;
863 	*buff++ = 0;
864 }
865 
866 /*
867  * return byte offset in history file for command <n>
868  */
869 off_t hist_tell(register History_t *hp, int n)
870 {
871 	return(hp->histcmds[hist_ind(hp,n)]);
872 }
873 
874 /*
875  * seek to the position of command <n>
876  */
877 off_t hist_seek(register History_t *hp, int n)
878 {
879 	return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
880 }
881 
882 /*
883  * write the command starting at offset <offset> onto file <outfile>.
884  * if character <last> appears before newline it is deleted
885  * each new-line character is replaced with string <nl>.
886  */
887 
888 void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
889 {
890 	register int oldc=0;
891 	register int c;
892 	if(offset<0 || !hp)
893 	{
894 		sfputr(outfile,sh_translate(e_unknown),'\n');
895 		return;
896 	}
897 	sfseek(hp->histfp,offset,SEEK_SET);
898 	while((c = sfgetc(hp->histfp)) != EOF)
899 	{
900 		if(c && oldc=='\n')
901 			sfputr(outfile,nl,-1);
902 		else if(last && (c==0 || (c=='\n' && oldc==last)))
903 			return;
904 		else if(oldc)
905 			sfputc(outfile,oldc);
906 		oldc = c;
907 		if(c==0)
908 			return;
909 	}
910 	return;
911 }
912 
913 /*
914  * find index for last line with given string
915  * If flag==0 then line must begin with string
916  * direction < 1 for backwards search
917 */
918 
919 Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
920 {
921 	register int index2;
922 	off_t offset;
923 	int *coffset=0;
924 	Histloc_t location;
925 	location.hist_command = -1;
926 	location.hist_char = 0;
927 	location.hist_line = 0;
928 	if(!hp)
929 		return(location);
930 	/* leading ^ means beginning of line unless escaped */
931 	if(flag)
932 	{
933 		index2 = *string;
934 		if(index2=='\\')
935 			string++;
936 		else if(index2=='^')
937 		{
938 			flag=0;
939 			string++;
940 		}
941 	}
942 	if(flag)
943 		coffset = &location.hist_char;
944 	index2 = (int)hp->histind;
945 	if(direction<0)
946 	{
947 		index2 -= hp->histsize;
948 		if(index2<1)
949 			index2 = 1;
950 		if(index1 <= index2)
951 			return(location);
952 	}
953 	else if(index1 >= index2)
954 		return(location);
955 	while(index1!=index2)
956 	{
957 		direction>0?++index1:--index1;
958 		offset = hist_tell(hp,index1);
959 		if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
960 		{
961 			location.hist_command = index1;
962 			return(location);
963 		}
964 #if KSHELL
965 		/* allow a search to be aborted */
966 		if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET)
967 			break;
968 #endif /* KSHELL */
969 	}
970 	return(location);
971 }
972 
973 /*
974  * search for <string> in history file starting at location <offset>
975  * If coffset==0 then line must begin with string
976  * returns the line number of the match if successful, otherwise -1
977  */
978 
979 int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
980 {
981 	register unsigned char *first, *cp;
982 	register int m,n,c=1,line=0;
983 #if SHOPT_MULTIBYTE
984 	mbinit();
985 #endif /* SHOPT_MULTIBYTE */
986 	sfseek(hp->histfp,offset,SEEK_SET);
987 	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
988 		return(-1);
989 	m = sfvalue(hp->histfp);
990 	n = strlen(string);
991 	while(m > n)
992 	{
993 		if(*cp==*string && memcmp(cp,string,n)==0)
994 		{
995 			if(coffset)
996 				*coffset = (cp-first);
997 			return(line);
998 		}
999 		if(!coffset)
1000 			break;
1001 		if(*cp=='\n')
1002 			line++;
1003 #if SHOPT_MULTIBYTE
1004 		if((c=mbsize(cp)) < 0)
1005 			c = 1;
1006 #endif /* SHOPT_MULTIBYTE */
1007 		cp += c;
1008 		m -= c;
1009 	}
1010 	return(-1);
1011 }
1012 
1013 
1014 #if SHOPT_ESH || SHOPT_VSH
1015 /*
1016  * copy command <command> from history file to s1
1017  * at most <size> characters copied
1018  * if s1==0 the number of lines for the command is returned
1019  * line=linenumber  for emacs copy and only this line of command will be copied
1020  * line < 0 for full command copy
1021  * -1 returned if there is no history file
1022  */
1023 
1024 int hist_copy(char *s1,int size,int command,int line)
1025 {
1026 	register int c;
1027 	register History_t *hp = sh_getinterp()->hist_ptr;
1028 	register int count = 0;
1029 	register char *s1max = s1+size;
1030 	if(!hp)
1031 		return(-1);
1032 	hist_seek(hp,command);
1033 	while ((c = sfgetc(hp->histfp)) && c!=EOF)
1034 	{
1035 		if(c=='\n')
1036 		{
1037 			if(count++ ==line)
1038 				break;
1039 			else if(line >= 0)
1040 				continue;
1041 		}
1042 		if(s1 && (line<0 || line==count))
1043 		{
1044 			if(s1 >= s1max)
1045 			{
1046 				*--s1 = 0;
1047 				break;
1048 			}
1049 			*s1++ = c;
1050 		}
1051 
1052 	}
1053 	sfseek(hp->histfp,(off_t)0,SEEK_END);
1054 	if(s1==0)
1055 		return(count);
1056 	if(count && (c= *(s1-1)) == '\n')
1057 		s1--;
1058 	*s1 = '\0';
1059 	return(count);
1060 }
1061 
1062 /*
1063  * return word number <word> from command number <command>
1064  */
1065 
1066 char *hist_word(char *string,int size,int word)
1067 {
1068 	register int c;
1069 	register char *s1 = string;
1070 	register unsigned char *cp = (unsigned char*)s1;
1071 	register int flag = 0;
1072 	History_t *hp = hist_ptr;
1073 	if(!hp)
1074 #if KSHELL
1075 	{
1076 		strncpy(string,((Shell_t*)hp->histshell)->lastarg,size);
1077 		return(string);
1078 	}
1079 #else
1080 		return(NIL(char*));
1081 #endif /* KSHELL */
1082 	hist_copy(string,size,(int)hp->histind-1,-1);
1083 	for(;c = *cp;cp++)
1084 	{
1085 		c = isspace(c);
1086 		if(c && flag)
1087 		{
1088 			*cp = 0;
1089 			if(--word==0)
1090 				break;
1091 			flag = 0;
1092 		}
1093 		else if(c==0 && flag==0)
1094 		{
1095 			s1 = (char*)cp;
1096 			flag++;
1097 		}
1098 	}
1099 	*cp = 0;
1100 	if(s1 != string)
1101 		strcpy(string,s1);
1102 	return(string);
1103 }
1104 
1105 #endif	/* SHOPT_ESH */
1106 
1107 #if SHOPT_ESH
1108 /*
1109  * given the current command and line number,
1110  * and number of lines back or foward,
1111  * compute the new command and line number.
1112  */
1113 
1114 Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
1115 {
1116 	Histloc_t next;
1117 	line += lines;
1118 	if(!hp)
1119 	{
1120 		command = -1;
1121 		goto done;
1122 	}
1123 	if(lines > 0)
1124 	{
1125 		register int count;
1126 		while(command <= hp->histind)
1127 		{
1128 			count = hist_copy(NIL(char*),0, command,-1);
1129 			if(count > line)
1130 				goto done;
1131 			line -= count;
1132 			command++;
1133 		}
1134 	}
1135 	else
1136 	{
1137 		register int least = (int)hp->histind-hp->histsize;
1138 		while(1)
1139 		{
1140 			if(line >=0)
1141 				goto done;
1142 			if(--command < least)
1143 				break;
1144 			line += hist_copy(NIL(char*),0, command,-1);
1145 		}
1146 		command = -1;
1147 	}
1148 done:
1149 	next.hist_line = line;
1150 	next.hist_command = command;
1151 	return(next);
1152 }
1153 #endif	/* SHOPT_ESH */
1154 
1155 
1156 /*
1157  * Handle history file exceptions
1158  */
1159 #ifdef SF_BUFCONST
1160 static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
1161 #else
1162 static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
1163 #endif
1164 {
1165 	register int newfd,oldfd;
1166 	History_t *hp = (History_t*)handle;
1167 	if(type==SF_WRITE)
1168 	{
1169 		if(errno==ENOSPC || hp->histwfail++ >= 10)
1170 			return(0);
1171 		/* write failure could be NFS problem, try to re-open */
1172 		close(oldfd=sffileno(fp));
1173 		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
1174 		{
1175 			if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
1176 				return(-1);
1177 			fcntl(oldfd,F_SETFD,FD_CLOEXEC);
1178 			close(newfd);
1179 			if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
1180 			{
1181 				register int index = hp->histind;
1182 				lseek(oldfd,(off_t)2,SEEK_SET);
1183 				hp->histcnt = 2;
1184 				hp->histind = 1;
1185 				hp->histcmds[1] = 2;
1186 				hist_eof(hp);
1187 				hp->histmarker = hp->histcnt;
1188 				hp->histind = index;
1189 			}
1190 			return(1);
1191 		}
1192 		errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
1193 		return(-1);
1194 	}
1195 	return(0);
1196 }
1197