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 */
hist_open()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
hist_check(fd)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
hist_trim(fdo,n)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
hist_nearend(fd,size)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
hist_close()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
hist_eof()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
hist_cancel()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
hist_flush()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
hist_position(n)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
hist_list(offset,last,nl)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
hist_find(string,index1,flag,direction)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
hist_copy(s1,command,line)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
hist_word(s1,word)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
hist_locate(command,line,lines)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 */
hist_subst(command,fd,replace)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