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