1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/src/cmd/ksh93/edit/history.c
Martijn Dekker b590a9f155 [shp cleanup 01..20] all the rest (re: 2d3ec8b6)
This combines 20 cleanup commits from the dev branch.

All changed files:
- Clean up pointer defererences to sh.
- Remove shp arguments from functions.

Other notable changes:

src/cmd/ksh93/include/shell.h,
src/cmd/ksh93/sh/init.c:
- On second thought, get rid of the function version of
  sh_getinterp() as libshell ABI compatibility is moot. We've
  already been breaking that by reordering the sh struct, so there
  is no way it's going to work without recompiling.

src/cmd/ksh93/sh/name.c:
- De-obfuscate the relationship between nv_scan() and scanfilter().
  The former just calls the latter as a static function, there's no
  need to do that via a function pointer and void* type conversions.

src/cmd/ksh93/bltins/typeset.c,
src/cmd/ksh93/sh/name.c,
src/cmd/ksh93/sh/nvdisc.c:
- 'struct adata' and 'struct tdata', defined as local struct types
  in these files, need to have their first three fields in common,
  the first being a pointer to sh. This is because scanfilter() in
  name.c accesses these fields via a type conversion. So the sh
  field needed to be removed in all three at the same time.
  TODO: de-obfuscate: good practice definition via a header file.

src/cmd/ksh93/sh/path.c:
- Naming consistency: reserve the path_ function name prefix for
  externs and rename statics with that prefix.
- The default path was sometimes referred to as the standard path.
  To use one term, rename std_path to defpath and onstdpath() to
  ondefpath().
- De-obfuscate SHOPT_PFSH conditional code by only calling
  pf_execve() (was path_pfexecve()) if that is compiled in.

src/cmd/ksh93/include/streval.h,
src/cmd/ksh93/sh/streval.c:
- Rename extern strval() to arith_strval() for consistency.

src/cmd/ksh93/sh/string.c:
- Remove outdated/incorrect isxdigit() fallback; '#ifnded isxdigit'
  is not a correct test as isxdigit() is specified as a function.
  Plus, it's part of C89/C90 which we now require. (re: ac8991e5)

src/cmd/ksh93/sh/suid_exec.c:
- Replace an incorrect reference to shgd->current_pid with
  getpid(); it cannot work as (contrary to its misleading directory
  placement) suid_exec is an independent libast program with no
  link to ksh or libshell at all. However, no one noticed because
  this was in fallback code for ancient systems without
  setreuid(2). Since that standard function was specified in POSIX
  Issue 4 Version 2 from 1994, we should remove that fallback code
  sometime as part of another obsolete code cleanup operation to
  avoid further bit rot. (re: 843b546c)

src/cmd/ksh93/bltins/print.c: genformat():
- Remove preformat[] which was always empty and had no effect.

src/cmd/ksh93/shell.3:
- Minor copy-edit.
- Remove documentation for nonexistent sh.infile_name. A search
  through ast-open-archive[*] reveals this never existed at all.
- Document sh.savexit (== $?).

src/cmd/ksh93/shell.3,
src/cmd/ksh93/include/shell.h,
src/cmd/ksh93/sh/init.c:
- Remove sh.gd/shgd; this is now unused and was never documented
  or exposed in the shell.h public interface.
- sh_sigcheck() was documented in shell.3 as taking no arguments
  whereas in the actual code it took a shp argument. I decided to
  go with the documentation.
- That leaves sh_parse() as the only documented function that still
  takes an shp argument. I'm just going to go ahead and remove it
  for consistency, reverting sh_parse() to its pre-2003 spec.
- Remove undocumented/unused sh_bltin_tree() function which simply
  returned sh.bltin_tree.
- Bump SH_VERSION to 20220106.
2022-01-07 16:16:31 +00:00

1164 lines
27 KiB
C

