1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1982-2008 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 <ctype.h> 78 #include <ls.h> 79 #if KSHELL 80 # include "defs.h" 81 # include "variables.h" 82 # include "path.h" 83 # include "builtins.h" 84 # include "io.h" 85 #endif /* KSHELL */ 86 #include "history.h" 87 88 #if !KSHELL 89 # define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x))) 90 # define NIL(type) ((type)0) 91 # define path_relative(x) (x) 92 # ifdef __STDC__ 93 # define nv_getval(s) getenv(#s) 94 # else 95 # define nv_getval(s) getenv("s") 96 # endif /* __STDC__ */ 97 # define e_unknown "unknown" 98 # define sh_translate(x) (x) 99 char login_sh = 0; 100 char hist_fname[] = "/.history"; 101 #endif /* KSHELL */ 102 103 #ifndef O_BINARY 104 # define O_BINARY 0 105 #endif /* O_BINARY */ 106 107 int _Hist = 0; 108 static void hist_marker(char*,long); 109 static History_t* hist_trim(History_t*, int); 110 static int hist_nearend(History_t*,Sfio_t*, off_t); 111 static int hist_check(int); 112 static int hist_clean(int); 113 #ifdef SF_BUFCONST 114 static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*); 115 static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*); 116 #else 117 static int hist_write(Sfio_t*, const void*, int, Sfdisc_t*); 118 static int hist_exceptf(Sfio_t*, int, Sfdisc_t*); 119 #endif 120 121 122 static int histinit; 123 static mode_t histmode; 124 static History_t *wasopen; 125 static History_t *hist_ptr; 126 127 #if SHOPT_ACCTFILE 128 static int acctfd; 129 static char *logname; 130 # include <pwd.h> 131 132 static int acctinit(History_t *hp) 133 { 134 register char *cp, *acctfile; 135 Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0); 136 137 if(!np || !(acctfile=nv_getval(np))) 138 return(0); 139 if(!(cp = getlogin())) 140 { 141 struct passwd *userinfo = getpwuid(getuid()); 142 if(userinfo) 143 cp = userinfo->pw_name; 144 else 145 cp = "unknown"; 146 } 147 logname = strdup(cp); 148 if((acctfd=sh_open(acctfile, 149 O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && 150 (unsigned)acctfd < 10) 151 { 152 int n; 153 if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0) 154 { 155 close(acctfd); 156 acctfd = n; 157 } 158 } 159 if(acctfd < 0) 160 { 161 acctfd = 0; 162 return(0); 163 } 164 if(strmatch(acctfile,e_devfdNN)) 165 { 166 char newfile[16]; 167 sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd); 168 nv_putval(np,newfile,NV_RDONLY); 169 } 170 else 171 fcntl(acctfd,F_SETFD,FD_CLOEXEC); 172 return(1); 173 } 174 #endif /* SHOPT_ACCTFILE */ 175 176 #if SHOPT_AUDIT 177 static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len) 178 { 179 Shell_t *shp = (Shell_t*)hp->histshell; 180 char *buff, *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(shp->euserid >=id1 && shp->euserid <= id2) 199 r |= 1; 200 if(shp->userid >=id1 && shp->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 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 */ 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(shp->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 shp->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(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(shp->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 shp->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 hist_eof(hp); /* this sets histind to last command */ 338 if((hist_start = (last=(int)hp->histind)-maxlines) <=0) 339 hist_start = 1; 340 mark = hp->histmarker; 341 while(first > hist_start) 342 { 343 size += size; 344 first = hist_nearend(hp,hp->histfp,hsize-size); 345 hp->histind = first; 346 } 347 histinit = hist_start; 348 hist_eof(hp); 349 if(!histinit) 350 { 351 sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET); 352 hp->histind = last; 353 hp->histmarker = mark; 354 } 355 histinit = 0; 356 } 357 if(fname) 358 { 359 unlink(fname); 360 free((void*)fname); 361 } 362 if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX) 363 { 364 #ifdef DEBUG 365 sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize); 366 sfsync(sfstderr); 367 #endif /* DEBUG */ 368 hp = hist_trim(hp,(int)hp->histind-maxlines); 369 } 370 sfdisc(hp->histfp,&hp->histdisc); 371 #if KSHELL 372 (HISTCUR)->nvalue.lp = (&hp->histind); 373 #endif /* KSHELL */ 374 sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname); 375 #if SHOPT_ACCTFILE 376 if(sh_isstate(SH_INTERACTIVE)) 377 acctinit(hp); 378 #endif /* SHOPT_ACCTFILE */ 379 #if SHOPT_AUDIT 380 { 381 char buff[SF_BUFSIZE]; 382 hp->auditfp = 0; 383 if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff)))) 384 { 385 if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10) 386 { 387 int n; 388 if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0) 389 { 390 sh_close(fd); 391 fd = n; 392 } 393 } 394 if(fd>=0) 395 { 396 hp->tty = strdup(ttyname(2)); 397 hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE); 398 } 399 } 400 } 401 #endif 402 return(1); 403 } 404 405 /* 406 * close the history file and free the space 407 */ 408 409 void hist_close(register History_t *hp) 410 { 411 Shell_t *shp = (Shell_t*)hp->histshell; 412 sfclose(hp->histfp); 413 #if SHOPT_AUDIT 414 if(hp->auditfp) 415 { 416 if(hp->tty) 417 free((void*)hp->tty); 418 sfclose(hp->auditfp); 419 } 420 #endif /* SHOPT_AUDIT */ 421 free((char*)hp); 422 hist_ptr = 0; 423 shp->hist_ptr = 0; 424 #if SHOPT_ACCTFILE 425 if(acctfd) 426 { 427 close(acctfd); 428 acctfd = 0; 429 } 430 #endif /* SHOPT_ACCTFILE */ 431 } 432 433 /* 434 * check history file format to see if it begins with special byte 435 */ 436 static int hist_check(register int fd) 437 { 438 unsigned char magic[2]; 439 lseek(fd,(off_t)0,SEEK_SET); 440 if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO)) 441 return(1); 442 return(0); 443 } 444 445 /* 446 * clean out history file OK if not modified in HIST_RECENT seconds 447 */ 448 static int hist_clean(int fd) 449 { 450 struct stat statb; 451 return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT); 452 } 453 454 /* 455 * Copy the last <n> commands to a new file and make this the history file 456 */ 457 458 static History_t* hist_trim(History_t *hp, int n) 459 { 460 register char *cp; 461 register int incmd=1, c=0; 462 register History_t *hist_new, *hist_old = hp; 463 char *buff, *endbuff, *tmpname=0; 464 off_t oldp,newp; 465 struct stat statb; 466 unlink(hist_old->histname); 467 if(access(hist_old->histname,F_OK) >= 0) 468 { 469 /* The unlink can fail on windows 95 */ 470 int fd; 471 char *last, *name=hist_old->histname; 472 close(sffileno(hist_old->histfp)); 473 tmpname = (char*)malloc(strlen(name)+14); 474 if(last = strrchr(name,'/')) 475 { 476 *last = 0; 477 pathtmp(tmpname,name,"hist",NIL(int*)); 478 *last = '/'; 479 } 480 else 481 pathtmp(tmpname,".","hist",NIL(int*)); 482 if(rename(name,tmpname) < 0) 483 tmpname = name; 484 fd = open(tmpname,O_RDONLY); 485 sfsetfd(hist_old->histfp,fd); 486 if(tmpname==name) 487 tmpname = 0; 488 } 489 hist_ptr = 0; 490 if(fstat(sffileno(hist_old->histfp),&statb)>=0) 491 { 492 histinit = 1; 493 histmode = statb.st_mode; 494 } 495 if(!sh_histinit(hp->histshell)) 496 { 497 /* use the old history file */ 498 return hist_ptr = hist_old; 499 } 500 hist_new = hist_ptr; 501 hist_ptr = hist_old; 502 if(--n < 0) 503 n = 0; 504 newp = hist_seek(hist_old,++n); 505 while(1) 506 { 507 if(!incmd) 508 { 509 c = hist_ind(hist_new,++hist_new->histind); 510 hist_new->histcmds[c] = hist_new->histcnt; 511 if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2) 512 { 513 char locbuff[HIST_MARKSZ]; 514 hist_marker(locbuff,hist_new->histind); 515 sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ); 516 hist_new->histcnt += HIST_MARKSZ; 517 hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt; 518 } 519 oldp = newp; 520 newp = hist_seek(hist_old,++n); 521 if(newp <=oldp) 522 break; 523 } 524 if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0))) 525 break; 526 *(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0; 527 /* copy to null byte */ 528 incmd = 0; 529 while(*cp++); 530 if(cp > endbuff) 531 incmd = 1; 532 else if(*cp==0) 533 cp++; 534 if(cp > endbuff) 535 cp = endbuff; 536 c = cp-buff; 537 hist_new->histcnt += c; 538 sfwrite(hist_new->histfp,buff,c); 539 } 540 hist_cancel(hist_new); 541 sfclose(hist_old->histfp); 542 if(tmpname) 543 { 544 unlink(tmpname); 545 free(tmpname); 546 } 547 free((char*)hist_old); 548 return hist_ptr = hist_new; 549 } 550 551 /* 552 * position history file at size and find next command number 553 */ 554 static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size) 555 { 556 register unsigned char *cp, *endbuff; 557 register int n, incmd=1; 558 unsigned char *buff, marker[4]; 559 if(size <= 2L || sfseek(iop,size,SEEK_SET)<0) 560 goto begin; 561 /* skip to marker command and return the number */ 562 /* numbering commands occur after a null and begin with HIST_CMDNO */ 563 while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR)) 564 { 565 n = sfvalue(iop); 566 *(endbuff=cp+n) = 0; 567 while(1) 568 { 569 /* check for marker */ 570 if(!incmd && *cp++==HIST_CMDNO && *cp==0) 571 { 572 n = cp+1 - buff; 573 incmd = -1; 574 break; 575 } 576 incmd = 0; 577 while(*cp++); 578 if(cp>endbuff) 579 { 580 incmd = 1; 581 break; 582 } 583 if(*cp==0 && ++cp>endbuff) 584 break; 585 } 586 size += n; 587 sfread(iop,(char*)buff,n); 588 if(incmd < 0) 589 { 590 if((n=sfread(iop,(char*)marker,4))==4) 591 { 592 n = (marker[0]<<16)|(marker[1]<<8)|marker[2]; 593 if(n < size/2) 594 { 595 hp->histmarker = hp->histcnt = size+4; 596 return(n); 597 } 598 n=4; 599 } 600 if(n >0) 601 size += n; 602 incmd = 0; 603 } 604 } 605 begin: 606 sfseek(iop,(off_t)2,SEEK_SET); 607 hp->histmarker = hp->histcnt = 2L; 608 return(1); 609 } 610 611 /* 612 * This routine reads the history file from the present position 613 * to the end-of-file and puts the information in the in-core 614 * history table 615 * Note that HIST_CMDNO is only recognized at the beginning of a command 616 * and that HIST_UNDO as the first character of a command is skipped 617 * unless it is followed by 0. If followed by 0 then it cancels 618 * the previous command. 619 */ 620 621 void hist_eof(register History_t *hp) 622 { 623 register char *cp,*first,*endbuff; 624 register int incmd = 0; 625 register off_t count = hp->histcnt; 626 int n,skip=0; 627 sfseek(hp->histfp,count,SEEK_SET); 628 while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0)) 629 { 630 n = sfvalue(hp->histfp); 631 *(endbuff = cp+n) = 0; 632 first = cp += skip; 633 while(1) 634 { 635 while(!incmd) 636 { 637 if(cp>first) 638 { 639 count += (cp-first); 640 n = hist_ind(hp, ++hp->histind); 641 #ifdef future 642 if(count==hp->histcmds[n]) 643 { 644 sfprintf(sfstderr,"count match n=%d\n",n); 645 if(histinit) 646 { 647 histinit = 0; 648 return; 649 } 650 } 651 else if(n>=histinit) 652 #endif 653 hp->histcmds[n] = count; 654 first = cp; 655 } 656 switch(*((unsigned char*)(cp++))) 657 { 658 case HIST_CMDNO: 659 if(*cp==0) 660 { 661 hp->histmarker=count+2; 662 cp += (HIST_MARKSZ-1); 663 hp->histind--; 664 #ifdef future 665 if(cp <= endbuff) 666 { 667 unsigned char *marker = (unsigned char*)(cp-4); 668 int n = ((marker[0]<<16) 669 |(marker[1]<<8)|marker[2]); 670 if((n<count/2) && n != (hp->histind+1)) 671 errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n); 672 } 673 #endif 674 } 675 break; 676 case HIST_UNDO: 677 if(*cp==0) 678 { 679 cp+=1; 680 hp->histind-=2; 681 } 682 break; 683 default: 684 cp--; 685 incmd = 1; 686 } 687 if(cp > endbuff) 688 { 689 cp++; 690 goto refill; 691 } 692 } 693 first = cp; 694 while(*cp++); 695 if(cp > endbuff) 696 break; 697 incmd = 0; 698 while(*cp==0) 699 { 700 if(++cp > endbuff) 701 goto refill; 702 } 703 } 704 refill: 705 count += (--cp-first); 706 skip = (cp-endbuff); 707 if(!incmd && !skip) 708 hp->histcmds[hist_ind(hp,++hp->histind)] = count; 709 } 710 hp->histcnt = count; 711 } 712 713 /* 714 * This routine will cause the previous command to be cancelled 715 */ 716 717 void hist_cancel(register History_t *hp) 718 { 719 register int c; 720 if(!hp) 721 return; 722 sfputc(hp->histfp,HIST_UNDO); 723 sfputc(hp->histfp,0); 724 sfsync(hp->histfp); 725 hp->histcnt += 2; 726 c = hist_ind(hp,--hp->histind); 727 hp->histcmds[c] = hp->histcnt; 728 } 729 730 /* 731 * flush the current history command 732 */ 733 734 void hist_flush(register History_t *hp) 735 { 736 register char *buff; 737 if(hp) 738 { 739 if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR)) 740 { 741 hp->histflush = sfvalue(hp->histfp)+1; 742 sfwrite(hp->histfp,buff,0); 743 } 744 else 745 hp->histflush=0; 746 if(sfsync(hp->histfp)<0) 747 { 748 hist_close(hp); 749 if(!sh_histinit(hp->histshell)) 750 sh_offoption(SH_HISTORY); 751 } 752 hp->histflush = 0; 753 } 754 } 755 756 /* 757 * This is the write discipline for the history file 758 * When called from hist_flush(), trailing newlines are deleted and 759 * a zero byte. Line sequencing is added as required 760 */ 761 762 #ifdef SF_BUFCONST 763 static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle) 764 #else 765 static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle) 766 #endif 767 { 768 register History_t *hp = (History_t*)handle; 769 register char *bufptr = ((char*)buff)+insize; 770 register int c,size = insize; 771 register off_t cur; 772 int saved=0; 773 char saveptr[HIST_MARKSZ]; 774 if(!hp->histflush) 775 return(write(sffileno(iop),(char*)buff,size)); 776 if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0) 777 { 778 errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno); 779 return(-1); 780 } 781 hp->histcnt = cur; 782 /* remove whitespace from end of commands */ 783 while(--bufptr >= (char*)buff) 784 { 785 c= *bufptr; 786 if(!isspace(c)) 787 { 788 if(c=='\\' && *(bufptr+1)!='\n') 789 bufptr++; 790 break; 791 } 792 } 793 /* don't count empty lines */ 794 if(++bufptr <= (char*)buff) 795 return(insize); 796 *bufptr++ = '\n'; 797 *bufptr++ = 0; 798 size = bufptr - (char*)buff; 799 #if SHOPT_AUDIT 800 if(hp->auditfp) 801 { 802 Shell_t *shp = (Shell_t*)hp->histshell; 803 time_t t=time((time_t*)0); 804 sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shp->euserid:shp->userid,t,hp->tty,size,buff,0); 805 sfsync(hp->auditfp); 806 } 807 #endif /* SHOPT_AUDIT */ 808 #if SHOPT_ACCTFILE 809 if(acctfd) 810 { 811 int timechars, offset; 812 offset = staktell(); 813 stakputs(buff); 814 stakseek(staktell() - 1); 815 timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *))); 816 lseek(acctfd, (off_t)0, SEEK_END); 817 write(acctfd, stakptr(offset), size - 2 + timechars); 818 stakseek(offset); 819 820 } 821 #endif /* SHOPT_ACCTFILE */ 822 if(size&01) 823 { 824 size++; 825 *bufptr++ = 0; 826 } 827 hp->histcnt += size; 828 c = hist_ind(hp,++hp->histind); 829 hp->histcmds[c] = hp->histcnt; 830 if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2) 831 { 832 memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ); 833 saved=1; 834 hp->histcnt += HIST_MARKSZ; 835 hist_marker(bufptr,hp->histind); 836 hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt; 837 size += HIST_MARKSZ; 838 } 839 errno = 0; 840 size = write(sffileno(iop),(char*)buff,size); 841 if(saved) 842 memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ); 843 if(size>=0) 844 { 845 hp->histwfail = 0; 846 return(insize); 847 } 848 return(-1); 849 } 850 851 /* 852 * Put history sequence number <n> into buffer <buff> 853 * The buffer must be large enough to hold HIST_MARKSZ chars 854 */ 855 856 static void hist_marker(register char *buff,register long cmdno) 857 { 858 *buff++ = HIST_CMDNO; 859 *buff++ = 0; 860 *buff++ = (cmdno>>16); 861 *buff++ = (cmdno>>8); 862 *buff++ = cmdno; 863 *buff++ = 0; 864 } 865 866 /* 867 * return byte offset in history file for command <n> 868 */ 869 off_t hist_tell(register History_t *hp, int n) 870 { 871 return(hp->histcmds[hist_ind(hp,n)]); 872 } 873 874 /* 875 * seek to the position of command <n> 876 */ 877 off_t hist_seek(register History_t *hp, int n) 878 { 879 return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET)); 880 } 881 882 /* 883 * write the command starting at offset <offset> onto file <outfile>. 884 * if character <last> appears before newline it is deleted 885 * each new-line character is replaced with string <nl>. 886 */ 887 888 void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl) 889 { 890 register int oldc=0; 891 register int c; 892 if(offset<0 || !hp) 893 { 894 sfputr(outfile,sh_translate(e_unknown),'\n'); 895 return; 896 } 897 sfseek(hp->histfp,offset,SEEK_SET); 898 while((c = sfgetc(hp->histfp)) != EOF) 899 { 900 if(c && oldc=='\n') 901 sfputr(outfile,nl,-1); 902 else if(last && (c==0 || (c=='\n' && oldc==last))) 903 return; 904 else if(oldc) 905 sfputc(outfile,oldc); 906 oldc = c; 907 if(c==0) 908 return; 909 } 910 return; 911 } 912 913 /* 914 * find index for last line with given string 915 * If flag==0 then line must begin with string 916 * direction < 1 for backwards search 917 */ 918 919 Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction) 920 { 921 register int index2; 922 off_t offset; 923 int *coffset=0; 924 Histloc_t location; 925 location.hist_command = -1; 926 location.hist_char = 0; 927 location.hist_line = 0; 928 if(!hp) 929 return(location); 930 /* leading ^ means beginning of line unless escaped */ 931 if(flag) 932 { 933 index2 = *string; 934 if(index2=='\\') 935 string++; 936 else if(index2=='^') 937 { 938 flag=0; 939 string++; 940 } 941 } 942 if(flag) 943 coffset = &location.hist_char; 944 index2 = (int)hp->histind; 945 if(direction<0) 946 { 947 index2 -= hp->histsize; 948 if(index2<1) 949 index2 = 1; 950 if(index1 <= index2) 951 return(location); 952 } 953 else if(index1 >= index2) 954 return(location); 955 while(index1!=index2) 956 { 957 direction>0?++index1:--index1; 958 offset = hist_tell(hp,index1); 959 if((location.hist_line=hist_match(hp,offset,string,coffset))>=0) 960 { 961 location.hist_command = index1; 962 return(location); 963 } 964 #if KSHELL 965 /* allow a search to be aborted */ 966 if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET) 967 break; 968 #endif /* KSHELL */ 969 } 970 return(location); 971 } 972 973 /* 974 * search for <string> in history file starting at location <offset> 975 * If coffset==0 then line must begin with string 976 * returns the line number of the match if successful, otherwise -1 977 */ 978 979 int hist_match(register History_t *hp,off_t offset,char *string,int *coffset) 980 { 981 register unsigned char *first, *cp; 982 register int m,n,c=1,line=0; 983 #if SHOPT_MULTIBYTE 984 mbinit(); 985 #endif /* SHOPT_MULTIBYTE */ 986 sfseek(hp->histfp,offset,SEEK_SET); 987 if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0))) 988 return(-1); 989 m = sfvalue(hp->histfp); 990 n = strlen(string); 991 while(m > n) 992 { 993 if(*cp==*string && memcmp(cp,string,n)==0) 994 { 995 if(coffset) 996 *coffset = (cp-first); 997 return(line); 998 } 999 if(!coffset) 1000 break; 1001 if(*cp=='\n') 1002 line++; 1003 #if SHOPT_MULTIBYTE 1004 if((c=mbsize(cp)) < 0) 1005 c = 1; 1006 #endif /* SHOPT_MULTIBYTE */ 1007 cp += c; 1008 m -= c; 1009 } 1010 return(-1); 1011 } 1012 1013 1014 #if SHOPT_ESH || SHOPT_VSH 1015 /* 1016 * copy command <command> from history file to s1 1017 * at most <size> characters copied 1018 * if s1==0 the number of lines for the command is returned 1019 * line=linenumber for emacs copy and only this line of command will be copied 1020 * line < 0 for full command copy 1021 * -1 returned if there is no history file 1022 */ 1023 1024 int hist_copy(char *s1,int size,int command,int line) 1025 { 1026 register int c; 1027 register History_t *hp = sh_getinterp()->hist_ptr; 1028 register int count = 0; 1029 register char *s1max = s1+size; 1030 if(!hp) 1031 return(-1); 1032 hist_seek(hp,command); 1033 while ((c = sfgetc(hp->histfp)) && c!=EOF) 1034 { 1035 if(c=='\n') 1036 { 1037 if(count++ ==line) 1038 break; 1039 else if(line >= 0) 1040 continue; 1041 } 1042 if(s1 && (line<0 || line==count)) 1043 { 1044 if(s1 >= s1max) 1045 { 1046 *--s1 = 0; 1047 break; 1048 } 1049 *s1++ = c; 1050 } 1051 1052 } 1053 sfseek(hp->histfp,(off_t)0,SEEK_END); 1054 if(s1==0) 1055 return(count); 1056 if(count && (c= *(s1-1)) == '\n') 1057 s1--; 1058 *s1 = '\0'; 1059 return(count); 1060 } 1061 1062 /* 1063 * return word number <word> from command number <command> 1064 */ 1065 1066 char *hist_word(char *string,int size,int word) 1067 { 1068 register int c; 1069 register char *s1 = string; 1070 register unsigned char *cp = (unsigned char*)s1; 1071 register int flag = 0; 1072 History_t *hp = hist_ptr; 1073 if(!hp) 1074 #if KSHELL 1075 { 1076 strncpy(string,((Shell_t*)hp->histshell)->lastarg,size); 1077 return(string); 1078 } 1079 #else 1080 return(NIL(char*)); 1081 #endif /* KSHELL */ 1082 hist_copy(string,size,(int)hp->histind-1,-1); 1083 for(;c = *cp;cp++) 1084 { 1085 c = isspace(c); 1086 if(c && flag) 1087 { 1088 *cp = 0; 1089 if(--word==0) 1090 break; 1091 flag = 0; 1092 } 1093 else if(c==0 && flag==0) 1094 { 1095 s1 = (char*)cp; 1096 flag++; 1097 } 1098 } 1099 *cp = 0; 1100 if(s1 != string) 1101 strcpy(string,s1); 1102 return(string); 1103 } 1104 1105 #endif /* SHOPT_ESH */ 1106 1107 #if SHOPT_ESH 1108 /* 1109 * given the current command and line number, 1110 * and number of lines back or foward, 1111 * compute the new command and line number. 1112 */ 1113 1114 Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines) 1115 { 1116 Histloc_t next; 1117 line += lines; 1118 if(!hp) 1119 { 1120 command = -1; 1121 goto done; 1122 } 1123 if(lines > 0) 1124 { 1125 register int count; 1126 while(command <= hp->histind) 1127 { 1128 count = hist_copy(NIL(char*),0, command,-1); 1129 if(count > line) 1130 goto done; 1131 line -= count; 1132 command++; 1133 } 1134 } 1135 else 1136 { 1137 register int least = (int)hp->histind-hp->histsize; 1138 while(1) 1139 { 1140 if(line >=0) 1141 goto done; 1142 if(--command < least) 1143 break; 1144 line += hist_copy(NIL(char*),0, command,-1); 1145 } 1146 command = -1; 1147 } 1148 done: 1149 next.hist_line = line; 1150 next.hist_command = command; 1151 return(next); 1152 } 1153 #endif /* SHOPT_ESH */ 1154 1155 1156 /* 1157 * Handle history file exceptions 1158 */ 1159 #ifdef SF_BUFCONST 1160 static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle) 1161 #else 1162 static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle) 1163 #endif 1164 { 1165 register int newfd,oldfd; 1166 History_t *hp = (History_t*)handle; 1167 if(type==SF_WRITE) 1168 { 1169 if(errno==ENOSPC || hp->histwfail++ >= 10) 1170 return(0); 1171 /* write failure could be NFS problem, try to re-open */ 1172 close(oldfd=sffileno(fp)); 1173 if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0) 1174 { 1175 if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd) 1176 return(-1); 1177 fcntl(oldfd,F_SETFD,FD_CLOEXEC); 1178 close(newfd); 1179 if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt) 1180 { 1181 register int index = hp->histind; 1182 lseek(oldfd,(off_t)2,SEEK_SET); 1183 hp->histcnt = 2; 1184 hp->histind = 1; 1185 hp->histcmds[1] = 2; 1186 hist_eof(hp); 1187 hp->histmarker = hp->histcnt; 1188 hp->histind = index; 1189 } 1190 return(1); 1191 } 1192 errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname); 1193 return(-1); 1194 } 1195 return(0); 1196 } 1197