From 12e246495c2f40ca289b27a552d548318f255472 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Fri, 3 Jun 2011 19:54:43 +0000 Subject: 29413: "print -S" for saving to history with lexical word split --- Src/hist.c | 239 +++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 138 insertions(+), 101 deletions(-) (limited to 'Src/hist.c') diff --git a/Src/hist.c b/Src/hist.c index 01a97da2b..87bfde882 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -2338,110 +2338,11 @@ readhistfile(char *fn, int err, int readflags) /* * Divide up the words. */ - nwordpos = 0; start = pt; uselex = isset(HISTLEXWORDS) && !(readflags & HFILE_FAST); - if (uselex) { - /* - * Attempt to do this using the lexer. - */ - LinkList wordlist = bufferwords(NULL, pt, NULL, - LEXFLAGS_COMMENTS_KEEP); - LinkNode wordnode; - int nwords_max; - nwords_max = 2 * countlinknodes(wordlist); - if (nwords_max > nwords) { - nwords = nwords_max; - words = (short *)realloc(words, nwords*sizeof(short)); - } - for (wordnode = firstnode(wordlist); - wordnode; - incnode(wordnode)) { - char *word = getdata(wordnode); - - for (;;) { - /* - * Not really an oddity: "\\\n" is - * removed from input as if whitespace. - */ - if (inblank(*pt)) - pt++; - else if (pt[0] == '\\' && pt[1] == '\n') - pt += 2; - else - break; - } - if (!strpfx(word, pt)) { - int bad = 0; - /* - * Oddity 1: newlines turn into semicolons. - */ - if (!strcmp(word, ";")) - continue; - while (*pt) { - if (!*word) { - bad = 1; - break; - } - /* - * Oddity 2: !'s turn into |'s. - */ - if (*pt == *word || - (*pt == '!' && *word == '|')) { - pt++; - word++; - } else { - bad = 1; - break; - } - } - if (bad) { -#ifdef DEBUG - dputs(ERRMSG("bad wordsplit reading history: " - "%s\nat: %s\nword: %s"), - start, pt, word); -#endif - pt = start; - nwordpos = 0; - uselex = 0; - break; - } - } else if (!strcmp(word, ";") && strpfx(";;", pt)) { - /* - * Don't get confused between a semicolon that's - * probably really a newline and a double - * semicolon that's terminating a case. - */ - continue; - } - words[nwordpos++] = pt - start; - pt += strlen(word); - words[nwordpos++] = pt - start; - } + histsplitwords(pt, &words, &nwords, &nwordpos, uselex); + if (uselex) freeheap(); - } - if (!uselex) { - do { - for (;;) { - if (inblank(*pt)) - pt++; - else if (pt[0] == '\\' && pt[1] == '\n') - pt += 2; - else - break; - } - if (*pt) { - if (nwordpos >= nwords) - words = (short *) - realloc(words, (nwords += 64)*sizeof(short)); - words[nwordpos++] = pt - start; - while (*pt && !inblank(*pt)) - pt++; - words[nwordpos++] = pt - start; - } - } while (*pt); - - } he->nwords = nwordpos/2; if (he->nwords) { @@ -3141,6 +3042,142 @@ bufferwords(LinkList list, char *buf, int *index, int flags) return list; } +/* + * Split up a line into words for use in a history file. + * + * lineptr is the line to be split. + * + * *wordsp and *nwordsp are an array already allocated to hold words + * and its length. The array holds both start and end positions, + * so *nwordsp actually counts twice the number of words in the + * original string. *nwordsp may be zero in which case the array + * will be allocated. + * + * *nwordposp returns the used length of *wordsp in the same units as + * *nwordsp, i.e. twice the number of words in the input line. + * + * If uselex is 1, attempt to do this using the lexical analyser. + * This is more accurate, but slower; for reading history files it's + * controlled by the option HISTLEXWORDS. If this failed (which + * indicates a bug in the shell) it falls back to whitespace-separated + * strings, printing a message if in debug mode. + * + * If uselex is 0, just look for whitespace-separated words; the only + * special handling is for a backslash-newline combination as used + * by the history file format to save multiline buffers. + */ +/**/ +mod_export void +histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, + int uselex) +{ + int nwords = *nwordsp, nwordpos = 0; + short *words = *wordsp; + char *start = lineptr; + + if (uselex) { + LinkList wordlist = bufferwords(NULL, lineptr, NULL, + LEXFLAGS_COMMENTS_KEEP); + LinkNode wordnode; + int nwords_max; + + nwords_max = 2 * countlinknodes(wordlist); + if (nwords_max > nwords) { + *nwordsp = nwords = nwords_max; + *wordsp = words = (short *)zrealloc(words, nwords*sizeof(short)); + } + for (wordnode = firstnode(wordlist); + wordnode; + incnode(wordnode)) { + char *word = getdata(wordnode); + + for (;;) { + /* + * Not really an oddity: "\\\n" is + * removed from input as if whitespace. + */ + if (inblank(*lineptr)) + lineptr++; + else if (lineptr[0] == '\\' && lineptr[1] == '\n') + lineptr += 2; + else + break; + } + if (!strpfx(word, lineptr)) { + int bad = 0; + /* + * Oddity 1: newlines turn into semicolons. + */ + if (!strcmp(word, ";")) + continue; + while (*lineptr) { + if (!*word) { + bad = 1; + break; + } + /* + * Oddity 2: !'s turn into |'s. + */ + if (*lineptr == *word || + (*lineptr == '!' && *word == '|')) { + lineptr++; + word++; + } else { + bad = 1; + break; + } + } + if (bad) { +#ifdef DEBUG + dputs(ERRMSG("bad wordsplit reading history: " + "%s\nat: %s\nword: %s"), + start, lineptr, word); +#endif + lineptr = start; + nwordpos = 0; + uselex = 0; + break; + } + } else if (!strcmp(word, ";") && strpfx(";;", lineptr)) { + /* + * Don't get confused between a semicolon that's + * probably really a newline and a double + * semicolon that's terminating a case. + */ + continue; + } + words[nwordpos++] = lineptr - start; + lineptr += strlen(word); + words[nwordpos++] = lineptr - start; + } + } + if (!uselex) { + do { + for (;;) { + if (inblank(*lineptr)) + lineptr++; + else if (lineptr[0] == '\\' && lineptr[1] == '\n') + lineptr += 2; + else + break; + } + if (*lineptr) { + if (nwordpos >= nwords) { + *nwordsp = nwords = nwords + 64; + *wordsp = words = (short *) + zrealloc(words, nwords*sizeof(*words)); + } + words[nwordpos++] = lineptr - start; + while (*lineptr && !inblank(*lineptr)) + lineptr++; + words[nwordpos++] = lineptr - start; + } + } while (*lineptr); + } + + *nwordposp = nwordpos; +} + /* Move the current history list out of the way and prepare a fresh history * list using hf for HISTFILE, hs for HISTSIZE, and shs for SAVEHIST. If * the hf value is an empty string, HISTFILE will be unset from the new -- cgit v1.2.3 From a05a51ce8a9ea46b6c019cd2bab39a405256a07f Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Tue, 12 Jul 2011 08:37:11 +0000 Subject: 29542: fix crash in hbegin(), remove bad test 29543: fix backslash-newline within words with histlexwords --- ChangeLog | 11 +++- Src/Zle/zle_main.c | 2 +- Src/hist.c | 150 ++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 124 insertions(+), 39 deletions(-) (limited to 'Src/hist.c') diff --git a/ChangeLog b/ChangeLog index e2fa793ca..20fa306e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2011-07-12 Peter Stephenson + + * 29543: Src/hist.c: saved history lines with backslash-newline + in the middle of words confused histlexwords. + + * 29542: Src/hist.c, Src/Zle/zle_main.c: remove test when + initialising history that could cause crashes (and was probably + never useful); ensure ZLE returns NULL if there's an error. + 2011-07-04 Peter Stephenson * Eric Moors: 29531: Completion/Unix/Command/_adb: completion @@ -15097,5 +15106,5 @@ ***************************************************** * This is used by the shell to define $ZSH_PATCHLEVEL -* $Revision: 1.5394 $ +* $Revision: 1.5395 $ ***************************************************** diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c index 6acedee70..771ff2cf0 100644 --- a/Src/Zle/zle_main.c +++ b/Src/Zle/zle_main.c @@ -1233,7 +1233,7 @@ zleread(char **lp, char **rp, int flags, int context) alarm(0); freeundo(); - if (eofsent) { + if (eofsent || errflag) { s = NULL; } else { zleline[zlell++] = ZWC('\n'); diff --git a/Src/hist.c b/Src/hist.c index 87bfde882..f1bae14f4 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -876,7 +876,18 @@ hbegin(int dohist) stophist = (!interact || unset(SHINSTDIN)) ? 2 : 0; else stophist = 0; - if (stophist == 2 || (inbufflags & INP_ALIAS)) { + /* + * pws: We used to test for "|| (inbufflags & INP_ALIAS)" + * in this test, but at this point we don't have input + * set up up so this can trigger unnecessarily. + * I don't see how the test at this point could ever be + * useful, since we only get here when we're initialising + * the history mechanism, before we've done any input. + * + * (I also don't see any point where this function is called with + * dohist=0.) + */ + if (stophist == 2) { chline = hptr = NULL; hlinesz = 0; chwords = NULL; @@ -3090,7 +3101,10 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, wordnode; incnode(wordnode)) { char *word = getdata(wordnode); + char *lptr, *wptr = word; + int loop_next = 0, skipping; + /* Skip stuff at the start of the word */ for (;;) { /* * Not really an oddity: "\\\n" is @@ -3098,57 +3112,119 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, */ if (inblank(*lineptr)) lineptr++; - else if (lineptr[0] == '\\' && lineptr[1] == '\n') + else if (lineptr[0] == '\\' && lineptr[1] == '\n') { + /* + * Optimisation: we handle this in the loop below, + * too. + */ lineptr += 2; - else + } else break; } - if (!strpfx(word, lineptr)) { - int bad = 0; - /* - * Oddity 1: newlines turn into semicolons. - */ - if (!strcmp(word, ";")) - continue; - while (*lineptr) { - if (!*word) { - bad = 1; + lptr = lineptr; + /* + * Skip chunks of word with possible intervening + * backslash-newline. + * + * To get round C's annoying lack of ability to + * reference the outer loop, we'll break from this + * one with + * loop_next = 0: carry on as normal + * loop_next = 1: break from outer loop + * loop_next = 2: continue round outer loop. + */ + do { + skipping = 0; + if (strpfx(wptr, lptr)) { + /* + * Normal case: word from lexer matches start of + * string from line. Just advance over it. + */ + int len; + if (!strcmp(wptr, ";") && strpfx(";;", lptr)) { + /* + * Don't get confused between a semicolon that's + * probably really a newline and a double + * semicolon that's terminating a case. + */ + loop_next = 2; break; } + len = strlen(wptr); + lptr += len; + wptr += len; + } else { /* - * Oddity 2: !'s turn into |'s. + * Didn't get to the end of the word. + * See what's amiss. */ - if (*lineptr == *word || - (*lineptr == '!' && *word == '|')) { - lineptr++; - word++; - } else { - bad = 1; + int bad = 0; + /* + * Oddity 1: newlines turn into semicolons. + */ + if (!strcmp(wptr, ";")) + { + loop_next = 2; break; } - } - if (bad) { + while (*lptr) { + if (!*wptr) { + /* + * End of the word before the end of the + * line: not good. + */ + bad = 1; + loop_next = 1; + break; + } + /* + * Oddity 2: !'s turn into |'s. + */ + if (*lptr == *wptr || + (*lptr == '!' && *wptr == '|')) { + lptr++; + wptr++; + } else if (lptr[0] == '\\' && + lptr[1] == '\n') { + /* + * \\\n can occur in the middle of a word; + * wptr is already pointing at this, we + * just need to skip over the break + * in lptr and look at the next chunk. + */ + lptr += 2; + skipping = 1; + break; + } else { + bad = 1; + loop_next = 1; + break; + } + } + if (bad) { #ifdef DEBUG - dputs(ERRMSG("bad wordsplit reading history: " - "%s\nat: %s\nword: %s"), - start, lineptr, word); + dputs(ERRMSG("bad wordsplit reading history: " + "%s\nat: %s\nword: %s"), + start, lineptr, word); #endif - lineptr = start; - nwordpos = 0; - uselex = 0; - break; + lineptr = start; + nwordpos = 0; + uselex = 0; + loop_next = 1; + } } - } else if (!strcmp(word, ";") && strpfx(";;", lineptr)) { - /* - * Don't get confused between a semicolon that's - * probably really a newline and a double - * semicolon that's terminating a case. - */ + } while (skipping); + if (loop_next) { + if (loop_next == 1) + break; continue; } + /* Record position of current word... */ words[nwordpos++] = lineptr - start; - lineptr += strlen(word); - words[nwordpos++] = lineptr - start; + words[nwordpos++] = lptr - start; + + /* ready for start of next word. */ + lineptr = lptr; } } if (!uselex) { -- cgit v1.2.3 From 5c3c825e5fee76829b8f61020c3f0e72f9f6b316 Mon Sep 17 00:00:00 2001 From: Peter Stephenson Date: Mon, 18 Jul 2011 18:13:03 +0000 Subject: users/16131: skip reading empty history file --- ChangeLog | 6 +++++- Src/hist.c | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'Src/hist.c') diff --git a/ChangeLog b/ChangeLog index 84e45bb23..ccf74e02d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2011-07-18 Peter Stephenson + + * users/16131: Src/hist.c: skip reading an empty history file. + 2011-07-18 Peter Stephenson * Matthieu Baerts: 29547: Completion/Unix/Command/_bzr: cdiff @@ -15111,5 +15115,5 @@ ***************************************************** * This is used by the shell to define $ZSH_PATCHLEVEL -* $Revision: 1.5396 $ +* $Revision: 1.5397 $ ***************************************************** diff --git a/Src/hist.c b/Src/hist.c index f1bae14f4..6edd7b8cd 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -2246,10 +2246,12 @@ readhistfile(char *fn, int err, int readflags) if (!fn && !(fn = getsparam("HISTFILE"))) return; + if (stat(unmeta(fn), &sb) < 0 || + sb.st_size == 0) + return; if (readflags & HFILE_FAST) { - if (stat(unmeta(fn), &sb) < 0 - || (lasthist.fsiz == sb.st_size && lasthist.mtim == sb.st_mtime) - || lockhistfile(fn, 0)) + if ((lasthist.fsiz == sb.st_size && lasthist.mtim == sb.st_mtime) + || lockhistfile(fn, 0)) return; lasthist.fsiz = sb.st_size; lasthist.mtim = sb.st_mtime; -- cgit v1.2.3 From d0366e19605f8ab3c2d05ec9a7dbcb63c41ac89b Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Sun, 14 Aug 2011 14:16:45 +0000 Subject: unposted: fix a typo in a comment. --- ChangeLog | 4 +++- Src/hist.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'Src/hist.c') diff --git a/ChangeLog b/ChangeLog index e1cbeaa84..de6eff507 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,8 @@ * 28852: Misc/zargs, Zle/match-words-by-style: Use syntax that doesn't depend on SHORT_LOOPS being set. + * unposted: Src/hist.c: fix a typo in a comment. + 2011-08-11 Peter Stephenson * Src/Modules/datetime.mdd: unposted: also fix the autofeatures. @@ -15242,5 +15244,5 @@ ***************************************************** * This is used by the shell to define $ZSH_PATCHLEVEL -* $Revision: 1.5424 $ +* $Revision: 1.5425 $ ***************************************************** diff --git a/Src/hist.c b/Src/hist.c index 6edd7b8cd..636f46411 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -229,7 +229,7 @@ ihwaddc(int c) /* Quote un-expanded bangs in the history line. */ if (c == bangchar && stophist < 2 && qbang) /* If qbang is not set, we do not escape this bangchar as it's * - * not mecessary (e.g. it's a bang in !=, or it is followed * + * not necessary (e.g. it's a bang in !=, or it is followed * * by a space). Roughly speaking, qbang is zero only if the * * history interpreter has already digested this bang and * * found that it is not necessary to escape it. */ -- cgit v1.2.3 From f7111d33584a54aa28fca5660f25e8a9fecdfb0d Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 18 Aug 2011 02:24:10 +0000 Subject: 29694: avoid overwriting current history word when not expanding an alias or history event in-place. --- Src/hist.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Src/hist.c') diff --git a/Src/hist.c b/Src/hist.c index 636f46411..aeb6edda5 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -1355,7 +1355,8 @@ ihwend(void) (chwordlen += 32) * sizeof(short)); } - if (hwgetword > -1) { + if (hwgetword > -1 && + (inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) { /* We want to reuse the current word position */ chwordpos = hwgetword; /* Start from where previous word ended, if possible */ -- cgit v1.2.3