/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 1982-2010 AT&T Intellectual Property * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Intellectual Property * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * David Korn * * * ***********************************************************************/ #pragma prototyped /* * History file manipulation routines * * David Korn * AT&T Labs * */ /* * Each command in the history file starts on an even byte is null terminated. * The first byte must contain the special character HIST_UNDO and the second * byte is the version number. The sequence HIST_UNDO 0, following a command, * nullifies the previous command. A six byte sequence starting with * HIST_CMDNO is used to store the command number so that it is not necessary * to read the file from beginning to end to get to the last block of * commands. This format of this sequence is different in version 1 * then in version 0. Version 1 allows commands to use the full 8 bit * character set. It can understand version 0 format files. */ #define HIST_MAX (sizeof(int)*HIST_BSIZE) #define HIST_BIG (0100000-1024) /* 1K less than maximum short */ #define HIST_LINE 32 /* typical length for history line */ #define HIST_MARKSZ 6 #define HIST_RECENT 600 #define HIST_UNDO 0201 /* invalidate previous command */ #define HIST_CMDNO 0202 /* next 3 bytes give command number */ #define HIST_BSIZE 4096 /* size of history file buffer */ #define HIST_DFLT 512 /* default size of history list */ #if SHOPT_AUDIT # define _HIST_AUDIT Sfio_t *auditfp; \ char *tty; \ int auditmask; #else # define _HIST_AUDIT #endif #define _HIST_PRIVATE \ void *histshell; \ off_t histcnt; /* offset into history file */\ off_t histmarker; /* offset of last command marker */ \ int histflush; /* set if flushed outside of hflush() */\ int histmask; /* power of two mask for histcnt */ \ char histbuff[HIST_BSIZE+1]; /* history file buffer */ \ int histwfail; \ _HIST_AUDIT \ off_t histcmds[2]; /* offset for recent commands, must be last */ #define hist_ind(hp,c) ((int)((c)&(hp)->histmask)) #include #include #include "FEATURE/time" #include #include #if KSHELL # include "defs.h" # include "variables.h" # include "path.h" # include "builtins.h" # include "io.h" #else # include #endif /* KSHELL */ #include "history.h" #if !KSHELL # define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x))) # define NIL(type) ((type)0) # define path_relative(x) (x) # ifdef __STDC__ # define nv_getval(s) getenv(#s) # else # define nv_getval(s) getenv("s") # endif /* __STDC__ */ # define e_unknown "unknown" # define sh_translate(x) (x) char login_sh = 0; char hist_fname[] = "/.history"; #endif /* KSHELL */ #ifndef O_BINARY # define O_BINARY 0 #endif /* O_BINARY */ int _Hist = 0; static void hist_marker(char*,long); static History_t* hist_trim(History_t*, int); static int hist_nearend(History_t*,Sfio_t*, off_t); static int hist_check(int); static int hist_clean(int); #ifdef SF_BUFCONST static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*); static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*); #else static int hist_write(Sfio_t*, const void*, int, Sfdisc_t*); static int hist_exceptf(Sfio_t*, int, Sfdisc_t*); #endif static int histinit; static mode_t histmode; static History_t *wasopen; static History_t *hist_ptr; #if SHOPT_ACCTFILE static int acctfd; static char *logname; # include static int acctinit(History_t *hp) { register char *cp, *acctfile; Namval_t *np = nv_search("ACCTFILE",((Shell_t*)hp->histshell)->var_tree,0); if(!np || !(acctfile=nv_getval(np))) return(0); if(!(cp = getlogin())) { struct passwd *userinfo = getpwuid(getuid()); if(userinfo) cp = userinfo->pw_name; else cp = "unknown"; } logname = strdup(cp); if((acctfd=sh_open(acctfile, O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && (unsigned)acctfd < 10) { int n; if((n = fcntl(acctfd, F_DUPFD, 10)) >= 0) { close(acctfd); acctfd = n; } } if(acctfd < 0) { acctfd = 0; return(0); } if(strmatch(acctfile,e_devfdNN)) { char newfile[16]; sfsprintf(newfile,sizeof(newfile),"%.8s%d\0",e_devfdNN,acctfd); nv_putval(np,newfile,NV_RDONLY); } else fcntl(acctfd,F_SETFD,FD_CLOEXEC); return(1); } #endif /* SHOPT_ACCTFILE */ #if SHOPT_AUDIT static int sh_checkaudit(History_t *hp, const char *name, char *logbuf, size_t len) { Shell_t *shp = (Shell_t*)hp->histshell; char *buff, *cp, *last; int id1, id2, r=0, n, fd; if((fd=open(name, O_RDONLY)) < 0) return(0); if((n = read(fd, logbuf,len-1)) < 0) goto done; while(logbuf[n-1]=='\n') n--; logbuf[n] = 0; if(!(cp=strchr(logbuf,';')) && !(cp=strchr(logbuf,' '))) goto done; *cp = 0; do { cp++; id1 = id2 = strtol(cp,&last,10); if(*last=='-') id1 = strtol(last+1,&last,10); if(shp->euserid >=id1 && shp->euserid <= id2) r |= 1; if(shp->userid >=id1 && shp->userid <= id2) r |= 2; cp = last; } while(*cp==';' || *cp==' '); done: close(fd); return(r); } #endif /*SHOPT_AUDIT*/ static const unsigned char hist_stamp[2] = { HIST_UNDO, HIST_VERSION }; static const Sfdisc_t hist_disc = { NULL, hist_write, NULL, hist_exceptf, NULL}; static void hist_touch(void *handle) { touch((char*)handle, (time_t)0, (time_t)0, 0); } /* * open the history file * if HISTNAME is not given and userid==0 then no history file. * if login_sh and HISTFILE is longer than HIST_MAX bytes then it is * cleaned up. * hist_open() returns 1, if history file is open */ int sh_histinit(void *sh_context) { Shell_t *shp = (Shell_t*)sh_context; register int fd; register History_t *hp; register char *histname; char *fname=0; int histmask, maxlines, hist_start=0; register char *cp; register off_t hsize = 0; if(shp->hist_ptr=hist_ptr) return(1); if(!(histname = nv_getval(HISTFILE))) { int offset = staktell(); if(cp=nv_getval(HOME)) stakputs(cp); stakputs(hist_fname); stakputc(0); stakseek(offset); histname = stakptr(offset); } #ifdef future if(hp=wasopen) { /* reuse history file if same name */ wasopen = 0; shp->hist_ptr = hist_ptr = hp; if(strcmp(histname,hp->histname)==0) return(1); else hist_free(); } #endif retry: cp = path_relative(histname); if(!histinit) histmode = S_IRUSR|S_IWUSR; if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT,histmode))>=0) { hsize=lseek(fd,(off_t)0,SEEK_END); } if((unsigned)fd <=2) { int n; if((n=fcntl(fd,F_DUPFD,10))>=0) { close(fd); fd=n; } } /* make sure that file has history file format */ if(hsize && hist_check(fd)) { close(fd); hsize = 0; if(unlink(cp)>=0) goto retry; fd = -1; } if(fd < 0) { #if KSHELL /* don't allow root a history_file in /tmp */ if(shp->userid) #endif /* KSHELL */ { if(!(fname = pathtmp(NIL(char*),0,0,NIL(int*)))) return(0); fd = open(fname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR); } } if(fd<0) return(0); /* set the file to close-on-exec */ fcntl(fd,F_SETFD,FD_CLOEXEC); if(cp=nv_getval(HISTSIZE)) maxlines = (unsigned)strtol(cp, (char**)0, 10); else maxlines = HIST_DFLT; for(histmask=16;histmask <= maxlines; histmask <<=1 ); if(!(hp=new_of(History_t,(--histmask)*sizeof(off_t)))) { close(fd); return(0); } shp->hist_ptr = hist_ptr = hp; hp->histshell = (void*)shp; hp->histsize = maxlines; hp->histmask = histmask; hp->histfp= sfnew(NIL(Sfio_t*),hp->histbuff,HIST_BSIZE,fd,SF_READ|SF_WRITE|SF_APPENDWR|SF_SHARE); memset((char*)hp->histcmds,0,sizeof(off_t)*(hp->histmask+1)); hp->histind = 1; hp->histcmds[1] = 2; hp->histcnt = 2; hp->histname = strdup(histname); hp->histdisc = hist_disc; if(hsize==0) { /* put special characters at front of file */ sfwrite(hp->histfp,(char*)hist_stamp,2); sfsync(hp->histfp); } /* initialize history list */ else { int first,last; off_t mark,size = (HIST_MAX/4)+maxlines*HIST_LINE; hp->histind = first = hist_nearend(hp,hp->histfp,hsize-size); hist_eof(hp); /* this sets histind to last command */ if((hist_start = (last=(int)hp->histind)-maxlines) <=0) hist_start = 1; mark = hp->histmarker; while(first > hist_start) { size += size; first = hist_nearend(hp,hp->histfp,hsize-size); hp->histind = first; } histinit = hist_start; hist_eof(hp); if(!histinit) { sfseek(hp->histfp,hp->histcnt=hsize,SEEK_SET); hp->histind = last; hp->histmarker = mark; } histinit = 0; } if(fname) { unlink(fname); free((void*)fname); } if(hist_clean(fd) && hist_start>1 && hsize > HIST_MAX) { #ifdef DEBUG sfprintf(sfstderr,"%d: hist_trim hsize=%d\n",getpid(),hsize); sfsync(sfstderr); #endif /* DEBUG */ hp = hist_trim(hp,(int)hp->histind-maxlines); } sfdisc(hp->histfp,&hp->histdisc); #if KSHELL (HISTCUR)->nvalue.lp = (&hp->histind); #endif /* KSHELL */ sh_timeradd(1000L*(HIST_RECENT-30), 1, hist_touch, (void*)hp->histname); #if SHOPT_ACCTFILE if(sh_isstate(SH_INTERACTIVE)) acctinit(hp); #endif /* SHOPT_ACCTFILE */ #if SHOPT_AUDIT { char buff[SF_BUFSIZE]; hp->auditfp = 0; if(sh_isstate(SH_INTERACTIVE) && (hp->auditmask=sh_checkaudit(hp,SHOPT_AUDITFILE, buff, sizeof(buff)))) { if((fd=sh_open(buff,O_BINARY|O_WRONLY|O_APPEND|O_CREAT,S_IRUSR|S_IWUSR))>=0 && fd < 10) { int n; if((n = sh_fcntl(fd,F_DUPFD, 10)) >= 0) { sh_close(fd); fd = n; } } if(fd>=0) { hp->tty = strdup(ttyname(2)); hp->auditfp = sfnew((Sfio_t*)0,NULL,-1,fd,SF_WRITE); } } } #endif return(1); } /* * close the history file and free the space */ void hist_close(register History_t *hp) { Shell_t *shp = (Shell_t*)hp->histshell; sfclose(hp->histfp); #if SHOPT_AUDIT if(hp->auditfp) { if(hp->tty) free((void*)hp->tty); sfclose(hp->auditfp); } #endif /* SHOPT_AUDIT */ free((char*)hp); hist_ptr = 0; shp->hist_ptr = 0; #if SHOPT_ACCTFILE if(acctfd) { close(acctfd); acctfd = 0; } #endif /* SHOPT_ACCTFILE */ } /* * check history file format to see if it begins with special byte */ static int hist_check(register int fd) { unsigned char magic[2]; lseek(fd,(off_t)0,SEEK_SET); if((read(fd,(char*)magic,2)!=2) || (magic[0]!=HIST_UNDO)) return(1); return(0); } /* * clean out history file OK if not modified in HIST_RECENT seconds */ static int hist_clean(int fd) { struct stat statb; return(fstat(fd,&statb)>=0 && (time((time_t*)0)-statb.st_mtime) >= HIST_RECENT); } /* * Copy the last commands to a new file and make this the history file */ static History_t* hist_trim(History_t *hp, int n) { register char *cp; register int incmd=1, c=0; register History_t *hist_new, *hist_old = hp; char *buff, *endbuff, *tmpname=0; off_t oldp,newp; struct stat statb; unlink(hist_old->histname); if(access(hist_old->histname,F_OK) >= 0) { /* The unlink can fail on windows 95 */ int fd; char *last, *name=hist_old->histname; close(sffileno(hist_old->histfp)); tmpname = (char*)malloc(strlen(name)+14); if(last = strrchr(name,'/')) { *last = 0; pathtmp(tmpname,name,"hist",NIL(int*)); *last = '/'; } else pathtmp(tmpname,".","hist",NIL(int*)); if(rename(name,tmpname) < 0) tmpname = name; fd = open(tmpname,O_RDONLY); sfsetfd(hist_old->histfp,fd); if(tmpname==name) tmpname = 0; } hist_ptr = 0; if(fstat(sffileno(hist_old->histfp),&statb)>=0) { histinit = 1; histmode = statb.st_mode; } if(!sh_histinit(hp->histshell)) { /* use the old history file */ return hist_ptr = hist_old; } hist_new = hist_ptr; hist_ptr = hist_old; if(--n < 0) n = 0; newp = hist_seek(hist_old,++n); while(1) { if(!incmd) { c = hist_ind(hist_new,++hist_new->histind); hist_new->histcmds[c] = hist_new->histcnt; if(hist_new->histcnt > hist_new->histmarker+HIST_BSIZE/2) { char locbuff[HIST_MARKSZ]; hist_marker(locbuff,hist_new->histind); sfwrite(hist_new->histfp,locbuff,HIST_MARKSZ); hist_new->histcnt += HIST_MARKSZ; hist_new->histmarker = hist_new->histcmds[hist_ind(hist_new,c)] = hist_new->histcnt; } oldp = newp; newp = hist_seek(hist_old,++n); if(newp <=oldp) break; } if(!(buff=(char*)sfreserve(hist_old->histfp,SF_UNBOUND,0))) break; *(endbuff=(cp=buff)+sfvalue(hist_old->histfp)) = 0; /* copy to null byte */ incmd = 0; while(*cp++); if(cp > endbuff) incmd = 1; else if(*cp==0) cp++; if(cp > endbuff) cp = endbuff; c = cp-buff; hist_new->histcnt += c; sfwrite(hist_new->histfp,buff,c); } hist_cancel(hist_new); sfclose(hist_old->histfp); if(tmpname) { unlink(tmpname); free(tmpname); } free((char*)hist_old); return hist_ptr = hist_new; } /* * position history file at size and find next command number */ static int hist_nearend(History_t *hp, Sfio_t *iop, register off_t size) { register unsigned char *cp, *endbuff; register int n, incmd=1; unsigned char *buff, marker[4]; if(size <= 2L || sfseek(iop,size,SEEK_SET)<0) goto begin; /* skip to marker command and return the number */ /* numbering commands occur after a null and begin with HIST_CMDNO */ while(cp=buff=(unsigned char*)sfreserve(iop,SF_UNBOUND,SF_LOCKR)) { n = sfvalue(iop); *(endbuff=cp+n) = 0; while(1) { /* check for marker */ if(!incmd && *cp++==HIST_CMDNO && *cp==0) { n = cp+1 - buff; incmd = -1; break; } incmd = 0; while(*cp++); if(cp>endbuff) { incmd = 1; break; } if(*cp==0 && ++cp>endbuff) break; } size += n; sfread(iop,(char*)buff,n); if(incmd < 0) { if((n=sfread(iop,(char*)marker,4))==4) { n = (marker[0]<<16)|(marker[1]<<8)|marker[2]; if(n < size/2) { hp->histmarker = hp->histcnt = size+4; return(n); } n=4; } if(n >0) size += n; incmd = 0; } } begin: sfseek(iop,(off_t)2,SEEK_SET); hp->histmarker = hp->histcnt = 2L; return(1); } /* * This routine reads the history file from the present position * to the end-of-file and puts the information in the in-core * history table * Note that HIST_CMDNO is only recognized at the beginning of a command * and that HIST_UNDO as the first character of a command is skipped * unless it is followed by 0. If followed by 0 then it cancels * the previous command. */ void hist_eof(register History_t *hp) { register char *cp,*first,*endbuff; register int incmd = 0; register off_t count = hp->histcnt; int n,skip=0; sfseek(hp->histfp,count,SEEK_SET); while(cp=(char*)sfreserve(hp->histfp,SF_UNBOUND,0)) { n = sfvalue(hp->histfp); *(endbuff = cp+n) = 0; first = cp += skip; while(1) { while(!incmd) { if(cp>first) { count += (cp-first); n = hist_ind(hp, ++hp->histind); #ifdef future if(count==hp->histcmds[n]) { sfprintf(sfstderr,"count match n=%d\n",n); if(histinit) { histinit = 0; return; } } else if(n>=histinit) #endif hp->histcmds[n] = count; first = cp; } switch(*((unsigned char*)(cp++))) { case HIST_CMDNO: if(*cp==0) { hp->histmarker=count+2; cp += (HIST_MARKSZ-1); hp->histind--; #ifdef future if(cp <= endbuff) { unsigned char *marker = (unsigned char*)(cp-4); int n = ((marker[0]<<16) |(marker[1]<<8)|marker[2]); if((nhistind+1)) errormsg(SH_DICT,2,"index=%d marker=%d", hp->histind, n); } #endif } break; case HIST_UNDO: if(*cp==0) { cp+=1; hp->histind-=2; } break; default: cp--; incmd = 1; } if(cp > endbuff) { cp++; goto refill; } } first = cp; while(*cp++); if(cp > endbuff) break; incmd = 0; while(*cp==0) { if(++cp > endbuff) goto refill; } } refill: count += (--cp-first); skip = (cp-endbuff); if(!incmd && !skip) hp->histcmds[hist_ind(hp,++hp->histind)] = count; } hp->histcnt = count; } /* * This routine will cause the previous command to be cancelled */ void hist_cancel(register History_t *hp) { register int c; if(!hp) return; sfputc(hp->histfp,HIST_UNDO); sfputc(hp->histfp,0); sfsync(hp->histfp); hp->histcnt += 2; c = hist_ind(hp,--hp->histind); hp->histcmds[c] = hp->histcnt; } /* * flush the current history command */ void hist_flush(register History_t *hp) { register char *buff; if(hp) { if(buff=(char*)sfreserve(hp->histfp,0,SF_LOCKR)) { hp->histflush = sfvalue(hp->histfp)+1; sfwrite(hp->histfp,buff,0); } else hp->histflush=0; if(sfsync(hp->histfp)<0) { hist_close(hp); if(!sh_histinit(hp->histshell)) sh_offoption(SH_HISTORY); } hp->histflush = 0; } } /* * This is the write discipline for the history file * When called from hist_flush(), trailing newlines are deleted and * a zero byte. Line sequencing is added as required */ #ifdef SF_BUFCONST static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle) #else static int hist_write(Sfio_t *iop,const void *buff,register int insize,Sfdisc_t* handle) #endif { register History_t *hp = (History_t*)handle; register char *bufptr = ((char*)buff)+insize; register int c,size = insize; register off_t cur; int saved=0; char saveptr[HIST_MARKSZ]; if(!hp->histflush) return(write(sffileno(iop),(char*)buff,size)); if((cur = lseek(sffileno(iop),(off_t)0,SEEK_END)) <0) { errormsg(SH_DICT,2,"hist_flush: EOF seek failed errno=%d",errno); return(-1); } hp->histcnt = cur; /* remove whitespace from end of commands */ while(--bufptr >= (char*)buff) { c= *bufptr; if(!isspace(c)) { if(c=='\\' && *(bufptr+1)!='\n') bufptr++; break; } } /* don't count empty lines */ if(++bufptr <= (char*)buff) return(insize); *bufptr++ = '\n'; *bufptr++ = 0; size = bufptr - (char*)buff; #if SHOPT_AUDIT if(hp->auditfp) { Shell_t *shp = (Shell_t*)hp->histshell; time_t t=time((time_t*)0); sfprintf(hp->auditfp,"%u;%u;%s;%*s%c",sh_isoption(SH_PRIVILEGED)?shp->euserid:shp->userid,t,hp->tty,size,buff,0); sfsync(hp->auditfp); } #endif /* SHOPT_AUDIT */ #if SHOPT_ACCTFILE if(acctfd) { int timechars, offset; offset = staktell(); stakputs(buff); stakseek(staktell() - 1); timechars = sfprintf(staksp, "\t%s\t%x\n",logname,time(NIL(long *))); lseek(acctfd, (off_t)0, SEEK_END); write(acctfd, stakptr(offset), size - 2 + timechars); stakseek(offset); } #endif /* SHOPT_ACCTFILE */ if(size&01) { size++; *bufptr++ = 0; } hp->histcnt += size; c = hist_ind(hp,++hp->histind); hp->histcmds[c] = hp->histcnt; if(hp->histflush>HIST_MARKSZ && hp->histcnt > hp->histmarker+HIST_BSIZE/2) { memcpy((void*)saveptr,(void*)bufptr,HIST_MARKSZ); saved=1; hp->histcnt += HIST_MARKSZ; hist_marker(bufptr,hp->histind); hp->histmarker = hp->histcmds[hist_ind(hp,c)] = hp->histcnt; size += HIST_MARKSZ; } errno = 0; size = write(sffileno(iop),(char*)buff,size); if(saved) memcpy((void*)bufptr,(void*)saveptr,HIST_MARKSZ); if(size>=0) { hp->histwfail = 0; return(insize); } return(-1); } /* * Put history sequence number into buffer * The buffer must be large enough to hold HIST_MARKSZ chars */ static void hist_marker(register char *buff,register long cmdno) { *buff++ = HIST_CMDNO; *buff++ = 0; *buff++ = (cmdno>>16); *buff++ = (cmdno>>8); *buff++ = cmdno; *buff++ = 0; } /* * return byte offset in history file for command */ off_t hist_tell(register History_t *hp, int n) { return(hp->histcmds[hist_ind(hp,n)]); } /* * seek to the position of command */ off_t hist_seek(register History_t *hp, int n) { return(sfseek(hp->histfp,hp->histcmds[hist_ind(hp,n)],SEEK_SET)); } /* * write the command starting at offset onto file . * if character appears before newline it is deleted * each new-line character is replaced with string . */ void hist_list(register History_t *hp,Sfio_t *outfile, off_t offset,int last, char *nl) { register int oldc=0; register int c; if(offset<0 || !hp) { sfputr(outfile,sh_translate(e_unknown),'\n'); return; } sfseek(hp->histfp,offset,SEEK_SET); while((c = sfgetc(hp->histfp)) != EOF) { if(c && oldc=='\n') sfputr(outfile,nl,-1); else if(last && (c==0 || (c=='\n' && oldc==last))) return; else if(oldc) sfputc(outfile,oldc); oldc = c; if(c==0) return; } return; } /* * find index for last line with given string * If flag==0 then line must begin with string * direction < 1 for backwards search */ Histloc_t hist_find(register History_t*hp,char *string,register int index1,int flag,int direction) { register int index2; off_t offset; int *coffset=0; Histloc_t location; location.hist_command = -1; location.hist_char = 0; location.hist_line = 0; if(!hp) return(location); /* leading ^ means beginning of line unless escaped */ if(flag) { index2 = *string; if(index2=='\\') string++; else if(index2=='^') { flag=0; string++; } } if(flag) coffset = &location.hist_char; index2 = (int)hp->histind; if(direction<0) { index2 -= hp->histsize; if(index2<1) index2 = 1; if(index1 <= index2) return(location); } else if(index1 >= index2) return(location); while(index1!=index2) { direction>0?++index1:--index1; offset = hist_tell(hp,index1); if((location.hist_line=hist_match(hp,offset,string,coffset))>=0) { location.hist_command = index1; return(location); } #if KSHELL /* allow a search to be aborted */ if(((Shell_t*)hp->histshell)->trapnote&SH_SIGSET) break; #endif /* KSHELL */ } return(location); } /* * search for in history file starting at location * If coffset==0 then line must begin with string * returns the line number of the match if successful, otherwise -1 */ int hist_match(register History_t *hp,off_t offset,char *string,int *coffset) { register unsigned char *first, *cp; register int m,n,c=1,line=0; #if SHOPT_MULTIBYTE mbinit(); #endif /* SHOPT_MULTIBYTE */ sfseek(hp->histfp,offset,SEEK_SET); if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0))) return(-1); m = sfvalue(hp->histfp); n = strlen(string); while(m > n) { if(*cp==*string && memcmp(cp,string,n)==0) { if(coffset) *coffset = (cp-first); return(line); } if(!coffset) break; if(*cp=='\n') line++; #if SHOPT_MULTIBYTE if((c=mbsize(cp)) < 0) c = 1; #endif /* SHOPT_MULTIBYTE */ cp += c; m -= c; } return(-1); } #if SHOPT_ESH || SHOPT_VSH /* * copy command from history file to s1 * at most characters copied * if s1==0 the number of lines for the command is returned * line=linenumber for emacs copy and only this line of command will be copied * line < 0 for full command copy * -1 returned if there is no history file */ int hist_copy(char *s1,int size,int command,int line) { register int c; register History_t *hp = sh_getinterp()->hist_ptr; register int count = 0; register char *s1max = s1+size; if(!hp) return(-1); hist_seek(hp,command); while ((c = sfgetc(hp->histfp)) && c!=EOF) { if(c=='\n') { if(count++ ==line) break; else if(line >= 0) continue; } if(s1 && (line<0 || line==count)) { if(s1 >= s1max) { *--s1 = 0; break; } *s1++ = c; } } sfseek(hp->histfp,(off_t)0,SEEK_END); if(s1==0) return(count); if(count && (c= *(s1-1)) == '\n') s1--; *s1 = '\0'; return(count); } /* * return word number from command number */ char *hist_word(char *string,int size,int word) { register int c; register char *s1 = string; register unsigned char *cp = (unsigned char*)s1; register int flag = 0; History_t *hp = hist_ptr; if(!hp) return(NIL(char*)); hist_copy(string,size,(int)hp->histind-1,-1); for(;c = *cp;cp++) { c = isspace(c); if(c && flag) { *cp = 0; if(--word==0) break; flag = 0; } else if(c==0 && flag==0) { s1 = (char*)cp; flag++; } } *cp = 0; if(s1 != string) strcpy(string,s1); return(string); } #endif /* SHOPT_ESH */ #if SHOPT_ESH /* * given the current command and line number, * and number of lines back or foward, * compute the new command and line number. */ Histloc_t hist_locate(History_t *hp,register int command,register int line,int lines) { Histloc_t next; line += lines; if(!hp) { command = -1; goto done; } if(lines > 0) { register int count; while(command <= hp->histind) { count = hist_copy(NIL(char*),0, command,-1); if(count > line) goto done; line -= count; command++; } } else { register int least = (int)hp->histind-hp->histsize; while(1) { if(line >=0) goto done; if(--command < least) break; line += hist_copy(NIL(char*),0, command,-1); } command = -1; } done: next.hist_line = line; next.hist_command = command; return(next); } #endif /* SHOPT_ESH */ /* * Handle history file exceptions */ #ifdef SF_BUFCONST static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle) #else static int hist_exceptf(Sfio_t* fp, int type, Sfdisc_t *handle) #endif { register int newfd,oldfd; History_t *hp = (History_t*)handle; if(type==SF_WRITE) { if(errno==ENOSPC || hp->histwfail++ >= 10) return(0); /* write failure could be NFS problem, try to re-open */ close(oldfd=sffileno(fp)); if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR,S_IRUSR|S_IWUSR)) >= 0) { if(fcntl(newfd, F_DUPFD, oldfd) !=oldfd) return(-1); fcntl(oldfd,F_SETFD,FD_CLOEXEC); close(newfd); if(lseek(oldfd,(off_t)0,SEEK_END) < hp->histcnt) { register int index = hp->histind; lseek(oldfd,(off_t)2,SEEK_SET); hp->histcnt = 2; hp->histind = 1; hp->histcmds[1] = 2; hist_eof(hp); hp->histmarker = hp->histcnt; hp->histind = index; } return(1); } errormsg(SH_DICT,2,"History file write error-%d %s: file unrecoverable",errno,hp->histname); return(-1); } return(0); }