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