#ifdef KSHELL
# include "defs.h"
# include "builtins.h"
#else
# include "io.h"
# include "edit.h"
# include <signal.h>
# include <ctype.h>
#endif
#include "history.h"
#ifdef MULTIBYTE
# include "national.h"
#endif
#include <tm.h>
#ifndef KSHELL
# define sh_heap(s) strcpy(malloc(strlen(s)+1),(s))
# define sh_arith(str) (int)strtol(str,(char**)0,10)
# define new_of(type,x) ((type*)malloc((unsigned)sizeof(type)+(x)))
# define e_unknown "unknown"
# define e_create "cannot create"
# define path_relative(x) (x)
static int io_readbuff();
static off_t io_seek();
static void io_fclose();
static int io_getc();
static void io_init();
static int io_renumber();
static int io_mktmp();
const char hist_fname[] = "/.history";
struct history *hist_ptr = 0;
#endif
static int fixmask;
static int noclean;
static void hist_trim();
static int hist_nearend();
static int hist_check();
static int hist_clean();
static void hist_free();
static int hist_version;
static struct history *wasopen;
static off_t hist_marker;
static char htrim;
static const unsigned char hist_stamp[2] = { H_UNDO, H_VERSION };
#define HIST_RECENT 600
int hist_open()
{
register int fd;
register struct history *fp;
register char *histname;
char fname[TMPSIZ];
char hname[256];
int maxlines;
register char *cp;
register off_t hsize = 0;
int his_start;
if(hist_ptr)
return(1);
histname = nam_strval(HISTFILE);
if(!histname)
{
if(cp=nam_strval(HOME))
cp = sh_copy(cp,hname);
else
cp = hname;
sh_copy(hist_fname,cp);
histname = hname;
}
*fname = 0;
if(fp=wasopen)
{
wasopen = 0;
hist_ptr = fp;
if(strcmp(histname,fp->fixname)==0)
return(1);
else
hist_free();
}
retry:
histname = path_relative(histname);
if((fd=open(histname,O_APPEND|O_RDWR|O_CREAT|O_BINARY,S_IRUSR|S_IWUSR))>=0)
{
hsize=io_seek(fd,(off_t)0,SEEK_END);
}
if(hsize && hist_check(fd))
{
io_fclose(fd);
hsize = 0;
if(unlink(histname)>=0)
goto retry;
fd = -1;
}
if(fd < 0)
{
#ifdef KSHELL
if(sh.userid)
#endif
fd = io_mktmp(fname, sizeof(fname));
}
if(fd<0)
return(0);
fd = io_renumber(fd,FCIO);
if(cp=nam_strval(HISTSIZE))
maxlines = (unsigned)sh_arith(cp);
else
maxlines = HIS_DFLT;
for(fixmask=16;fixmask <= maxlines; fixmask <<=1 );
if(!(fp=new_of(struct history,(--fixmask)*sizeof(off_t))))
{
io_fclose(fd);
return(0);
}
hist_ptr = fp;
fp->fixfd = fd;
fp->fixmax = maxlines;
io_init(fd,(struct fileblk*)0,(char*)0);
fp->fixfp = io_ftable[fd];
fp->fixind = 1;
fp->fixcmds[1] = 2;
fp->fixcnt = 2;
fp->fixname = sh_heap(histname);
if(hsize==0)
{
write(fd,(char*)hist_stamp,2);
p_setout(fd);
}
else
{
int first;
off_t size = (HISMAX/4)+maxlines*HISLINE;
first = fp->fixind = hist_nearend(fd,hsize-size);
hist_eof();
his_start = fp->fixind-maxlines;
if(his_start <=0)
his_start = 1;
while(first > his_start)
{
size += size;
first = hist_nearend(fd,hsize-size);
fp->fixind = first;
}
hist_eof();
}
if(*fname)
unlink(fname);
if(hist_clean(fp->fixfd) && his_start>1 && hsize > HISMAX)
{
#ifdef DEBUG
p_setout(ERRIO);
p_num(getpid(),':');
p_str("hist_trim hsize",'=');
p_num(hsize,NL);
p_flush();
#endif
hist_trim(fp->fixind-maxlines);
}
return(1);
}
static void hist_free()
{
register struct history *fp = hist_ptr;
io_fclose(fp->fixfd);
free((char*)fp);
hist_ptr = 0;
}
static int hist_check(fd)
register int fd;
{
unsigned char magic[2];
io_seek(fd,(off_t)0,SEEK_SET);
if((read(fd,(char*)magic,2)!=2) || (magic[0]!=H_UNDO))
return(1);
hist_version = magic[1];
return(0);
}
#ifdef YELLOWP
# undef F_SETLK
#endif
#ifdef _FLOCK
# undef F_SETLK
# ifndef LOCK_EX
# include <sys/file.h>
# endif
#endif
#ifdef F_SETLK
# if defined(F_SETLK) && !defined(F_WRLCK)
# undef F_SETLK
# endif
static struct flock hislock = { F_WRLCK, 0, (off_t)0, (off_t)2, (pid_t)0 };
#endif
static int hist_clean(fd)
register int fd;
{
register int r = 0;
struct stat statb;
if(noclean)
return(0);
#ifdef _FLOCK
r = (flock(fd,LOCK_NB|LOCK_EX)==0);
#else
# ifdef F_SETLK
r = (fcntl(fd,F_SETLK,&hislock)==0);
# else
# ifdef KSHELL
r = sh.login_sh;
# endif
# endif
#endif
if(fstat(fd,&statb)>=0)
{
if(r)
{
if((time((time_t*)0)-statb.st_mtime) < HIST_RECENT)
r = 0;
}
}
return(r);
}
static void hist_trim(n)
int n;
{
register int c;
register struct fileblk *fp;
register struct history *hist_new;
struct history *hist_old = hist_ptr;
off_t old,new=0;
int fdo;
fdo= io_renumber(hist_ptr->fixfd,fcntl(hist_ptr->fixfd,F_DUPFD,USERIO));
hist_ptr->fixfd = fdo;
unlink(hist_ptr->fixname);
hist_ptr = 0;
if(!hist_open())
{
hist_ptr = hist_old;
hist_ptr->fixfd = io_renumber(fdo,FCIO);
return;
}
hist_new = hist_ptr;
p_setout(hist_new->fixfd);
fp = hist_new->fixfp;
if(n < 0)
n = 0;
htrim++;
do
{
hist_ptr = hist_old;
old = new;
new = io_seek(fdo,hist_position(++n),SEEK_SET);
hist_ptr = hist_new;
while((c=io_getc(fdo))!=EOF && c)
{
if(fp->ptr >= fp->last)
p_flush();
*fp->ptr++ = c;
hist_new->fixcnt++;
}
#ifdef KSHELL
st.states |= FIXFLG;
#endif
hist_flush();
}
while(new>old && c!=EOF);
htrim = 0;
hist_cancel();
io_fclose(fdo);
free((char*)hist_old);
}
static int hist_nearend(fd,size)
register int fd;
off_t size;
{
register int n = 0;
register int state = 0;
register int c;
if(size <=0)
goto begin;
io_seek(fd,size,SEEK_SET);
while((c=io_getc(fd))!=EOF) switch(state)
{
case 1:
if(c==H_CMDNO)
{
hist_ptr->fixcnt = size + n + 6;
state = 2;
}
break;
case 2:
if(hist_version && c)
{
n += 2;
state = 0;
break;
}
n = 0;
case 3:
case 4:
case 5:
if(hist_version)
n = (n<<8) + c;
else if(state<4)
n = (n<<7) + (c&0177);
state++;
break;
case 6:
return(n);
default:
state = (c==0);
n++;
}
begin:
io_seek(fd,(off_t)2,SEEK_SET);
hist_ptr->fixcnt = 2;
return(1);
}
void hist_close()
{
wasopen = hist_ptr;
hist_ptr = 0;
}
void hist_eof()
{
register struct history *fp = hist_ptr;
register int c;
register int incr = 0;
register int oldc = 0;
register off_t count = fp->fixcnt;
int skip = 0;
io_seek(fp->fixfd,count,SEEK_SET);
#ifdef INT16
while((c=io_getc(fp->fixfd))!=EOF)
{
#else
while(1)
{
c = *(fp->fixfp->ptr)++;
if(c==0 && (fp->fixfp->ptr > fp->fixfp->last))
{
c = io_readbuff(fp->fixfp);
if(c == EOF)
break;
}
c &= STRIP;
#endif
count++;
if(skip-- > 0)
{
if(skip==2)
hist_marker = count;
oldc = 0;
continue;
}
if(c == 0)
{
if(oldc==H_CMDNO && incr==0)
skip = 3;
fp->fixind += incr;
fp->fixcmds[fp->fixind&fixmask] = count;
incr = 0;
}
else if(oldc == 0)
{
if(c == H_CMDNO)
{
if(hist_version==0)
skip = 4;
incr = 0;
}
else if(c==H_UNDO)
incr = -1;
}
else
incr = 1;
oldc = c;
}
fp->fixcnt = count;
p_setout(fp->fixfd);
}
void hist_cancel()
{
register struct history *fp = hist_ptr;
register int c;
if(!fp)
return;
p_setout(fp->fixfd);
p_char(H_UNDO);
p_char(0);
p_flush();
fp->fixcnt += 2;
c = (--fp->fixind)&fixmask;
fp->fixcmds[c] = fp->fixcnt;
}
void hist_flush()
{
register struct history *fp = hist_ptr;
register struct fileblk *fd;
register int c;
int flush = 0;
int savcount;
if(!fp)
return;
#ifdef KSHELL
if(!(st.states&FIXFLG))
return;
st.states &= ~FIXFLG;
#endif
fd = fp->fixfp;
if(htrim)
{
p_char(0);
fp->fixcnt++;
goto set_count;
}
if((fp->fixcnt=lseek(fp->fixfd,(off_t)0,SEEK_END)) <0)
{
#ifdef DEBUG
p_setout(ERRIO);
p_num(getpid(),':');
p_str("hist_flush: EOF seek failed errno",'=');
p_num(errno,NL);
p_flush();
#endif
hist_free();
noclean++;
if(!htrim)
hist_open();
noclean = 0;
return;
}
p_setout(fp->fixfd);
while(--fd->ptr >= fd->base)
{
c= *fd->ptr;
if(!isspace(c))
{
if(c=='\\' && *(fd->ptr+1)!='\n')
fd->ptr++;
break;
}
}
if(++fd->ptr <= fd->base && !fp->fixflush)
{
fp->fixind--;
goto set_count;
}
p_char('\n');
p_char(0);
flush++;
fp->fixcnt += (fd->ptr-fd->base);
set_count:
if(fp->fixcnt&01)
{
fp->fixcnt++;
p_char(0);
flush++;
}
c = (++fp->fixind)&fixmask;
savcount = fp->fixcmds[c];
fp->fixcmds[c] = fp->fixcnt;
if(fp->fixcnt > hist_marker+IOBSIZE/2)
{
fp->fixcnt += 6;
p_char(H_CMDNO);
p_char(0);
c = (fp->fixind>>16);
p_char(c);
c = (fp->fixind>>8);
p_char(c);
c = fp->fixind;
p_char(c);
p_char(0);
flush++;
fp->fixcmds[c&fixmask] = fp->fixcnt;
hist_marker = fp->fixcnt;
}
if(flush && !htrim)
{
p_flush();
if(fd->flag&IOERR)
{
c = (fp->fixind--)&fixmask;
fp->fixcmds[c] = savcount;
}
}
fp->fixflush = 0;
}
off_t hist_position(n)
int n;
{
register struct history *fp = hist_ptr;
return(fp->fixcmds[n&fixmask]);
}
void hist_list(offset,last,nl)
off_t offset;
int last;
char *nl;
{
register int oldc=0;
register int c;
register struct history *fp = hist_ptr;
if(offset<0 || !fp)
{
p_str(e_unknown,'\n');
return;
}
io_seek(fp->fixfd,offset,SEEK_SET);
while((c = io_getc(fp->fixfd)) != EOF)
{
if(c && oldc=='\n')
p_str(nl,0);
else if(oldc==last && c=='\n')
return;
else if(c==0 && last)
return;
else if(oldc)
p_char(oldc);
oldc = c;
if(c==0)
return;
}
return;
}
histloc hist_find(string,index1,flag,direction)
char *string;
register int index1;
int flag;
int direction;
{
register struct history *fp = hist_ptr;
register int index2;
off_t offset;
histloc location;
location.his_command = -1;
if(!fp)
return(location);
if(flag)
{
index2 = *string;
if(index2=='\\')
string++;
else if(index2=='^')
{
flag=0;
string++;
}
}
index2 = fp->fixind;
if(direction<0)
{
index2 -= fp->fixmax;
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_position(index1);
if((location.his_line=hist_match(offset,string,flag))>=0)
{
location.his_command = index1;
return(location);
}
#ifdef KSHELL
if(sh.trapnote&SIGSET)
break;
#endif
}
return(location);
}
int hist_match(offset,string,flag)
off_t offset;
char *string;
int flag;
{
register char *cp;
register int c;
register struct history *fp = hist_ptr;
register off_t count;
int line = 0;
#ifdef MULTIBYTE
int nbytes = 0;
#endif
do
{
if(offset>=0)
{
io_seek(fp->fixfd,offset,SEEK_SET);
count = offset;
}
offset = -1;
for(cp=string;*cp;cp++)
{
if((c=io_getc(fp->fixfd)) == EOF || c ==0)
break;
count++;
#ifdef MULTIBYTE
if(--nbytes > 0)
{
if(cp==string)
{
cp--;
continue;
}
}
else
{
nbytes = echarset(c);
nbytes = in_csize(nbytes) + (nbytes>=2);
}
#endif
if(c == '\n')
line++;
if(flag && c == *string && offset<0)
offset = count;
if(*cp != c )
break;
}
if(*cp==0)
return(line);
}
while(flag && c && c != EOF);
return(-1);
}
#if ESH || VSH
int hist_copy(s1,command,line)
register char *s1;
int command, line;
{
register int c;
register struct history *fp = hist_ptr;
register int count = 0;
register char *s1max = s1+MAXLINE;
off_t offset;
if(!fp)
return(-1);
offset = hist_position(command);
io_seek(fp->fixfd,offset,SEEK_SET);
while ((c = io_getc(fp->fixfd)) && 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;
}
}
io_seek(fp->fixfd,(off_t)0,SEEK_END);
if(s1==0)
return(count);
if(count && (c= *(s1-1)) == '\n')
s1--;
*s1 = '\0';
return(count);
}
char *hist_word(s1,word)
char *s1;
int word;
{
register int c;
register char *cp = s1;
register int flag = 0;
if(hist_ptr==0)
#ifdef KSHELL
return(sh.lastarg);
#else
return((char*)0);
#endif
hist_copy(s1,hist_ptr->fixind-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 = cp;
flag++;
}
}
*cp = 0;
return(s1);
}
#endif
#ifdef ESH
histloc hist_locate(command,line,lines)
register int command;
register int line;
int lines;
{
histloc next;
line += lines;
if(!hist_ptr)
{
command = -1;
goto done;
}
if(lines > 0)
{
register int count;
while(command <= hist_ptr->fixind)
{
count = hist_copy(NIL,command,-1);
if(count > line)
goto done;
line -= count;
command++;
}
}
else
{
register int least = hist_ptr->fixind-hist_ptr->fixmax;
while(1)
{
if(line >=0)
goto done;
if(--command < least)
break;
line += hist_copy(NIL,command,-1);
}
command = -1;
}
next.his_command = command;
return(next);
done:
next.his_line = line;
next.his_command = command;
return(next);
}
#endif
#ifdef KSHELL
void hist_subst(command,fd,replace)
const char *command;
int fd;
char *replace;
{
register char *new=replace;
register char *sp;
register int c;
struct fileblk fb;
char inbuff[IOBSIZE+1];
char *string;
while(*++new != '=');
io_init(fd,&fb,inbuff);
stakseek(0);
while ((c=io_getc(fd)) != EOF)
stakputc(c);
string = stakfreeze(1);
io_fclose(fd);
*new++ = 0;
if((sp=sh_substitute(string,replace,new))==0)
sh_fail(command,e_subst);
*(new-1) = '=';
p_setout(hist_ptr->fixfd);
p_str(sp,0);
hist_flush();
p_setout(ERRIO);
p_str(sp,0);
sh_eval(sp);
}
#else
static void io_init(fd,fp,buf)
int fd;
register struct fileblk *fp;
char *buf;
{
if(!fp)
{
fp = new_of(struct fileblk,IOBSIZE+1);
buf = (char*)(fp+1);
fp->flag = IOFREE;
}
else
fp->flag = 0;
fp->fdes = fd;
fp->fseek = 0;
fp->base = fp->ptr = fp->last = buf;
*fp->ptr = 0;
fp->flag |= (IORW|IOREAD);
io_ftable[fd] = fp;
}
static int io_getc(fd)
int fd;
{
register struct fileblk *fp = io_ftable[fd];
register int c;
if(!fp)
return(EOF);
if(c= *fp->ptr++)
return(c&STRIP);
if(fp->ptr <= fp->last)
return(0);
return(io_readbuff(fp));
}
static off_t hoffset;
static off_t io_seek(fd, offset, ptrname)
int fd;
off_t offset;
register int ptrname;
{
register struct fileblk *fp;
register int c;
off_t p;
if(!(fp=io_ftable[fd]))
return(lseek(fd,offset,ptrname));
fp->flag &= ~IOEOF;
if(!(fp->flag&IOREAD))
{
p_flush();
}
c = 0;
if(fd==FCIO && ptrname==0 && (fp->flag&IOREAD) && offset<hoffset)
{
p = hoffset - (fp->last - fp->base);
if(offset >= p)
{
fp->ptr = fp->base + (int)(offset-p);
return(offset);
}
else
{
c = offset&(IOBSIZE-1);
offset -= c;
}
}
if(fp->flag&IORW)
{
fp->flag &= ~(IOWRT|IOREAD);
fp->last = fp->ptr = fp->base;
*fp->last = 0;
}
p = lseek(fd, offset, ptrname);
if(fd==FCIO)
{
if(ptrname==0)
hoffset = p;
else
hoffset = -1;
if(c)
{
io_readbuff(fp);
fp->ptr += (c-1);
}
}
return(p);
}
static int io_readbuff(fp)
register struct fileblk *fp;
{
register int n;
if (fp->flag & IORW)
fp->flag |= IOREAD;
if (!(fp->flag&IOREAD))
return(EOF);
fp->ptr = fp->last;
#ifdef SYSCALL
n = syscall(3, filenum(fp),fp->base, IOBSIZE);
#else
n = rEAd(filenum(fp),fp->base, IOBSIZE);
#endif
fp->ptr = fp->base;
if(n > 0)
fp->last = fp->base + n;
else
fp->last = fp->ptr;
*fp->last = 0;
if (n <= 0)
{
if (n == 0)
{
fp->flag |= IOEOF;
if (fp->flag & IORW)
fp->flag &= ~IOREAD;
}
else
fp->flag |= IOERR;
return(-1);
}
if(fp->fdes==FCIO)
hoffset += n;
return(*fp->ptr++&STRIP);
}
static void io_fclose(fd)
register int fd;
{
register struct fileblk *fp = io_ftable[fd];
if(fp && !(fp->flag&IOREAD))
p_flush();
close(fd);
if(fp && (fp->flag&IOFREE))
free(fp);
io_ftable[fd] = 0;
}
static int io_renumber(fa, fb)
register int fa;
register int fb;
{
if(fa >= 0)
{
close(fb);
fcntl(fa,0,fb);
if(io_ftable[fb] = io_ftable[fa])
io_ftable[fb]->fdes = fb;
io_ftable[fa] = 0;
close(fa);
#ifdef F_SETFD
fcntl(fb,F_SETFD,1);
#else
# ifdef FIOCLEX
ioctl(fb, FIOCLEX, NULL);
# endif
#endif
}
return(fb);
}
static int io_mktmp(fname, len)
register char *fname;
int len;
{
int fd;
if(!pathtemp(fname,len,NiL,"hist",&fd))
{
sh_fail("tmp-file",e_create);
fd = -1;
}
return(fd);
}
#endif