summaryrefslogtreecommitdiff
path: root/system/ksh-openbsd/patches/08-new_history_implementation.diff
diff options
context:
space:
mode:
Diffstat (limited to 'system/ksh-openbsd/patches/08-new_history_implementation.diff')
-rw-r--r--system/ksh-openbsd/patches/08-new_history_implementation.diff584
1 files changed, 584 insertions, 0 deletions
diff --git a/system/ksh-openbsd/patches/08-new_history_implementation.diff b/system/ksh-openbsd/patches/08-new_history_implementation.diff
new file mode 100644
index 0000000000..d083b42554
--- /dev/null
+++ b/system/ksh-openbsd/patches/08-new_history_implementation.diff
@@ -0,0 +1,584 @@
+From: Marco Peereboom
+To: tech@openbsd.org
+Subject: ksh history corruption
+
+I have had enough of corrupt ksh history so I had a look at the code to
+try to fix it. The magical code was very magical so I basically deleted
+most of it and made ksh history into a flat text file. It handles
+multiple ksh instances writing to the same text file with locks just
+like the current ksh does. I haven't noticed any differences in
+behavior running this.
+
+Code is much simpler and it shaves ~4k of the binary too.
+
+Index: alloc.c
+===================================================================
+RCS file: /cvs/src/bin/ksh/alloc.c,v
+retrieving revision 1.8
+diff -u -p -r1.8 alloc.c
+--- alloc.c 21 Jul 2008 17:30:08 -0000 1.8
++++ alloc.c 30 Aug 2011 18:05:47 -0000
+@@ -62,7 +62,7 @@ alloc(size_t size, Area *ap)
+ {
+ struct link *l;
+
+- l = malloc(sizeof(struct link) + size);
++ l = calloc(1, sizeof(struct link) + size);
+ if (l == NULL)
+ internal_errorf(1, "unable to allocate memory");
+ l->next = ap->freelist;
+Index: history.c
+===================================================================
+RCS file: /cvs/src/bin/ksh/history.c,v
+retrieving revision 1.39
+diff -u -p -r1.39 history.c
+--- history.c 19 May 2010 17:36:08 -0000 1.39
++++ history.c 31 Aug 2011 19:33:24 -0000
+@@ -11,8 +11,7 @@
+ * a) the original in-memory history mechanism
+ * b) 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
++ * things.
+ */
+
+ #include "sh.h"
+@@ -22,19 +21,10 @@
+ # include <sys/file.h>
+ # include <sys/mman.h>
+
+-/*
+- * variables for handling the data file
+- */
+-static int histfd;
+-static int hsize;
+-
+-static int hist_count_lines(unsigned char *, int);
+-static int hist_shrink(unsigned char *, int);
+-static unsigned char *hist_skip_back(unsigned char *,int *,int);
+-static void histload(Source *, unsigned char *, int);
+-static void histinsert(Source *, int, unsigned char *);
+-static void writehistfile(int, char *);
+-static int sprinkle(int);
++static void writehistfile(FILE *);
++static FILE *history_open(int *);
++static int history_load(FILE *, Source *);
++static void history_close(FILE *);
+
+ static int hist_execute(char *);
+ static int hist_replace(char **, const char *, const char *, int);
+@@ -45,8 +35,8 @@ static void histbackup(void);
+ static char **current; /* current position in history[] */
+ static char *hname; /* current name of history file */
+ static int hstarted; /* set after hist_init() called */
+-static Source *hist_source;
+-
++static Source *hist_source;
++static struct stat last_sb;
+
+ int
+ c_fc(char **wp)
+@@ -529,15 +519,10 @@ sethistfile(const char *name)
+ /* 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;
++ if (hname) {
+ afree(hname, APERM);
+ hname = NULL;
+ /* let's reset the history */
+@@ -577,18 +562,26 @@ init_histvec(void)
+ void
+ histsave(int lno, const char *cmd, int dowrite)
+ {
+- char **hp;
+- char *c, *cp;
++ char **hp;
++ char *c, *cp;
++ int changed;
++ FILE *f = NULL;
++
++ if (dowrite) {
++ f = history_open(&changed);
++ if (f && changed) {
++ /* reset history */
++ histptr = history - 1;
++ hist_source->line = 0;
++ history_load(f, hist_source);
++ }
++ }
+
+ c = str_save(cmd, APERM);
+ if ((cp = strchr(c, '\n')) != NULL)
+ *cp = '\0';
+
+- if (histfd && dowrite)
+- writehistfile(lno, c);
+-
+ hp = histptr;
+-
+ if (++hp >= history + histsize) { /* remove oldest command */
+ afree((void*)*history, APERM);
+ for (hp = history; hp < history + histsize - 1; hp++)
+@@ -596,371 +589,125 @@ histsave(int lno, const char *cmd, int d
+ }
+ *hp = c;
+ histptr = hp;
+-}
+-
+-/*
+- * 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
++ if (dowrite && f) {
++ writehistfile(f);
++ history_close(f);
++ }
++}
+
+-void
+-hist_init(Source *s)
++static FILE *
++history_open(int *changed)
+ {
+- unsigned char *base;
+- int lines;
+- int fd;
+-
+- if (Flag(FTALKING) == 0)
+- return;
+-
+- hstarted = 1;
+-
+- hist_source = s;
+-
+- hname = str_val(global("HISTFILE"));
+- if (hname == NULL)
+- return;
+- hname = str_save(hname, APERM);
++ int fd;
++ FILE *f = NULL;
++ struct stat sb;
+
+- retry:
+- /* we have a file and are interactive */
+- if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
+- return;
+-
+- histfd = savefd(fd);
+- if (histfd != fd)
++ if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1)
++ return (NULL);
++ f = fdopen(fd, "r+");
++ if (f == NULL) {
+ close(fd);
++ goto bad;
++ }
+
+- (void) flock(histfd, LOCK_EX);
++ if (fstat(fileno(f), &sb) == -1)
++ goto bad;
++ if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==))
++ *changed = 0;
++ else
++ *changed = 1;
+
+- hsize = lseek(histfd, 0L, SEEK_END);
++ return (f);
++bad:
++ if (f)
++ fclose(f);
+
+- 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_FILE|MAP_PRIVATE, histfd, 0);
+- /*
+- * check on its validity
+- */
+- if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
+- if (base != MAP_FAILED)
+- munmap((caddr_t)base, hsize);
+- hist_finish();
+- if (unlink(hname) != 0)
+- return;
+- 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))
+- if (unlink(hname) != 0)
+- return;
+- munmap((caddr_t)base, hsize);
+- hist_finish();
+- goto retry;
+- }
+- }
+- histload(hist_source, base+2, hsize-2);
+- munmap((caddr_t)base, hsize);
+- }
+- (void) flock(histfd, LOCK_UN);
+- hsize = lseek(histfd, 0L, SEEK_END);
++ return (NULL);
+ }
+
+-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 void
++history_close(FILE *f)
++{
++ fflush(f);
++ fstat(fileno(f), &last_sb);
++ fclose(f);
++}
+
+ static int
+-hist_count_lines(unsigned char *base, int bytes)
++history_load(FILE *f, Source *s)
+ {
+- State state = shdr;
+- int lines = 0;
++ char *p, line[LINE + 1];
++ uint32_t i;
+
+- while (bytes--) {
+- switch (state) {
+- case shdr:
+- if (*base == COMMAND)
+- state = sn1;
++ /* just read it all; will auto resize history upon next command */
++ for (i = 1; ; i++) {
++ p = fgets(line, sizeof line, f);
++ if (p == NULL || feof(f) || ferror(f))
+ 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;
++ if ((p = strchr(line, '\n')) == NULL) {
++ bi_errorf("history file is corrupt");
++ return (1);
+ }
+- base++;
++ *p = '\0';
++
++ s->line = i;
++ s->cmd_offset = i;
++ histsave(i, (char *)line, 0);
+ }
+- return lines;
++
++ return (0);
+ }
+
+-/*
+- * Shrink the history file to histsize lines
+- */
+-static int
+-hist_shrink(unsigned char *oldbase, int oldbytes)
++void
++hist_init(Source *s)
+ {
+- 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) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
+- if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0)
+- return 1;
++ FILE *f = NULL;
++ int changed;
+
+- 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);
++ if (Flag(FTALKING) == 0)
++ return;
+
+- /*
+- * rename
+- */
+- if (rename(nfile, hname) < 0)
+- return 1;
+- return 0;
+-}
++ hstarted = 1;
+
++ hist_source = s;
+
+-/*
+- * 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(unsigned char *base, int *bytes, int no)
+-{
+- int lines = 0;
+- unsigned char *ep;
++ hname = str_val(global("HISTFILE"));
++ if (hname == NULL)
++ return;
++ hname = str_save(hname, APERM);
+
+- for (ep = base + *bytes; --ep > base; ) {
+- /* this doesn't really work: the 4 byte line number that is
+- * encoded after the COMMAND byte can itself contain the
+- * COMMAND byte....
+- */
+- for (; ep > base && *ep != COMMAND; ep--)
+- ;
+- if (ep == base)
+- break;
+- if (++lines == no) {
+- *bytes = *bytes - ((char *)ep - (char *)base);
+- return ep;
+- }
+- }
+- return NULL;
+-}
++ f = history_open(&changed);
++ if (f == NULL)
++ return;
+
+-/*
+- * load the history structure from the stored data
+- */
+-static void
+-histload(Source *s, unsigned char *base, int bytes)
+-{
+- State state;
+- int lno = 0;
+- unsigned char *line = NULL;
+-
+- 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;
+- s->cmd_offset = lno;
+- histsave(lno, (char *)line, 0);
+- }
+- state = shdr;
+- }
+- }
+- }
++ history_load(f, s);
++ history_close(f);
+ }
+
+-/*
+- * Insert a line into the history at a specified number
+- */
+ static void
+-histinsert(Source *s, int lno, unsigned char *line)
++writehistfile(FILE *f)
+ {
+- char **hp;
++ int i;
++ char *cmd;
+
+- if (lno >= s->line-(histptr-history) && lno <= s->line) {
+- hp = &histptr[lno-s->line];
+- if (*hp)
+- afree((void*)*hp, APERM);
+- *hp = str_save((char *)line, APERM);
+- }
+-}
++ if (ftruncate(fileno(f), 0) == -1)
++ return;
++ rewind(f);
+
+-/*
+- * 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(int lno, char *cmd)
+-{
+- int sizenow;
+- unsigned char *base;
+- unsigned char *new;
+- int bytes;
+- unsigned char hdr[5];
+-
+- (void) flock(histfd, LOCK_EX);
+- sizenow = lseek(histfd, 0L, SEEK_END);
+- 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_FILE|MAP_PRIVATE, histfd, 0);
+- if (base == MAP_FAILED)
+- goto bad;
+- new = base + hsize;
+- if (*new != COMMAND) {
+- munmap((caddr_t)base, sizenow);
+- goto bad;
+- }
+- hist_source->line--;
+- histload(hist_source, new, bytes);
+- hist_source->line++;
+- lno = hist_source->line;
+- munmap((caddr_t)base, sizenow);
+- hsize = sizenow;
+- } else {
+- /* it has shrunk */
+- /* but to what? */
+- /* we'll give up for now */
+- goto bad;
+- }
++ for (i = 0; i < histsize; i++) {
++ cmd = history[i];
++ if (cmd == NULL)
++ break;
++ if (fprintf(f, "%s\n", cmd) == -1)
++ return;
+ }
+- /*
+- * 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, SEEK_END);
+- (void) flock(histfd, LOCK_UN);
+- return;
+-bad:
+- hist_finish();
+ }
+
+ void
+ hist_finish(void)
+ {
+- (void) flock(histfd, LOCK_UN);
+- (void) close(histfd);
+- histfd = 0;
+ }
+-
+-/*
+- * add magic to the history file
+- */
+-static int
+-sprinkle(int fd)
+-{
+- static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
+-
+- return(write(fd, mag, 2) != 2);
+-}
+-
+ #else /* HISTORY */
+
+ /* No history to be compiled in: dummy routines to avoid lots more ifdefs */
+