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