xref: /titanic_51/usr/src/lib/libshell/common/edit/history.c (revision 074bb90d80fdbeb2d04a8450a55ecbc96de28785)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1982-2009 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(x)	(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 
108 int	_Hist = 0;
109 static void	hist_marker(char*,long);
110 static History_t* hist_trim(History_t*, int);
111 static int	hist_nearend(History_t*,Sfio_t*, off_t);
112 static int	hist_check(int);
113 static 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 
123 static int	histinit;
124 static mode_t	histmode;
125 static History_t *wasopen;
126 static 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(strmatch(acctfile,e_devfdNN))
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
178 static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len)
179 {
180 	Shell_t	*shp = (Shell_t*)hp->histshell;
181 	char	*buff, *cp, *last;
182 	int	id1, id2, r=0, n, fd;
183 	if((fd=open(name, O_RDONLY)) < 0)
184 		return(0);
185 	if((n = read(fd, logbuf,len-1)) < 0)
186 		goto done;
187 	while(logbuf[n-1]=='\n')
188 		n--;
189 	logbuf[n] = 0;
190 	if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' ')))
191 		goto done;
192 	*cp = 0;
193 	do
194 	{
195 		cp++;
196 		id1 = id2 = strtol(cp,&last,10);
197 		if(*last=='-')
198 			id1 = strtol(last+1,&last,10);
199 		if(shp->euserid >=id1 && shp->euserid <= id2)
200 			r |= 1;
201 		if(shp->userid >=id1 && shp->userid <= id2)
202 			r |= 2;
203 		cp = last;
204 	}
205 	while(*cp==';' ||  *cp==' ');
206 done:
207 	close(fd);
208 	return(r);
209 
210 }
211 #endif /*SHOPT_AUDIT*/
212 
213 static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION };
214 static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL};
215 
216 static void hist_touch(void *handle)
217 {
218 	touch((char*)handle, (time_t)0, (time_t)0, 0);
219 }
220 
221 /*
222  * open the history file
223  * if HISTNAME is not given and userid==0 then no history file.
224  * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is
225  * cleaned up.
226  * hist_open() returns 1, if history file is open
227  */
228 int  sh_histinit(void *sh_context)
229 {
230 	Shell_t *shp = (Shell_t*)sh_context;
231 	register int fd;
232 	register History_t *hp;
233 	register char *histname;
234 	char *fname=0;
235 	int histmask, maxlines, hist_start=0;
236 	register char *cp;
237 	register off_t hsize = 0;
238 
239 	if(shp->hist_ptr=hist_ptr)
240 		return(1);
241 	if(!(histname = nv_getval(HISTFILE)))
242 	{
243 		int offset = staktell();
244 		if(cp=nv_getval(HOME))
245 			stakputs(cp);
246 		stakputs(hist_fname);
247 		stakputc(0);
248 		stakseek(offset);
249 		histname = stakptr(offset);
250 	}
251 #ifdef future
252 	if(hp=wasopen)
253 	{
254 		/* reuse history file if same name */
255 		wasopen = 0;
256 		shp->hist_ptr = hist_ptr = hp;
257 		if(strcmp(histname,hp->histname)==0)
258 			return(1);
259 		else
260 			hist_free();
261 	}
262 #endif
263 retry:
264 	cp = path_relative(histname);
265 	if(!histinit)
266 		histmode = S_IRUSR|S_IWUSR;
267 	if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0)
268 	{
269 		hsize=lseek(fd,(off_t)0,SEEK_END);
270 	}
271 	if((unsigned)fd <=2)
272 	{
273 		int n;
274 		if((n=fcntl(fd,F_DUPFD,10))>=0)
275 		{
276 			close(fd);
277 			fd=n;
278 		}
279 	}
280 	/* make sure that file has history file format */
281 	if(hsize && hist_check(fd))
282 	{
283 		close(fd);
284 		hsize = 0;
285 		if(unlink(cp)>=0)
286 			goto retry;
287 		fd = -1;
288 	}
289 	if(fd < 0)
290 	{
291 #if KSHELL
292 		/* don't allow root a history_file in /tmp */
293 		if(shp->userid)
294 #endif	/* KSHELL */
295 		{
296 			if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*))))
297 				return(0);
298 			fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);
299 		}
300 	}
301 	if(fd<0)
302 		return(0);
303 	/* set the file to close-on-exec */
304 	fcntl(fd,F_SETFD,FD_CLOEXEC);
305 	if(cp=nv_getval(HISTSIZE))
306 		maxlines = (unsigned)strtol(cp, (char**)0, 10);
307 	else
308 		maxlines = HIST_DFLT;
309 	for(histmask=16;histmask <= maxlines; histmask <<=1 );
310 	if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t))))
311 	{
312 		close(fd);
313 		return(0);
314 	}
315 	shp->hist_ptr = hist_ptr = hp;
316 	hp->histshell = (void*)shp;
317 	hp->histsize = maxlines;
318 	hp->histmask = histmask;
319 	hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE);
320 	memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1));
321 	hp->histind = 1;
322 	hp->histcmds[1] = 2;
323 	hp->histcnt = 2;
324 	hp->histname = strdup(histname);
325 	hp->histdisc = hist_disc;
326 	if(hsize==0)
327 	{
328 		/* put special characters at front of file */
329 		sfwrite(hp->histfp,(char*)hist_stamp,2);
330 		sfsync(hp->histfp);
331 	}
332 	/* initialize history list */
333 	else
334 	{
335 		int first,last;
336 		off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE;
337 		hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size);
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 				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 
410 void hist_close(register History_t *hp)
411 {
412 	Shell_t	*shp = (Shell_t*)hp->histshell;
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 	shp->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  */
437 static 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  */
449 static 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 
459 static 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 			tmpname = name;
485 		fd = open(tmpname,O_RDONLY);
486 		sfsetfd(hist_old->histfp,fd);
487 		if(tmpname==name)
488 			tmpname = 0;
489 	}
490 	hist_ptr = 0;
491 	if(fstat(sffileno(hist_old->histfp),&statb)>=0)
492 	{
493 		histinit = 1;
494 		histmode =  statb.st_mode;
495 	}
496 	if(!sh_histinit(hp->histshell))
497 	{
498 		/* use the old history file */
499 		return hist_ptr = hist_old;
500 	}
501 	hist_new = hist_ptr;
502 	hist_ptr = hist_old;
503 	if(--n < 0)
504 		n = 0;
505 	newp = hist_seek(hist_old,++n);
506 	while(1)
507 	{
508 		if(!incmd)
509 		{
510 			c = hist_ind(hist_new,++hist_new->histind);
511 			hist_new->histcmds[c] = hist_new->histcnt;
512 			if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2)
513 			{
514 				char locbuff[HIST_MARKSZ];
515 				hist_marker(locbuff,hist_new->histind);
516 				sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ);
517 				hist_new->histcnt += HIST_MARKSZ;
518 				hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt;
519 			}
520 			oldp = newp;
521 			newp = hist_seek(hist_old,++n);
522 			if(newp <=oldp)
523 				break;
524 		}
525 		if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0)))
526 			break;
527 		*(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0;
528 		/* copy to null byte */
529 		incmd = 0;
530 		while(*cp++);
531 		if(cp > endbuff)
532 			incmd = 1;
533 		else if(*cp==0)
534 			cp++;
535 		if(cp > endbuff)
536 			cp = endbuff;
537 		c = cp-buff;
538 		hist_new->histcnt += c;
539 		sfwrite(hist_new->histfp,buff,c);
540 	}
541 	hist_cancel(hist_new);
542 	sfclose(hist_old->histfp);
543 	if(tmpname)
544 	{
545 		unlink(tmpname);
546 		free(tmpname);
547 	}
548 	free((char*)hist_old);
549 	return hist_ptr = hist_new;
550 }
551 
552 /*
553  * position history file at size and find next command number
554  */
555 static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size)
556 {
557         register unsigned char *cp, *endbuff;
558         register int n, incmd=1;
559         unsigned char *buff, marker[4];
560 	if(size <= 2L || sfseek(iop,size,SEEK_SET)<0)
561 		goto begin;
562 	/* skip to marker command and return the number */
563 	/* numbering commands occur after a null and begin with HIST_CMDNO */
564         while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR))
565         {
566 		n = sfvalue(iop);
567                 *(endbuff=cp+n) = 0;
568                 while(1)
569                 {
570 			/* check for marker */
571                         if(!incmd && *cp++==HIST_CMDNO && *cp==0)
572                         {
573                                 n = cp+1 - buff;
574                                 incmd = -1;
575                                 break;
576                         }
577                         incmd = 0;
578                         while(*cp++);
579                         if(cp>endbuff)
580                         {
581                                 incmd = 1;
582                                 break;
583                         }
584                         if(*cp==0 && ++cp>endbuff)
585                                 break;
586                 }
587                 size += n;
588 		sfread(iop,(char*)buff,n);
589 		if(incmd < 0)
590                 {
591 			if((n=sfread(iop,(char*)marker,4))==4)
592 			{
593 				n = (marker[0]<<16)|(marker[1]<<8)|marker[2];
594 				if(n < size/2)
595 				{
596 					hp->histmarker = hp->histcnt = size+4;
597 					return(n);
598 				}
599 				n=4;
600 			}
601 			if(n >0)
602 				size += n;
603 			incmd = 0;
604 		}
605 	}
606 begin:
607 	sfseek(iop,(off_t)2,SEEK_SET);
608 	hp->histmarker = hp->histcnt = 2L;
609 	return(1);
610 }
611 
612 /*
613  * This routine reads the history file from the present position
614  * to the end-of-file and puts the information in the in-core
615  * history table
616  * Note that HIST_CMDNO is only recognized at the beginning of a command
617  * and that HIST_UNDO as the first character of a command is skipped
618  * unless it is followed by 0.  If followed by 0 then it cancels
619  * the previous command.
620  */
621 
622 void hist_eof(register History_t *hp)
623 {
624 	register char *cp,*first,*endbuff;
625 	register int incmd = 0;
626 	register off_t count = hp->histcnt;
627 	int n,skip=0;
628 	sfseek(hp->histfp,count,SEEK_SET);
629         while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0))
630 	{
631 		n = sfvalue(hp->histfp);
632 		*(endbuff = cp+n) = 0;
633 		first = cp += skip;
634 		while(1)
635 		{
636 			while(!incmd)
637 			{
638 				if(cp>first)
639 				{
640 					count += (cp-first);
641 					n = hist_ind(hp, ++hp->histind);
642 #ifdef future
643 					if(count==hp->histcmds[n])
644 					{
645 	sfprintf(sfstderr,"count match n=%d\n",n);
646 						if(histinit)
647 						{
648 							histinit = 0;
649 							return;
650 						}
651 					}
652 					else if(n>=histinit)
653 #endif
654 						hp->histcmds[n] = count;
655 					first = cp;
656 				}
657 				switch(*((unsigned char*)(cp++)))
658 				{
659 					case HIST_CMDNO:
660 						if(*cp==0)
661 						{
662 							hp->histmarker=count+2;
663 							cp += (HIST_MARKSZ-1);
664 							hp->histind--;
665 #ifdef future
666 							if(cp <= endbuff)
667 							{
668 								unsigned char *marker = (unsigned char*)(cp-4);
669 								int n = ((marker[0]<<16)
670 |(marker[1]<<8)|marker[2]);
671 								if((n<count/2) && n !=  (hp->histind+1))
672 									errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n);
673 							}
674 #endif
675 						}
676 						break;
677 					case HIST_UNDO:
678 						if(*cp==0)
679 						{
680 							cp+=1;
681 							hp->histind-=2;
682 						}
683 						break;
684 					default:
685 						cp--;
686 						incmd = 1;
687 				}
688 				if(cp > endbuff)
689 				{
690 					cp++;
691 					goto refill;
692 				}
693 			}
694 			first = cp;
695 			while(*cp++);
696 			if(cp > endbuff)
697 				break;
698 			incmd = 0;
699 			while(*cp==0)
700 			{
701 				if(++cp > endbuff)
702 					goto refill;
703 			}
704 		}
705 	refill:
706 		count += (--cp-first);
707 		skip = (cp-endbuff);
708 		if(!incmd && !skip)
709 			hp->histcmds[hist_ind(hp,++hp->histind)] = count;
710 	}
711 	hp->histcnt = count;
712 }
713 
714 /*
715  * This routine will cause the previous command to be cancelled
716  */
717 
718 void hist_cancel(register History_t *hp)
719 {
720 	register int c;
721 	if(!hp)
722 		return;
723 	sfputc(hp->histfp,HIST_UNDO);
724 	sfputc(hp->histfp,0);
725 	sfsync(hp->histfp);
726 	hp->histcnt += 2;
727 	c = hist_ind(hp,--hp->histind);
728 	hp->histcmds[c] = hp->histcnt;
729 }
730 
731 /*
732  * flush the current history command
733  */
734 
735 void hist_flush(register History_t *hp)
736 {
737 	register char *buff;
738 	if(hp)
739 	{
740 		if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR))
741 		{
742 			hp->histflush = sfvalue(hp->histfp)+1;
743 			sfwrite(hp->histfp,buff,0);
744 		}
745 		else
746 			hp->histflush=0;
747 		if(sfsync(hp->histfp)<0)
748 		{
749 			hist_close(hp);
750 			if(!sh_histinit(hp->histshell))
751 				sh_offoption(SH_HISTORY);
752 		}
753 		hp->histflush = 0;
754 	}
755 }
756 
757 /*
758  * This is the write discipline for the history file
759  * When called from hist_flush(), trailing newlines are deleted and
760  * a zero byte.  Line sequencing is added as required
761  */
762 
763 #ifdef SF_BUFCONST
764 static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
765 #else
766 static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle)
767 #endif
768 {
769 	register History_t *hp = (History_t*)handle;
770 	register char *bufptr = ((char*)buff)+insize;
771 	register int c,size = insize;
772 	register off_t cur;
773 	int saved=0;
774 	char saveptr[HIST_MARKSZ];
775 	if(!hp->histflush)
776 		return(write(sffileno(iop),(char*)buff,size));
777 	if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0)
778 	{
779 		errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno);
780 		return(-1);
781 	}
782 	hp->histcnt = cur;
783 	/* remove whitespace from end of commands */
784 	while(--bufptr >= (char*)buff)
785 	{
786 		c= *bufptr;
787 		if(!isspace(c))
788 		{
789 			if(c=='\\' && *(bufptr+1)!='\n')
790 				bufptr++;
791 			break;
792 		}
793 	}
794 	/* don't count empty lines */
795 	if(++bufptr <= (char*)buff)
796 		return(insize);
797 	*bufptr++ = '\n';
798 	*bufptr++ = 0;
799 	size = bufptr - (char*)buff;
800 #if	 SHOPT_AUDIT
801 	if(hp->auditfp)
802 	{
803 		Shell_t *shp = (Shell_t*)hp->histshell;
804 		time_t	t=time((time_t*)0);
805 		sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shp->euserid:shp->userid,t,hp->tty,size,buff,0);
806 		sfsync(hp->auditfp);
807 	}
808 #endif	/* SHOPT_AUDIT */
809 #if	SHOPT_ACCTFILE
810 	if(acctfd)
811 	{
812 		int timechars, offset;
813 		offset = staktell();
814 		stakputs(buff);
815 		stakseek(staktell() - 1);
816 		timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *)));
817 		lseek(acctfd, (off_t)0, SEEK_END);
818 		write(acctfd, stakptr(offset), size - 2 + timechars);
819 		stakseek(offset);
820 
821 	}
822 #endif /* SHOPT_ACCTFILE */
823 	if(size&01)
824 	{
825 		size++;
826 		*bufptr++ = 0;
827 	}
828 	hp->histcnt +=  size;
829 	c = hist_ind(hp,++hp->histind);
830 	hp->histcmds[c] = hp->histcnt;
831 	if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2)
832 	{
833 		memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ);
834 		saved=1;
835 		hp->histcnt += HIST_MARKSZ;
836 		hist_marker(bufptr,hp->histind);
837 		hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt;
838 		size += HIST_MARKSZ;
839 	}
840 	errno = 0;
841 	size = write(sffileno(iop),(char*)buff,size);
842 	if(saved)
843 		memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ);
844 	if(size>=0)
845 	{
846 		hp->histwfail = 0;
847 		return(insize);
848 	}
849 	return(-1);
850 }
851 
852 /*
853  * Put history sequence number <n> into buffer <buff>
854  * The buffer must be large enough to hold HIST_MARKSZ chars
855  */
856 
857 static void hist_marker(register char *buff,register long cmdno)
858 {
859 	*buff++ = HIST_CMDNO;
860 	*buff++ = 0;
861 	*buff++ = (cmdno>>16);
862 	*buff++ = (cmdno>>8);
863 	*buff++ = cmdno;
864 	*buff++ = 0;
865 }
866 
867 /*
868  * return byte offset in history file for command <n>
869  */
870 off_t hist_tell(register History_t *hp, int n)
871 {
872 	return(hp->histcmds[hist_ind(hp,n)]);
873 }
874 
875 /*
876  * seek to the position of command <n>
877  */
878 off_t hist_seek(register History_t *hp, int n)
879 {
880 	return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET));
881 }
882 
883 /*
884  * write the command starting at offset <offset> onto file <outfile>.
885  * if character <last> appears before newline it is deleted
886  * each new-line character is replaced with string <nl>.
887  */
888 
889 void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl)
890 {
891 	register int oldc=0;
892 	register int c;
893 	if(offset<0 || !hp)
894 	{
895 		sfputr(outfile,sh_translate(e_unknown),'\n');
896 		return;
897 	}
898 	sfseek(hp->histfp,offset,SEEK_SET);
899 	while((c = sfgetc(hp->histfp)) != EOF)
900 	{
901 		if(c && oldc=='\n')
902 			sfputr(outfile,nl,-1);
903 		else if(last && (c==0 || (c=='\n' && oldc==last)))
904 			return;
905 		else if(oldc)
906 			sfputc(outfile,oldc);
907 		oldc = c;
908 		if(c==0)
909 			return;
910 	}
911 	return;
912 }
913 
914 /*
915  * find index for last line with given string
916  * If flag==0 then line must begin with string
917  * direction < 1 for backwards search
918 */
919 
920 Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction)
921 {
922 	register int index2;
923 	off_t offset;
924 	int *coffset=0;
925 	Histloc_t location;
926 	location.hist_command = -1;
927 	location.hist_char = 0;
928 	location.hist_line = 0;
929 	if(!hp)
930 		return(location);
931 	/* leading ^ means beginning of line unless escaped */
932 	if(flag)
933 	{
934 		index2 = *string;
935 		if(index2=='\\')
936 			string++;
937 		else if(index2=='^')
938 		{
939 			flag=0;
940 			string++;
941 		}
942 	}
943 	if(flag)
944 		coffset = &location.hist_char;
945 	index2 = (int)hp->histind;
946 	if(direction<0)
947 	{
948 		index2 -= hp->histsize;
949 		if(index2<1)
950 			index2 = 1;
951 		if(index1 <= index2)
952 			return(location);
953 	}
954 	else if(index1 >= index2)
955 		return(location);
956 	while(index1!=index2)
957 	{
958 		direction>0?++index1:--index1;
959 		offset = hist_tell(hp,index1);
960 		if((location.hist_line=hist_match(hp,offset,string,coffset))>=0)
961 		{
962 			location.hist_command = index1;
963 			return(location);
964 		}
965 #if KSHELL
966 		/* allow a search to be aborted */
967 		if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET)
968 			break;
969 #endif /* KSHELL */
970 	}
971 	return(location);
972 }
973 
974 /*
975  * search for <string> in history file starting at location <offset>
976  * If coffset==0 then line must begin with string
977  * returns the line number of the match if successful, otherwise -1
978  */
979 
980 int hist_match(register History_t *hp,off_t offset,char *string,int *coffset)
981 {
982 	register unsigned char *first, *cp;
983 	register int m,n,c=1,line=0;
984 #if SHOPT_MULTIBYTE
985 	mbinit();
986 #endif /* SHOPT_MULTIBYTE */
987 	sfseek(hp->histfp,offset,SEEK_SET);
988 	if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
989 		return(-1);
990 	m = sfvalue(hp->histfp);
991 	n = strlen(string);
992 	while(m > n)
993 	{
994 		if(*cp==*string && memcmp(cp,string,n)==0)
995 		{
996 			if(coffset)
997 				*coffset = (cp-first);
998 			return(line);
999 		}
1000 		if(!coffset)
1001 			break;
1002 		if(*cp=='\n')
1003 			line++;
1004 #if SHOPT_MULTIBYTE
1005 		if((c=mbsize(cp)) < 0)
1006 			c = 1;
1007 #endif /* SHOPT_MULTIBYTE */
1008 		cp += c;
1009 		m -= c;
1010 	}
1011 	return(-1);
1012 }
1013 
1014 
1015 #if SHOPT_ESH || SHOPT_VSH
1016 /*
1017  * copy command <command> from history file to s1
1018  * at most <size> characters copied
1019  * if s1==0 the number of lines for the command is returned
1020  * line=linenumber  for emacs copy and only this line of command will be copied
1021  * line < 0 for full command copy
1022  * -1 returned if there is no history file
1023  */
1024 
1025 int hist_copy(char *s1,int size,int command,int line)
1026 {
1027 	register int c;
1028 	register History_t *hp = sh_getinterp()->hist_ptr;
1029 	register int count = 0;
1030 	register char *s1max = s1+size;
1031 	if(!hp)
1032 		return(-1);
1033 	hist_seek(hp,command);
1034 	while ((c = sfgetc(hp->histfp)) && c!=EOF)
1035 	{
1036 		if(c=='\n')
1037 		{
1038 			if(count++ ==line)
1039 				break;
1040 			else if(line >= 0)
1041 				continue;
1042 		}
1043 		if(s1 && (line<0 || line==count))
1044 		{
1045 			if(s1 >= s1max)
1046 			{
1047 				*--s1 = 0;
1048 				break;
1049 			}
1050 			*s1++ = c;
1051 		}
1052 
1053 	}
1054 	sfseek(hp->histfp,(off_t)0,SEEK_END);
1055 	if(s1==0)
1056 		return(count);
1057 	if(count && (c= *(s1-1)) == '\n')
1058 		s1--;
1059 	*s1 = '\0';
1060 	return(count);
1061 }
1062 
1063 /*
1064  * return word number <word> from command number <command>
1065  */
1066 
1067 char *hist_word(char *string,int size,int word)
1068 {
1069 	register int c;
1070 	register char *s1 = string;
1071 	register unsigned char *cp = (unsigned char*)s1;
1072 	register int flag = 0;
1073 	History_t *hp = hist_ptr;
1074 	if(!hp)
1075 		return(NIL(char*));
1076 	hist_copy(string,size,(int)hp->histind-1,-1);
1077 	for(;c = *cp;cp++)
1078 	{
1079 		c = isspace(c);
1080 		if(c && flag)
1081 		{
1082 			*cp = 0;
1083 			if(--word==0)
1084 				break;
1085 			flag = 0;
1086 		}
1087 		else if(c==0 && flag==0)
1088 		{
1089 			s1 = (char*)cp;
1090 			flag++;
1091 		}
1092 	}
1093 	*cp = 0;
1094 	if(s1 != string)
1095 		strcpy(string,s1);
1096 	return(string);
1097 }
1098 
1099 #endif	/* SHOPT_ESH */
1100 
1101 #if SHOPT_ESH
1102 /*
1103  * given the current command and line number,
1104  * and number of lines back or foward,
1105  * compute the new command and line number.
1106  */
1107 
1108 Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines)
1109 {
1110 	Histloc_t next;
1111 	line += lines;
1112 	if(!hp)
1113 	{
1114 		command = -1;
1115 		goto done;
1116 	}
1117 	if(lines > 0)
1118 	{
1119 		register int count;
1120 		while(command <= hp->histind)
1121 		{
1122 			count = hist_copy(NIL(char*),0, command,-1);
1123 			if(count > line)
1124 				goto done;
1125 			line -= count;
1126 			command++;
1127 		}
1128 	}
1129 	else
1130 	{
1131 		register int least = (int)hp->histind-hp->histsize;
1132 		while(1)
1133 		{
1134 			if(line >=0)
1135 				goto done;
1136 			if(--command < least)
1137 				break;
1138 			line += hist_copy(NIL(char*),0, command,-1);
1139 		}
1140 		command = -1;
1141 	}
1142 done:
1143 	next.hist_line = line;
1144 	next.hist_command = command;
1145 	return(next);
1146 }
1147 #endif	/* SHOPT_ESH */
1148 
1149 
1150 /*
1151  * Handle history file exceptions
1152  */
1153 #ifdef SF_BUFCONST
1154 static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
1155 #else
1156 static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle)
1157 #endif
1158 {
1159 	register int newfd,oldfd;
1160 	History_t *hp = (History_t*)handle;
1161 	if(type==SF_WRITE)
1162 	{
1163 		if(errno==ENOSPC || hp->histwfail++ >= 10)
1164 			return(0);
1165 		/* write failure could be NFS problem, try to re-open */
1166 		close(oldfd=sffileno(fp));
1167 		if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0)
1168 		{
1169 			if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd)
1170 				return(-1);
1171 			fcntl(oldfd,F_SETFD,FD_CLOEXEC);
1172 			close(newfd);
1173 			if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt)
1174 			{
1175 				register int index = hp->histind;
1176 				lseek(oldfd,(off_t)2,SEEK_SET);
1177 				hp->histcnt = 2;
1178 				hp->histind = 1;
1179 				hp->histcmds[1] = 2;
1180 				hist_eof(hp);
1181 				hp->histmarker = hp->histcnt;
1182 				hp->histind = index;
1183 			}
1184 			return(1);
1185 		}
1186 		errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname);
1187 		return(-1);
1188 	}
1189 	return(0);
1190 }
1191