On Fri, Apr 28, 2017 at 10:01:06AM +0200, Theo Buehler wrote: > This is an updated version of an old patch by marco [1], including some > improvements and fixes by natano and me. Here's marco's description: > > > 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. > > If you wish to retain your current ksh history, you can create a > plaintext version of it using a command like this (assuming HISTFILE and > HISTSIZE are set): > > cp $HISTFILE $HISTFILE.old > fc -ln 1 | sed 's/^ //' > ~/ksh_hist.txt > > Then apply the patch and install the new ksh. > > Once you've exited all interactive sessions with the old ksh (a reboot is > probably your safest bet), you can use ksh_hist.txt as your history file. > > Note that the old ksh recklessly truncates a history file that it > doesn't understand, so be careful not to run old and new interactive ksh > processes in parallel. > > [1]: https://marc.info/?l=openbsd-tech&m=131490865525379&w=2
I had no feedback on this. Here's the patch again with the extra cast (pointed out by Michael W. Bombardieri) removed. Index: bin/ksh/alloc.c =================================================================== RCS file: /cvs/src/bin/ksh/alloc.c,v retrieving revision 1.15 diff -u -p -r1.15 alloc.c --- bin/ksh/alloc.c 1 Jun 2016 10:29:20 -0000 1.15 +++ bin/ksh/alloc.c 29 May 2017 09:47:43 -0000 @@ -47,7 +47,7 @@ alloc(size_t size, Area *ap) if (size > SIZE_MAX - sizeof(struct link)) internal_errorf(1, "unable to allocate memory"); - 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: bin/ksh/history.c =================================================================== RCS file: /cvs/src/bin/ksh/history.c,v retrieving revision 1.58 diff -u -p -r1.58 history.c --- bin/ksh/history.c 24 Aug 2016 16:09:40 -0000 1.58 +++ bin/ksh/history.c 29 May 2017 09:47:43 -0000 @@ -9,8 +9,7 @@ * a) the original in-memory history mechanism * b) a more complicated mechanism done by p...@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 <sys/stat.h> @@ -22,25 +21,16 @@ #include <stdio.h> #include <string.h> #include <unistd.h> +#include <vis.h> #include "sh.h" #ifdef HISTORY -# 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(void); +static FILE *history_open(void); +static int history_load(Source *); +static void history_close(void); static int hist_execute(char *); static int hist_replace(char **, const char *, const char *, int); @@ -48,11 +38,14 @@ static char **hist_get(const char *, i static char **hist_get_oldest(void); static void histbackup(void); +static FILE *histfd; 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 uint32_t line_co; +static struct stat last_sb; int c_fc(char **wp) @@ -546,15 +539,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 */ @@ -562,6 +550,7 @@ sethistfile(const char *name) hist_source->line = 0; } + history_close(); hist_init(hist_source); } @@ -578,6 +567,16 @@ init_histvec(void) } } +static void +history_lock(int operation) +{ + while (flock(fileno(histfd), operation) != 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + else + break; + } +} /* * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to @@ -594,18 +593,28 @@ init_histvec(void) void histsave(int lno, const char *cmd, int dowrite) { - char **hp; - char *c, *cp; + struct stat sb; + char **hp, *c, *cp, *encoded; + + if (dowrite && histfd) { + history_lock(LOCK_EX); + if (fstat(fileno(histfd), &sb) != -1) { + if (timespeccmp(&sb.st_mtim, &last_sb.st_mtim, ==)) + ; /* file is unchanged */ + else { + /* reset history */ + histptr = history - 1; + hist_source->line = 0; + history_load(hist_source); + } + } + } c = str_save(cmd, APERM); - if ((cp = strchr(c, '\n')) != NULL) + if ((cp = strrchr(c, '\n')) != NULL) *cp = '\0'; - if (histfd && dowrite) - writehistfile(lno, c); - hp = histptr; - if (++hp >= history + histsize) { /* remove oldest command */ afree(*history, APERM); for (hp = history; hp < history + histsize - 1; hp++) @@ -613,365 +622,148 @@ 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 is still recorded, 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. - */ - -/* - * History file format: - * Bytes 1, 2: HMAGIC - just to check that we are dealing with - the correct object - * Each command, in the format: - <command byte><command number(4 bytes)><bytes><null> - */ -#define HMAGIC1 0xab -#define HMAGIC2 0xcd -#define COMMAND 0xff + if (dowrite && histfd) { + /* append to file */ + if (fseeko(histfd, 0, SEEK_END) == 0 && + stravis(&encoded, c, VIS_SAFE | VIS_NL) != -1) { + fprintf(histfd, "%s\n", encoded); + fflush(histfd); + fstat(fileno(histfd), &last_sb); + line_co++; + writehistfile(); + free(encoded); + } + history_lock(LOCK_UN); + } +} -void -hist_init(Source *s) +static FILE * +history_open(void) { - unsigned char *base; - int lines; - int fd; - struct stat sb; - - if (Flag(FTALKING) == 0) - return; - - hstarted = 1; - - hist_source = s; + int fd, fddup; + FILE *f; + struct stat sb; - hname = str_val(global("HISTFILE")); - if (hname == NULL) - return; - hname = str_save(hname, APERM); - - retry: - /* we have a file and are interactive */ - if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) - return; + if ((fd = open(hname, O_RDWR | O_CREAT | O_EXLOCK, 0600)) == -1) + return NULL; if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { close(fd); - return; + return NULL; } - - histfd = savefd(fd); - if (histfd != fd) + fddup = savefd(fd); + if (fddup != fd) close(fd); - (void) flock(histfd, LOCK_EX); + if ((f = fdopen(fddup, "r+")) == NULL) + close(fddup); + else + last_sb = sb; - hsize = lseek(histfd, 0L, SEEK_END); + return f; +} - if (hsize == 0) { - /* add magic */ - if (sprinkle(histfd)) { - hist_finish(); - return; - } - } - else if (hsize > 0) { - /* - * we have some data - */ - base = 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); +static void +history_close(void) +{ + if (histfd) { + fflush(histfd); + fclose(histfd); + histfd = NULL; } - (void) flock(histfd, LOCK_UN); - hsize = lseek(histfd, 0L, SEEK_END); } -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(unsigned char *base, int bytes) +history_load(Source *s) { - State state = shdr; - int lines = 0; + char *p, encoded[LINE + 1], line[LINE + 1]; + + rewind(histfd); - while (bytes--) { - switch (state) { - case shdr: - if (*base == COMMAND) - state = sn1; + /* just read it all; will auto resize history upon next command */ + for (line_co = 1; ; line_co++) { + p = fgets(encoded, sizeof(encoded), histfd); + if (p == NULL || feof(histfd) || ferror(histfd)) 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(encoded, '\n')) == NULL) { + bi_errorf("history file is corrupt"); + return 1; } - base++; + *p = '\0'; + s->line = line_co; + s->cmd_offset = line_co; + strunvis(line, encoded); + histsave(line_co, line, 0); } - return lines; -} -/* - * Shrink the history file to histsize lines - */ -static int -hist_shrink(unsigned char *oldbase, int oldbytes) -{ - int fd; - char nfile[1024]; - unsigned char *nbase = oldbase; - int nbytes = oldbytes; + writehistfile(); - nbase = hist_skip_back(nbase, &nbytes, histsize); - if (nbase == NULL) - return 1; - if (nbase == oldbase) - return 0; + 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; +void +hist_init(Source *s) +{ + if (Flag(FTALKING) == 0) + return; - if (sprinkle(fd)) { - close(fd); - unlink(nfile); - return 1; - } - if (write(fd, nbase, nbytes) != nbytes) { - close(fd); - unlink(nfile); - return 1; - } - close(fd); + hstarted = 1; - /* - * rename - */ - if (rename(nfile, hname) < 0) - return 1; - return 0; -} + hist_source = s; + hname = str_val(global("HISTFILE")); + if (hname == NULL) + return; + hname = str_save(hname, APERM); + histfd = history_open(); + if (histfd == NULL) + return; -/* - * 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; + history_load(s); - 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; + history_lock(LOCK_UN); } -/* - * load the history structure from the stored data - */ static void -histload(Source *s, unsigned char *base, int bytes) +writehistfile(void) { - 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; - } - } - } -} + char *cmd, *encoded; + int i; -/* - * Insert a line into the history at a specified number - */ -static void -histinsert(Source *s, int lno, unsigned char *line) -{ - char **hp; + /* see if file has grown over 25% */ + if (line_co < histsize + (histsize / 4)) + return; - if (lno >= s->line-(histptr-history) && lno <= s->line) { - hp = &histptr[lno-s->line]; - afree(*hp, APERM); - *hp = str_save((char *)line, APERM); - } -} + /* rewrite the whole caboodle */ + rewind(histfd); + if (ftruncate(fileno(histfd), 0) == -1) { + bi_errorf("failed to rewrite history file - %s", + strerror(errno)); + } + for (i = 0; i < histsize; i++) { + cmd = history[i]; + if (cmd == NULL) + break; -/* - * 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]; - struct iovec iov[2]; - - (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 = 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; + if (stravis(&encoded, cmd, VIS_SAFE | VIS_NL) != -1) { + if (fprintf(histfd, "%s\n", encoded) == -1) { + free(encoded); + return; } - 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; + free(encoded); } } - /* - * 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; - iov[0].iov_base = hdr; - iov[0].iov_len = 5; - iov[1].iov_base = cmd; - iov[1].iov_len = strlen(cmd) + 1; - (void) writev(histfd, iov, 2); - hsize = lseek(histfd, 0L, SEEK_END); - (void) flock(histfd, LOCK_UN); - return; -bad: - hist_finish(); + + line_co = histsize; + + fflush(histfd); + fstat(fileno(histfd), &last_sb); } 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); + history_close(); } #else /* HISTORY */