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