/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2012 AT&T Intellectual Property *
* Copyright (c) 2020-2021 Contributors to ksh 93u+m *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.eclipse.org/org/documents/epl-v10.html *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
/*
* History file manipulation routines
*
* David Korn
* AT&T Labs
*
*/
/*
* Each command in the history file starts on an even byte and 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
* than 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 \
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 <ast.h>
#include <sfio.h>
#include "FEATURE/time"
#include <error.h>
#include <ls.h>
#include "defs.h"
#include "variables.h"
#include "path.h"
#include "builtins.h"
#include "io.h"
#include "history.h"
#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);
static ssize_t hist_write(Sfio_t*, const void*, size_t, Sfdisc_t*);
static int hist_exceptf(Sfio_t*, int, void*, Sfdisc_t*);
static int histinit;
static mode_t histmode;
static History_t *hist_ptr;
#if SHOPT_ACCTFILE
static int acctfd;
static char *logname;
# include <pwd.h>
static int acctinit(History_t *hp)
{
register char *cp, *acctfile;
Namval_t *np = nv_search("ACCTFILE",sh.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 = sh_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 = sh_fcntl(acctfd, F_dupfd_cloexec, 10)) >= 0)
{
sh_close(acctfd);
acctfd = n;
}
}
if(acctfd < 0)
{
acctfd = 0;
return(0);
}
if(sh_isdevfd(acctfile))
{
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)
{
char *cp, *last;
int id1, id2, r=0, n, fd;
if((fd=open(name, O_RDONLY,O_cloexec)) < 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(sh.euserid >=id1 && sh.euserid <= id2)
r |= 1;
if(sh.userid >=id1 && sh.userid <= id2)
r |= 2;
cp = last;
}
while(*cp==';' || *cp==' ');
done:
sh_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)
{
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(sh.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);
}
retry:
cp = path_relative(histname);
if(!histinit)
histmode = S_IRUSR|S_IWUSR;
if((fd=open(cp,O_BINARY|O_APPEND|O_RDWR|O_CREAT|O_cloexec,histmode))>=0)
{
hsize=lseek(fd,(off_t)0,SEEK_END);
}
if((unsigned)fd < 10)
{
int n;
if((n=sh_fcntl(fd,F_dupfd_cloexec,10))>=0)
{
sh_close(fd);
fd=n;
}
}
/* make sure that file has history file format */
if(hsize && hist_check(fd))
{
sh_close(fd);
hsize = 0;
if(unlink(cp)>=0)
goto retry;
fd = -1;
}
if(fd < 0)
{
/* don't allow root a history_file in /tmp */
if(sh.userid)
{
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|O_cloexec);
}
}
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 );
hp = new_of(History_t,(--histmask)*sizeof(off_t));
sh.hist_ptr = hist_ptr = hp;
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 = sh_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);
histinit = 1;
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",sh.current_pid,hsize);
sfsync(sfstderr);
#endif /* DEBUG */
hp = hist_trim(hp,(int)hp->histind-maxlines);
}
sfdisc(hp->histfp,&hp->histdisc);
(HISTCUR)->nvalue.lp = (&hp->histind);
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|O_cloexec,S_IRUSR|S_IWUSR))>=0 && fd < 10)
{
int n;
if((n = sh_fcntl(fd,F_dupfd_cloexec, 10)) >= 0)
{
sh_close(fd);
fd = n;
}
}
if(fd>=0)
{
fcntl(fd,F_SETFD,FD_CLOEXEC);
hp->tty = sh_strdup(isatty(2)?ttyname(2):"notty");
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)
{
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;
sh.hist_ptr = 0;
#if SHOPT_ACCTFILE
if(acctfd)
{
sh_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 <n> 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;
sh_close(sffileno(hist_old->histfp));
tmpname = (char*)sh_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)
{
free(tmpname);
tmpname = name;
}
fd = open(tmpname,O_RDONLY|O_cloexec);
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())
{
/* 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 oldind,n,skip=0;
off_t last = sfseek(hp->histfp,(off_t)0,SEEK_END);
if(last < count)
{
last = -1;
count = 2+HIST_MARKSZ;
oldind = hp->histind;
if((hp->histind -= hp->histsize) < 0)
hp->histind = 1;
}
again:
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);
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--;
if(!histinit && (cp <= endbuff))
{
unsigned char *marker = (unsigned char*)(cp-4);
hp->histind = ((marker[0]<<16)|(marker[1]<<8)|marker[2] -1);
}
}
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;
if(incmd && last)
{
sfputc(hp->histfp,0);
hist_cancel(hp);
count = 2;
skip = 0;
oldind -= hp->histind;
hp->histind = hp->histind-hp->histsize + oldind +2;
if(hp->histind<0)
hp->histind = 1;
if(last<0)
{
char buff[HIST_MARKSZ];
int fd = open(hp->histname,O_RDWR|O_cloexec);
if(fd>=0)
{
hist_marker(buff,hp->histind);
write(fd,(char*)hist_stamp,2);
write(fd,buff,HIST_MARKSZ);
sh_close(fd);
}
}
last = 0;
goto again;
}
}
/*
* 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())
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
*/
static ssize_t hist_write(Sfio_t *iop,const void *buff,register size_t insize,Sfdisc_t* handle)
{
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)
{
time_t t=time((time_t*)0);
sfprintf(hp->auditfp, "%u;%lu;%s;%*s%c",
sh_isoption(SH_PRIVILEGED) ? sh.euserid : sh.userid,
(unsigned long)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 <n> into buffer <buff>
* 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 <n>
*/
off_t hist_tell(register History_t *hp, int n)
{
return(hp->histcmds[hist_ind(hp,n)]);
}
/*
* seek to the position of command <n>
*/
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 <offset> onto file <outfile>.
* if character <last> appears before newline it is deleted
* each new-line character is replaced with string <nl>.
*/
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);
}
/* allow a search to be aborted */
if(sh.trapnote & SH_SIGSET)
break;
}
return(location);
}
/*
* search for <string> in history file starting at location <offset>
* 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;
mbinit();
sfseek(hp->histfp,offset,SEEK_SET);
if(!(cp = first = (unsigned char*)sfgetr(hp->histfp,0,0)))
return(-1);
m = sfvalue(hp->histfp);
n = (int)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((c=mbsize(cp)) < 0)
c = 1;
cp += c;
m -= c;
}
return(-1);
}
#if SHOPT_ESH || SHOPT_VSH
/*
* copy command <command> from history file to s1
* at most <size> 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.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 <word> from command number <command>
*/
char *hist_word(char *string,int size,int word)
{
register int c;
register int is_space;
register int quoted;
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(quoted=0;c = *cp;cp++)
{
is_space = isspace(c) && !quoted;
if(is_space && flag)
{
*cp = 0;
if(--word==0)
break;
flag = 0;
}
else if(is_space==0 && flag==0)
{
s1 = (char*)cp;
flag++;
}
if (c=='\'' && !quoted)
{
for(cp++;*cp && *cp != c;cp++)
;
}
else if (c=='\"' && !quoted)
{
for(cp++;*cp && (*cp != c || quoted);cp++)
quoted = *cp=='\\' ? !quoted : 0;
}
quoted = *cp=='\\' ? !quoted : 0;
}
*cp = 0;
if(s1 != string)
/* We can't use strcpy() because the two buffers may overlap. */
memmove(string,s1,strlen(s1)+1);
return(string);
}
#endif /* SHOPT_ESH */
#if SHOPT_ESH
/*
* given the current command and line number,
* and number of lines back or forward,
* 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
*/
static int hist_exceptf(Sfio_t* fp, int type, void *data, Sfdisc_t *handle)
{
register int newfd,oldfd;
History_t *hp = (History_t*)handle;
NOT_USED(data);
if(type==SF_WRITE)
{
if(errno==ENOSPC || hp->histwfail++ >= 10)
return(0);
/* write failure could be NFS problem, try to reopen */
sh_close(oldfd=sffileno(fp));
if((newfd=open(hp->histname,O_BINARY|O_APPEND|O_CREAT|O_RDWR|O_cloexec,S_IRUSR|S_IWUSR)) >= 0)
{
if(sh_fcntl(newfd, F_dupfd_cloexec, 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);
}