home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Geek Gadgets 1
/
ADE-1.bin
/
ade-dist
/
pdksh-4.9-src.tgz
/
tar.out
/
contrib
/
pdksh
/
sh
/
history.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-09-28
|
18KB
|
975 lines
/*
* command history
*
* only implements in-memory history.
*/
#ifndef lint
static char *RCSid = "$Id: history.c,v 1.7 93/06/01 23:40:36 sjg Exp $";
#endif
/*
* This file contains
* a) the original in-memory history mechanism
* b) a simple file saving history mechanism done by sjg@zen
* define EASY_HISTORY to get this
* c) a more complicated mechanism done by pc@hillside.co.uk
* that more closely follows the real ksh way of doing
* things. You need to have the mmap system call for this
* to work on your system
*/
#include "config.h"
#include "stdh.h"
#ifdef EASY_HISTORY
#include <errno.h>
#include <setjmp.h>
#include "sh.h"
static FILE *hist_fh = NULL;
static FILE *hist_open ARGS((char *mode));
#ifndef HISTFILE
# define HISTFILE ".pdksh_hist"
#endif
#else
/* Defines and includes for the complicated case */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <errno.h>
#include <setjmp.h>
#include "sh.h"
/*
* variables for handling the data file
*/
static char *hname;
static int histfd;
static int hsize;
static int hstarted;
static int hist_count_lines ARGS((unsigned char *, int));
static int hist_shrink ARGS((unsigned char *, int));
static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int));
static void histload ARGS((Source *, unsigned char *, int));
static void histinsert ARGS((Source *, int, unsigned char *));
static void writehistfile ARGS((int, char *));
static int sprinkle ARGS((int));
#ifdef MAP_FILE
#define MAP_FLAGS MAP_FILE|MAP_PRIVATE
#else
#define MAP_FLAGS MAP_PRIVATE
#endif
#endif /* of EASY_HISTORY */
char *histrpl();
char **current;
int curpos;
c_fc(wp)
register char **wp;
{
register char *id;
FILE *f;
struct temp *tf;
register char **hp;
char **hbeg, **hend;
char *p, *cmd = NULL;
int lflag = 0, nflag = 0, sflag = 0, rflag = 0, gflag = 0;
int done = 0;
void histbackup();
for (wp++; (id = *wp) != NULL && *id++ == '-' && !done; wp++)
while (*id && !done) {
switch (*id++) {
case 'l':
lflag++;
break;
case 'n':
nflag++;
break;
case 'r':
rflag++;
break;
case 'g':
gflag++;
break;
case 'e':
if (++wp && (p = *wp)) {
if (p[0] == '-' && !p[1]) {
sflag++;
} else {
cmd = alloc((size_t)(strlen(p)+4),ATEMP);
strcpy(cmd, p);
strcat(cmd, " $_");
}
} else
errorf("argument expected\n");
id = "";
break;
default:
wp--;
done++;
break;
}
}
if (sflag) {
char *pat = NULL, *rep = NULL;
hp = histptr - 1;
while ((id = *wp++) != NULL) {
/* todo: multiple substitutions */
if ((p = strchr(id, '=')) != NULL) {
pat = id;
rep = p;
*rep++ = '\0';
} else
hp = histget(id);
}
if (hp == NULL || hp < history)
errorf("cannot find history\n");
if (pat == NULL)
strcpy(line, *hp);
else
histrpl(*hp, pat, rep, gflag);
histbackup();
#ifdef EASY_HISTORY
histsave(line);
#else
histsave(source->line+1, line, 1);
#endif
histpush--;
line[0] = '\0';
return 0;
}
if (*wp != NULL) {
hbeg = histget(*wp++); /* first */
if (*wp != NULL)
hend = histget(*wp++); /* last */
else if (lflag)
hend = histptr;
else
hend = hbeg;
} else {
if (lflag)
hbeg = histptr - 16, hend = histptr;
else
hbeg = hend = histptr - 1;
if (hbeg < history)
hbeg = history;
}
if (hbeg == NULL || hend == NULL)
errorf("can't find history\n");
if (lflag)
f = stdout;
else {
nflag++;
tf = maketemp(ATEMP);
tf->next = e.temps; e.temps = tf;
f = fopen(tf->name, "w");
if (f == NULL)
errorf("cannot create temp file %s", tf->name);
setvbuf(f, (char *)NULL, _IOFBF, BUFSIZ);
}
for (hp = (rflag ? hend : hbeg); rflag ? (hp >= hbeg) : (hp <= hend);
rflag ? hp-- : hp++) {
if (!nflag)
fprintf(f, "%3d: ", source->line - (int)(histptr-hp));
fprintf(f, "%s\n", *hp);
}
if (lflag)
return 0;
else
fclose(f);
setstr(local("_"), tf->name);
if (cmd) {
command(cmd); /* edit temp file */
afree(cmd, ATEMP);
} else
command("${FCEDIT:-/bin/ed} $_");
f = fopen(tf->name, "r");
if (f == NULL)
errorf("cannot open temp file %s\n", tf->name);
setvbuf(f, (char *)NULL, _IOFBF, BUFSIZ);
/* we push the editted lines onto the history list */
while (fgets(line, sizeof(line), f) != NULL) {
#ifdef EASY_HISTORY
histsave(line);
#else
histsave(source->line, line, 1);
#endif
histpush--;
}
line[0] = '\0';
fclose(f);
return 0;
}
/******************************/
/* Back up over last histsave */
/******************************/
void
histbackup()
{
static int last_line = -1;
if (histptr > history && last_line != source->line) {
source->line--;
afree((void*)*histptr, APERM);
histptr--;
last_line = source->line;
}
}
/*
* get pointer to history given pattern
* pattern is a number or string
*/
char **
histget(str)
char *str;
{
register char **hp = NULL;
if (*str == '-')
hp = histptr + getn(str);
else
if (digit(*str))
hp = histptr + (getn(str) - source->line);
else
if (*str == '?') { /* unanchored match */
for (hp = histptr-1; hp >= history; hp--)
if (strstr(*hp, str+1) != NULL)
break;
} else { /* anchored match */
for (hp = histptr; hp >= history; hp--)
if (strncmp(*hp, str, strlen(str)) == 0)
break;
}
return (history <= hp && hp <= histptr) ? hp : NULL;
}
char *
histrpl(s, pat, rep, global)
char *s;
char *pat, *rep;
int global;
{
char *s1, *p, *last = NULL;
int len = strlen(pat);
if (strlen(s) - strlen(pat) + strlen(rep) >= LINE)
errorf("substitution too long\n");
line[0] = '\0';
p = line;
while (s1 = strstr(s, pat)) {
strncpy(p, s, s1 - s); /* first part */
strcpy(p + (s1 - s), rep); /* replacement */
s = s1 + len;
last = s1;
p = strchr(p, 0);
if (!global)
s = "";
}
if (last)
strcpy(p, last + len); /* last part */
else
errorf("substitution failed\n");
return line;
}
/*
* Return the current position.
*/
char **
histpos()
{
return current;
}
int
histN()
{
return curpos;
}
int
histnum(n)
{
int last = histptr - history;
if (n < 0 || n >= last) {
current = histptr;
curpos = last;
return last;
} else {
current = &history[n];
curpos = n;
return n;
}
}
/*
* This will become unecessary if histget is modified to allow
* searching from positions other than the end, and in either
* direction.
*/
char *
findhist(start, fwd, str)
int start;
int fwd;
char *str;
{
int pos = start;
char *line, *last;
/* XXX check that we are valid after this */
if (fwd)
pos++;
else
pos--;
histnum(pos);
line = *histpos();
do {
last = line;
if (strstr(line, str) != 0) {
/* keep position current */
return (line);
}
if (fwd)
pos++;
else
pos--;
histnum(pos);
line = *histpos();
} while (line && *line && line != last && pos>0);
histnum(start);
if (pos <= 0)
return (char*)-1; /* TODO */
return NULL;
}
#ifdef EASY_HISTORY
/*
* save command in history
*/
void
histsave(cmd)
char *cmd;
{
register char **hp = histptr;
char *cp;
if (++hp >= history + HISTORY) { /* remove oldest command */
afree((void*)*history, APERM);
for (hp = history; hp < history + HISTORY - 1; hp++)
hp[0] = hp[1];
}
*hp = strsave(cmd, APERM);
if ((cp = strchr(*hp, '\n')) != NULL)
*cp = '\0';
histptr = hp;
}
/*
* 92-04-25 <sjg@zen>
* A simple history file implementation.
* At present we only save the history when we exit.
* This can cause problems when there are multiple shells are
* running under the same user-id. The last shell to exit gets
* to save its history.
*/
void
hist_init(s)
Source *s;
{
static int once = 0;
FILE *fh;
if (once++)
return;
if (fh = hist_open("r"))
{
while (fgets(line, sizeof(line), fh) != NULL)
{
histsave(line);
s->line++;
}
line[0] = '\0';
fclose(fh);
#if 0 /* this might be a good idea? */
hist_fh = hist_open("a");
#endif
}
}
void
init_histvec()
{ ; }
/*
* save our history.
* We check that we do not have more than we are allowed.
* If the history file is read-only we do nothing.
* Handy for having all shells start with a useful history set.
*/
void
hist_finish()
{
static int once = 0;
FILE *fh;
register int i, mx;
register char **hp, *mode = "w";
if (once++)
return;
if ((mx = atoi(strval(global("HISTSIZE")))) > HISTORY || mx <= 0)
mx = HISTORY;
/* check how many we have */
i = histptr - history;
if (i >= mx)
{
hp = &histptr[-mx];
}
else
{
hp = history;
}
if (fh = hist_open(mode))
{
for (i = 0; i < mx && hp[i]; i++)
fprintf(fh, "%s\n", hp[i]);
fclose(fh);
}
}
/*
* simply grab the nominated history file.
*/
static FILE *
hist_open(mode)
char *mode;
{
register char *rcp;
FILE *fh;
char name[128];
if ((rcp = strval(global("HISTFILE"))) == NULL || *rcp == '\0')
{
(void) sprintf(name, "%s/%s", strval(global("HOME")), HISTFILE);
rcp = name;
}
return fopen(rcp, mode);
}
#else /* EASY_HISTORY */
/*
* Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
* a) permit HISTSIZE to control number of lines of history stored
* b) maintain a physical history file
*
* It turns out that there is a lot of ghastly hackery here
*/
/*
* save command in history
*/
void
histsave(lno, cmd, dowrite)
int lno;
char *cmd;
int dowrite;
{
register char **hp;
char *cp;
cmd = strsave(cmd, APERM);
if ((cp = strchr(cmd, '\n')) != NULL)
*cp = '\0';
if (histfd && dowrite)
writehistfile(lno, cmd);
hp = histptr;
if (++hp >= history + histsize) { /* remove oldest command */
afree((void*)*history, APERM);
for (hp = history; hp < history + histsize - 1; hp++)
hp[0] = hp[1];
}
*hp = cmd;
histptr = hp;
}
/*
* set history
* this means reallocating the dataspace
*/
void
sethistsize(n)
int n;
{
int offset;
if (n != histsize) {
offset = histptr - history;
history = (char **)aresize(history, n*sizeof(char *), APERM);
if (n < histsize && offset > histsize)
offset = histsize;
histsize = n;
histptr = history + offset;
}
}
/*
* set history file
* This can mean reloading/resetting/starting history file
* maintenance
*/
void
sethistfile(name)
char *name;
{
/* if not started then nothing to do */
if (hstarted == 0)
return;
/* if the name is the same as the name we have */
if (hname && strcmp(hname, name) == 0)
return;
/*
* its a new name - possibly
*/
if (histfd) {
/* yes the file is open */
(void) close(histfd);
histfd = 0;
hsize = 0;
afree(hname, APERM);
hname = NULL;
/* let's reset the history */
histptr = history - 1;
source->line = 0;
}
hist_init(source);
}
/*
* initialise the history vector
*/
void
init_histvec()
{
if (history == (char **)NULL) {
history = (char **)alloc(histsize*sizeof (char *), APERM);
histptr = history-1;
}
}
/*
* Write history data to a file nominated by HISTFILE
* if HISTFILE is unset then history still happens, but
* the data is not written to a file
* All copies of ksh looking at the file will maintain the
* same history. This is ksh behaviour.
*
* This stuff uses mmap()
* if your system ain't got it - then you'll have to undef HISTORYFILE
*/
/*
* Open a history file
* Format is:
* Bytes 1, 2: HMAGIC - just to check that we are dealing with
* the correct object
* Then follows a number of stored commands
* Each command is
* <command byte><command number(4 bytes)><bytes><null>
*/
#define HMAGIC1 0xab
#define HMAGIC2 0xcd
#define COMMAND 0xff
void
hist_init(s)
Source *s;
{
unsigned char *base;
int lines;
int bytes;
int fd;
hstarted = 1;
if (flag[FTALKING] == 0)
return;
hname = strval(global("HISTFILE"));
if (hname == NULL)
return;
hname = strsave(hname, APERM);
retry:
/* we have a file and are interactive */
if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
return;
histfd = fcntl(fd, F_DUPFD, FDBASE);
close(fd);
(void) fd_clexec(histfd);
(void) flock(histfd, LOCK_EX);
hsize = lseek(histfd, 0L, L_XTND);
if (hsize == 0) {
/* add magic */
if (sprinkle(histfd)) {
hist_finish();
return;
}
}
else if (hsize > 0) {
/*
* we have some data
*/
base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0);
/*
* check on its validity
*/
if ((int)base == -1 || *base != HMAGIC1 || base[1] != HMAGIC2) {
if ((int)base != -1)
munmap((caddr_t)base, hsize);
hist_finish();
unlink(hname);
goto retry;
}
if (hsize > 2) {
lines = hist_count_lines(base+2, hsize-2);
if (lines > histsize) {
/* we need to make the file smaller */
if (hist_shrink(base, hsize))
unlink(hname);
munmap((caddr_t)base, hsize);
hist_finish();
goto retry;
}
}
histload(s, base+2, hsize-2);
munmap((caddr_t)base, hsize);
}
(void) flock(histfd, LOCK_UN);
hsize = lseek(histfd, 0L, L_XTND);
}
typedef enum state {
shdr, /* expecting a header */
sline, /* looking for a null byte to end the line */
sn1, /* bytes 1 to 4 of a line no */
sn2, sn3, sn4,
} State;
static int
hist_count_lines(base, bytes)
register unsigned char *base;
register int bytes;
{
State state = shdr;
register lines = 0;
while (bytes--) {
switch (state)
{
case shdr:
if (*base == COMMAND)
state = sn1;
break;
case sn1:
state = sn2; break;
case sn2:
state = sn3; break;
case sn3:
state = sn4; break;
case sn4:
state = sline; break;
case sline:
if (*base == '\0')
lines++, state = shdr;
}
base++;
}
return lines;
}
/*
* Shrink the history file to histsize lines
*/
static int
hist_shrink(oldbase, oldbytes)
unsigned char *oldbase;
int oldbytes;
{
int fd;
char nfile[1024];
struct stat statb;
unsigned char *nbase = oldbase;
int nbytes = oldbytes;
nbase = hist_skip_back(nbase, &nbytes, histsize);
if (nbase == NULL)
return 1;
if (nbase == oldbase)
return 0;
/*
* create temp file
*/
(void) sprintf(nfile, "%s.%d", hname, getpid());
if ((fd = creat(nfile, 0600)) < 0)
return 1;
if (sprinkle(fd)) {
close(fd);
unlink(nfile);
return 1;
}
if (write(fd, nbase, nbytes) != nbytes) {
close(fd);
unlink(nfile);
return 1;
}
/*
* worry about who owns this file
*/
if (fstat(histfd, &statb) >= 0)
fchown(fd, statb.st_uid, statb.st_gid);
close(fd);
/*
* rename
*/
if (rename(nfile, hname) < 0)
return 1;
return 0;
}
/*
* find a pointer to the data `no' back from the end of the file
* return the pointer and the number of bytes left
*/
static unsigned char *
hist_skip_back(base, bytes, no)
unsigned char *base;
int *bytes;
int no;
{
register int lines = 0;
register unsigned char *ep;
for (ep = base + *bytes; ep > base; ep--)
{
while (*ep != COMMAND) {
if (--ep == base)
break;
}
if (++lines == no) {
*bytes = *bytes - ((char *)ep - (char *)base);
return ep;
}
}
if (ep > base)
return base;
return NULL;
}
/*
* load the history structure from the stored data
*/
static void
histload(s, base, bytes)
Source *s;
register unsigned char *base;
register int bytes;
{
State state;
int lno;
unsigned char *line;
for (state = shdr; bytes-- > 0; base++) {
switch (state) {
case shdr:
if (*base == COMMAND)
state = sn1;
break;
case sn1:
lno = (((*base)&0xff)<<24);
state = sn2;
break;
case sn2:
lno |= (((*base)&0xff)<<16);
state = sn3;
break;
case sn3:
lno |= (((*base)&0xff)<<8);
state = sn4;
break;
case sn4:
lno |= (*base)&0xff;
line = base+1;
state = sline;
break;
case sline:
if (*base == '\0') {
/* worry about line numbers */
if (histptr >= history && lno-1 != s->line) {
/* a replacement ? */
histinsert(s, lno, line);
}
else {
s->line = lno;
histsave(lno, (char *)line, 0);
}
state = shdr;
}
}
}
}
/*
* Insert a line into the history at a specified number
*/
static void
histinsert(s, lno, line)
Source *s;
int lno;
unsigned char *line;
{
register char **hp;
if (lno >= s->line-(histptr-history) && lno <= s->line) {
hp = &histptr[lno-s->line];
if (*hp)
afree((void*)*hp, APERM);
*hp = strsave((char *)line, APERM);
}
}
/*
* write a command to the end of the history file
* This *MAY* seem easy but it's also necessary to check
* that the history file has not changed in size.
* If it has - then some other shell has written to it
* and we should read those commands to update our history
*/
static void
writehistfile(lno, cmd)
int lno;
char *cmd;
{
int sizenow;
unsigned char *base;
unsigned char *new;
int bytes;
char hdr[5];
(void) flock(histfd, LOCK_EX);
sizenow = lseek(histfd, 0L, L_XTND);
if (sizenow != hsize) {
/*
* Things have changed
*/
if (sizenow > hsize) {
/* someone has added some lines */
bytes = sizenow - hsize;
base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0);
if ((int)base == -1)
goto bad;
new = base + hsize;
if (*new != COMMAND) {
munmap((caddr_t)base, sizenow);
goto bad;
}
source->line--;
histload(source, new, bytes);
source->line++;
lno = source->line;
munmap((caddr_t)base, sizenow);
hsize = sizenow;
} else {
/* it has shrunk */
/* but to what? */
/* we'll give up for now */
goto bad;
}
}
/*
* we can write our bit now
*/
hdr[0] = COMMAND;
hdr[1] = (lno>>24)&0xff;
hdr[2] = (lno>>16)&0xff;
hdr[3] = (lno>>8)&0xff;
hdr[4] = lno&0xff;
(void) write(histfd, hdr, 5);
(void) write(histfd, cmd, strlen(cmd)+1);
hsize = lseek(histfd, 0L, L_XTND);
(void) flock(histfd, LOCK_UN);
return;
bad:
hist_finish();
}
void
hist_finish()
{
(void) flock(histfd, LOCK_UN);
(void) close(histfd);
histfd = 0;
}
/*
* add magic to the history file
*/
static int
sprinkle(fd)
int fd;
{
static char mag[] = { HMAGIC1, HMAGIC2 };
return(write(fd, mag, 2) != 2);
}
#endif