xref: /original-bsd/local/toolchest/ksh/sh/history.c (revision 7323bcb8)
1 /*
2 
3  *      Copyright (c) 1984, 1985, 1986 AT&T
4  *      All Rights Reserved
5 
6  *      THIS IS UNPUBLISHED PROPRIETARY SOURCE
7  *      CODE OF AT&T.
8  *      The copyright notice above does not
9  *      evidence any actual or intended
10  *      publication of such source code.
11 
12  */
13 /* @(#)history.c	1.1 */
14 
15 /*
16  *   History file manipulation routines
17  *
18  *   David Korn
19  *   AT&T Bell Laboratories
20  *   Room 5D-112
21  *   Murray Hill, N. J. 07974
22  *   Tel. x7975
23  *
24  */
25 
26 
27 /*
28  * Each command in the history file starts on an even byte is null terminated.
29  * The first byte must contain the special character H_UNDO and the second
30  * byte is the version number.  The sequence H_UNDO 0, following a command,
31  * nullifies the previous command. A six byte sequence starting with
32  * H_CMDNO is used to store the command number so that it is not necessary
33  * to read the file from beginning to end to get to the last block of
34  * commands.  This format of this sequence is different in version 1
35  * then in version 0.  Version 1 allows commands to use the full 8 bit
36  * character set.  It can understand version 0 format files.
37  */
38 
39 
40 #ifdef KSHELL
41 #include	"defs.h"
42 #include	"io.h"
43 #include	"flags.h"
44 #include	"name.h"
45 #include	"shtype.h"
46 #include	"stak.h"
47 #include	"brkincr.h"
48 #include	"builtins.h"
49 #else
50 #include	<stdio.h>
51 #include	<setjmp.h>
52 #include	<signal.h>
53 #include	<ctype.h>
54 #endif	/* KSHELL */
55 
56 #include	"history.h"
57 #ifdef MULTIBYTE
58 #include	"national.h"
59 #endif /* MULTIBYTE */
60 
61 int	hist_open();
62 void	hist_close();
63 long	hist_list();
64 void	hist_flush();
65 void	hist_cancel();
66 void	hist_eof();
67 histloc	hist_find();
68 #ifdef ESH
69 histloc	hist_locate();
70 #endif	/* ESH */
71 long	hist_position();
72 #ifdef KSHELL
73 void	hist_subst();
74 #endif	/* KSHELL */
75 
76 #ifdef KSHELL
77 extern char	*valup();
78 extern long	aeval();
79 extern FILE	*chkrdwr();
80 extern FILE	*create();
81 extern void	failed();
82 extern void	p_str();
83 #else
84 #define frenumber		hist_rename
85 #define tmp_open(s)	tmpfile()
86 #define p_str(s,c)	(fputs(s,stderr),putc(c,stderr))
87 #define closefd(f)	fclose(f)
88 #define	aeval(str)	atoi(str)
89 #define TMPSIZ	20
90 #define NL	'\n'
91 #define output	stderr
92 struct fixcmd *fc_fix;
93 extern char	*getenv();
94 extern FILE	*hist_rename();
95 char login_sh = 0;
96 MSG histfname = "./history";
97 #define unknown "unknown"
98 #endif	/* KSHELL */
99 extern char	*substitute();
100 extern FILE	*tmp_open();
101 extern long	lseek();
102 extern char	*malloc();
103 extern char	*movstr();
104 extern void	free();
105 
106 static int fixmask;
107 static void hist_trim();
108 static int hist_nearend();
109 static int hist_check();
110 static int hist_version;
111 static int heof;
112 
113 
114 /*
115  * open the history file
116  * if HISTNAME is not given and userid==0 then no history file.
117  * if login_sh and HISTFILE is longer than HISTMAX bytes then it is
118  * cleaned up.
119  */
120 int  hist_open()
121 {
122 	register FILE *fd;
123 	register struct fixcmd *fp;
124 	register char *histname;
125 	char fname[TMPSIZ];
126 	char hname[256];
127 	int maxlines;
128 	register char *cp;
129 	register long hsize = 0;
130 	int his_start;
131 
132 	if(fc_fix)
133 		return(0);
134 	histname = valup(HISTFILE);
135 	if(histname==NULL)
136 	{
137 #ifdef KSHELL
138 		if(userid==0 && login_sh)
139 			return(-1);
140 #endif	/* KSHELL */
141 		cp = movstr(valup(HOME),hname);
142 		movstr(histfname,cp);
143 		histname = hname;
144 	}
145 	*fname = 0;
146 retry:
147 	/* first try to open the current file */
148 #ifdef KSHELL
149 	if((fd=fdopen(open(histname,012),"a+"))==NULL)
150 	{
151 		/* if you can't then try to create it */
152 		if(fd=create(histname))
153 		{
154 			fd = chkrdwr(histname,fd);
155 			chmod(histname,0600);
156 		}
157 	}
158 	else
159 		hsize=lseek(fileno(fd),0L,2);
160 #else
161 	if(fd=fopen(histname,"a+"))
162 	{
163 		chmod(histname,0600);
164 		hsize=lseek(fileno(fd),0L,2);
165 	}
166 #endif	/* KSHELL */
167 	/* make sure that file has history file format */
168 	if(hsize && hist_check(fd))
169 	{
170 		fclose(fd);
171 		unlink(histname);
172 		hsize = 0;
173 		goto retry;
174 	}
175 	if(fd == NULL)
176 		fd = tmp_open(fname);
177 	if(fd==NULL)
178 		return(-1);
179 	fd = frenumber(fd,FCIO);
180 	if(cp=valup(HISTSIZE))
181 		maxlines = (unsigned)aeval(cp);
182 	else
183 		maxlines = HIS_DFLT;
184 	for(fixmask=16;fixmask <= maxlines; fixmask <<=1 );
185 	if((fp=(struct fixcmd*)malloc(sizeof(struct fixcmd)+ (--fixmask)*sizeof(long)))==NULL)
186 	{
187 		fclose(fd);
188 		return(-1);
189 	}
190 	fc_fix = fp;
191 	fp->fixfd = fd;
192 	fp->fixmax = maxlines;
193 	setbuf(fd,malloc(BUFSIZ));
194 	fp->fixind = 1;
195 	fp->fixline = 0;
196 	fp->fixcmds[1] = 2;
197 	fp->fixcnt = 2;
198 	if(hsize==0)
199 	{
200 		/* put special characters at front of file */
201 		putc(H_UNDO,fd);
202 		putc(H_VERSION,fd);
203 		fflush(fd);
204 	}
205 	/* initialize history list */
206 	if(hsize)
207 	{
208 		int nlines = maxlines;
209 		long size = hsize - (HISMAX/4);
210 		do
211 		{
212 			size -= ((HISMAX/4) + nlines*HISLINE);
213 			his_start = fp->fixind = hist_nearend(fd,size);
214 			hist_eof();
215 			nlines = maxlines - (fp->fixind-his_start);
216 		}
217 		while(his_start >1 && nlines>0);
218 	}
219 	if(*fname)
220 		unlink(fname);
221 	if(login_sh && his_start>1 && hsize > HISMAX)
222 	{
223 		FILE *fdo;
224 		if((fdo=fdopen(open(histname,0),"r"))==NULL)
225 			return(0);
226 		unlink(histname);
227 		hist_trim(fdo,fp->fixind-maxlines);
228 	}
229 	return(0);
230 }
231 
232 /*
233  * check history file format to see if it begins with special byte
234  */
235 
236 static int hist_check(fd)
237 register FILE *fd;
238 {
239 	setbuf(fd,NULL);
240 	fseek(fd,0L,0);
241 	if(getc(fd) != H_UNDO)
242 		return(1);
243 	hist_version = getc(fd);
244 	return(0);
245 }
246 
247 /*
248  * Copy the last <n> commands to a new file and make this the history file
249  */
250 
251 static void hist_trim(fdo,n)
252 register FILE *fdo;
253 {
254 	register FILE *fd;
255 	register int c;
256 	register struct fixcmd *fp = fc_fix;
257 	struct fixcmd *fsave;
258 	/* use the old history I/O buffer for fdo */
259 	setbuf(fdo,fp->fixfd->_base);
260 	setbuf(fp->fixfd,NULL);
261 	fc_fix = 0;
262 	hist_open();
263 	if(fc_fix==0)
264 		return;
265 	fsave = fc_fix;
266 	fd = fc_fix->fixfd;
267 	do
268 	{
269 		fc_fix = fp;
270 		fseek(fdo,hist_position(++n),0);
271 		fc_fix = fsave;
272 		while((c=getc(fdo))!=EOF && c)
273 		{
274 			putc(c,fd);
275 		}
276 #ifdef KSHELL
277 		states |= FIXFLG;
278 #endif	/* KSHELL */
279 		hist_flush();
280 	}
281 	while(c!=EOF);
282 	fclose(fdo);
283 	free((char*)fdo->_base);
284 	free((char*)fp);
285 }
286 
287 /*
288  * position history file at size and find next command number
289  */
290 
291 static int hist_nearend(fd,size)
292 register FILE *fd;
293 long size;
294 {
295 	register int n = 0;
296 	register int state = -1;
297 	register int c;
298 	if(size <=0)
299 		goto begin;
300 	fseek(fd,size,0);
301 	/* skip to numbered command and return the number */
302 	/* numbering commands occur after a null and begin with H_CMDNO */
303 	while((c=getc(fd))!=EOF)
304 	{
305 		if(state==5)
306 		{
307 			return(n);
308 		}
309 		else if(state>0)
310 		{
311 			if(state==1)
312 			{
313 				/* see if H_CMDNO is followed by 0 */
314 				if(hist_version && c)
315 				{
316 					n += 2;
317 					state = -1;
318 					continue;
319 				}
320 				n = 0;
321 			}
322 			if(hist_version)
323 				n = (n<<8) + c;
324 			else if(state < 3)
325 				n = (n<<7) + (c&0177);
326 			state++;
327 		}
328 		else if(state==0 && c==H_CMDNO)
329 		{
330 			fc_fix->fixcnt = size + n + 6;
331 			state = 1;
332 		}
333 		else
334 		{
335 			state = (c==0?0:-1);
336 			n++;
337 		}
338 	}
339 begin:
340 	fseek(fd,2L,0);
341 	fc_fix->fixcnt = 2;
342 	return(1);
343 }
344 
345 /*
346  * This routine unlinks the history file if the file is a temp file
347  */
348 
349 void hist_close()
350 {
351 	if(fc_fix)
352 		fclose(fc_fix->fixfd);
353 }
354 
355 /*
356  * This routine reads the history file from the present position
357  * to the end-of-file and puts the information in the in-core
358  * history table
359  * Note that H_CMDNO is only recognized at the beginning of a command
360  * and that H_UNDO as the first character of a command is skipped
361  * unless it is followed by 0.  If followed by 0 then it cancels
362  * the previous command.
363  */
364 
365 void hist_eof()
366 {
367 	register struct fixcmd *fp = fc_fix;
368 	register int c;
369 	register int incr = 0;
370 	register int oldc = 0;
371 	register long count = fp->fixcnt;
372 	int skip = 0;
373 	heof++;		/* don't add line number markers */
374 	fseek(fp->fixfd,count,0);
375 	while((c=getc(fp->fixfd))!=EOF)
376 	{
377 		count++;
378 		if(skip-- > 0)
379 		{
380 			oldc = 0;
381 			continue;
382 		}
383 		if(c == 0)
384 		{
385 			if(oldc==H_CMDNO && incr==0)
386 				skip = 3;
387 			fp->fixind += incr;
388 			fp->fixcmds[fp->fixind&fixmask] = count;
389 			incr = 0;
390 		}
391 		else if(oldc == 0)
392 		{
393 			if(c == H_CMDNO)
394 			{
395 				/* old format history file */
396 				if(hist_version==0)
397 					skip = 4;
398 				incr = 0;
399 			}
400 			else if(c==H_UNDO)
401 				incr = -1;
402 		}
403 		else
404 			incr = 1;
405 		oldc = c;
406 	}
407 	fp->fixline = 0;
408 	fp->fixcnt = count;
409 	heof = 0;
410 }
411 
412 /*
413  * This routine will cause the previous command to be cancelled
414  */
415 
416 void hist_cancel()
417 {
418 	register struct fixcmd *fp = fc_fix;
419 	register FILE *fd;
420 	register int c;
421 	if(fp==NULL)
422 		return;
423 	fd = fp->fixfd;
424 	putc(H_UNDO,fd);
425 	putc(0,fd);
426 	fflush(fd);
427 	fp->fixcnt += 2;
428 	c = (--fp->fixind)&fixmask;
429 	fp->fixcmds[c] = fp->fixcnt;
430 }
431 
432 /*
433  * This routine adds one or two null bytes and flushes the history buffer
434  */
435 
436 void hist_flush()
437 {
438 	register struct fixcmd *fp = fc_fix;
439 	register FILE *fd;
440 	register int c;
441 	if(fp==NULL)
442 		return;
443 #ifdef KSHELL
444 	if((states&FIXFLG) == 0)
445 		return;
446 	states &= ~FIXFLG;
447 #endif	/* KSHELL */
448 	fd = fp->fixfd;
449 	fp->fixline = 0;
450 	/* remove whitespace from end of commands */
451 	while(--fd->_ptr >= fd->_base)
452 	{
453 		if((c= *fd->_ptr)!=NL && !isspace(c))
454 			break;
455 	}
456 	fd->_cnt = ++fd->_ptr - fd->_base;
457 	if(fd->_cnt<=0)
458 	{
459 		fp->fixind--;
460 		goto set_count;
461 	}
462 	putc(NL,fd);
463 	putc('\0',fd);
464 	fflush(fd);
465 set_count:
466 	fp->fixcnt = lseek(fileno(fd),0L,2);
467 	/* start each command on an even byte boundary */
468 	if(fp->fixcnt&01)
469 	{
470 		fp->fixcnt++;
471 		putc('\0',fd);
472 		fflush(fd);
473 	}
474 	c = (++fp->fixind)&fixmask;
475 	fp->fixcmds[c] = fp->fixcnt;
476 	if((c = fp->fixcmds[c])> (HISMAX/4) && !heof)
477 	{
478 		/* put line number in file */
479 		fp->fixcnt += 6;
480 		putc(H_CMDNO,fd);
481 		putc(0,fd);
482 		c = (fp->fixind>>16);
483 		putc(c,fd);
484 		c = (fp->fixind>>8);
485 		putc(c,fd);
486 		c = fp->fixind;
487 		putc(c,fd);
488 		putc(0,fd);
489 		fflush(fd);
490 		fp->fixcmds[c&fixmask] = fp->fixcnt;
491 	}
492 }
493 
494 /*
495  * return byte offset in history file for command <n>
496  */
497 
498 long hist_position(n)
499 int n;
500 {
501 	register struct fixcmd *fp = fc_fix;
502 	return(fp->fixcmds[n&fixmask]);
503 }
504 
505 /*
506  * write the command starting at offset <offset> onto file <fd>.
507  * listing stops when character <last> is encountered or end-of-string.
508  * each new-line character is replaced with string <nl>.
509  */
510 
511 long hist_list(offset,last,nl)
512 long offset;
513 int last;
514 char *nl;
515 {
516 	register int oldc;
517 	register FILE *fd;
518 	register int c;
519 	if(offset<0)
520 	{
521 		p_str(unknown,NL);
522 		return(-1);
523 	}
524 	fd = fc_fix->fixfd;
525 	fseek(fd,offset,0);
526 	oldc=getc(fd);
527 	for(offset++;oldc && oldc!=last;oldc=c,offset++)
528 	{
529 		if((c = getc(fd)) == EOF)
530 			return(offset);
531 		if(oldc=='\n')
532 		{
533 			if(c)
534 			{
535 				fputs(nl,output);
536 				continue;
537 			}
538 			/* don't print trailing newline for job control */
539 			else if(last=='&')
540 				return(offset);
541 		}
542 		putc(oldc,output);
543 	}
544 	return(offset);
545 }
546 
547 /*
548  * find index for last line with given string
549  * If flag==0 then line must begin with string
550  * direction < 1 for backwards search
551 */
552 
553 histloc hist_find(string,index1,flag,direction)
554 char *string;
555 int index1;
556 int direction;
557 {
558 	register struct fixcmd *fp = fc_fix;
559 	register char *cp;
560 	register int c;
561 	long offset;
562 	int count;
563 	int index2;
564 	histloc location;
565 #ifdef MULTIBYTE
566 	int nbytes = 0;
567 #endif /* MULTIBYTE */
568 	location.his_command = -1;
569 	if(fp==NULL)
570 		return(location);
571 	index2 = fp->fixind;
572 	if(direction<0)
573 	{
574 		index2 -= fp->fixmax;
575 		if(index2<1)
576 			index2 = 1;
577 		if(index1 <= index2)
578 			return(location);
579 	}
580 	else if(index1 >= index2)
581 		return(location);
582 	while(index1!=index2)
583 	{
584 		direction>0?++index1:--index1;
585 		offset = hist_position(index1);
586 		location.his_line = 0;
587 #ifdef KSHELL
588 		/* allow a search to be aborted */
589 		if(trapnote&SIGSET)
590 			exitsh(SIGFAIL);
591 #endif /* KSHELL */
592 		do
593 		{
594 			if(offset>=0)
595 			{
596 				fseek(fp->fixfd,offset,0);
597 				count = offset;
598 			}
599 			offset = -1;
600 			for(cp=string;*cp;cp++)
601 			{
602 				if((c=getc(fp->fixfd)) == EOF)
603 					break;
604 				if(c == 0)
605 					break;
606 				count++;
607 #ifdef MULTIBYTE
608 				/* always position at character boundary */
609 				if(--nbytes > 0)
610 				{
611 					if(cp==string)
612 					{
613 						cp--;
614 						continue;
615 					}
616 				}
617 				else
618 				{
619 					nbytes = echarset(c);
620 					nbytes = in_csize(nbytes) + (nbytes>=2);
621 				}
622 #endif /* MULTIBYTE */
623 				if(c == '\n')
624 					location.his_line++;
625 				/* save earliest possible matching character */
626 				if(flag && c == *string && offset<0)
627 					offset = count;
628 				if(*cp != c )
629 					break;
630 			}
631 			if(*cp==0)
632 				/* match found */
633 			{
634 				location.his_command = index1;
635 				return(location);
636 			}
637 		}
638 		while(flag && c && c != EOF);
639 	}
640 	fseek(fp->fixfd,0L,2);
641 	return(location);
642 }
643 
644 #if ESH || VSH
645 /*
646  * copy command <command> from history file to s1
647  * at most MAXLINE characters copied
648  * if s1==0 the number of lines for the command is returned
649  * line=linenumber  for emacs copy and only this line of command will be copied
650  * line < 0 for full command copy
651  * -1 returned if there is no history file
652  */
653 
654 int hist_copy(s1,command,line)
655 register char *s1;
656 {
657 	register int c;
658 	register struct fixcmd *fp = fc_fix;
659 	register int count = 0;
660 	register char *s1max = s1+MAXLINE;
661 	long offset;
662 	if(fp==NULL)
663 		return(-1);
664 	offset =  hist_position(command);
665 	fseek(fp->fixfd,offset,0);
666 	while ((c = getc(fp->fixfd)) && c!=EOF)
667 	{
668 		if(c=='\n')
669 		{
670 			if(count++ ==line)
671 				break;
672 			else if(line >= 0)
673 				continue;
674 		}
675 		if(s1 && (line<0 || line==count))
676 		{
677 			if(s1 >= s1max)
678 			{
679 				*--s1 = 0;
680 				break;
681 			}
682 			*s1++ = c;
683 		}
684 
685 	}
686 	if(s1==0)
687 		return(count);
688 	if((c= *(s1-1)) == '\n')
689 		s1--;
690 	*s1 = '\0';
691 	fseek(fp->fixfd,0L,2);
692 	return(count);
693 }
694 
695 /*
696  * return word number <word> from command number <command>
697  */
698 
699 char *hist_word(s1,word)
700 char *s1;
701 {
702 	register int c;
703 	register char *cp = s1;
704 	register int flag = 0;
705 	if(fc_fix==0)
706 #ifdef KSHELL
707 		return(lastarg);
708 #else
709 		return(NULL);
710 #endif /* KSHELL */
711 	hist_copy(s1,fc_fix->fixind-1,-1);
712 	for(;c = *cp;cp++)
713 	{
714 		c = isspace(c);
715 		if(c && flag)
716 		{
717 			*cp = 0;
718 			if(--word==0)
719 				break;
720 			flag = 0;
721 		}
722 		else if(c==0 && flag==0)
723 		{
724 			s1 = cp;
725 			flag++;
726 		}
727 	}
728 	*cp = 0;
729 	return(s1);
730 }
731 
732 #endif	/* ESH */
733 
734 #ifdef ESH
735 /*
736  * given the current command and line number,
737  * and number of lines back or foward,
738  * compute the new command and line number.
739  */
740 
741 histloc hist_locate(command,line,lines)
742 register int command;
743 register int line;
744 int lines;
745 {
746 	histloc next;
747 	line += lines;
748 	if(fc_fix==NULL)
749 	{
750 		command = -1;
751 		goto done;
752 	}
753 	if(lines > 0)
754 	{
755 		register int count;
756 		while(command <= fc_fix->fixind)
757 		{
758 			count = hist_copy(NIL,command,-1);
759 			if(count > line)
760 				goto done;
761 			line -= count;
762 			command++;
763 		}
764 	}
765 	else
766 	{
767 		register int least = fc_fix->fixind-fc_fix->fixmax;
768 		while(1)
769 		{
770 			if(line >=0)
771 				goto done;
772 			if(--command < least)
773 				break;
774 			line += hist_copy(NIL,command,-1);
775 		}
776 		command = -1;
777 	}
778 	next.his_command = command;
779 	return(next);
780 done:
781 	next.his_line = line;
782 	next.his_command = command;
783 	return(next);
784 }
785 #endif	/* ESH */
786 
787 #ifdef KSHELL
788 
789 /*
790  * given a file containing a command and a string of the form old=new,
791  * execute the command with the string old replaced by new
792  */
793 void hist_subst(command,fd,replace)
794 char *command;
795 FILE *fd;
796 char *replace;
797 {
798 	register char *new=replace;
799 	register char *sp = locstak();
800 	register int c;
801 	char *string;
802 	while(*++new != '='); /* skip to '=' */
803 	while ((c=getc(fd)) != EOF)
804 		*sp++ = c;
805 	string = endstak(sp);
806 	fclose(fd);
807 	*new++ =  0;
808 	if(substitute(string,replace,new,(sp=locstak())))
809 		endstak(sp+strlen(sp));
810 	else
811 		failed(command,badsub);
812 	*(new-1) =  '=';
813 	fputs(sp,fc_fix->fixfd);
814 	hist_flush();
815 	fputs(sp,output);
816 	execexp(sp,(FILE*)0);
817 }
818 #endif	/* KSHELL */
819