diff options
Diffstat (limited to 'Src/hist.c')
-rw-r--r-- | Src/hist.c | 357 |
1 files changed, 266 insertions, 91 deletions
diff --git a/Src/hist.c b/Src/hist.c index 4660fd073..bd03c4f11 100644 --- a/Src/hist.c +++ b/Src/hist.c @@ -134,6 +134,8 @@ mod_export int hist_skip_flags; /* Bits of histactive variable */ #define HA_ACTIVE (1<<0) /* History mechanism is active */ #define HA_NOINC (1<<1) /* Don't store, curhist not incremented */ +#define HA_INWORD (1<<2) /* We're inside a word, don't add + start and end markers */ /* Array of word beginnings and endings in current history line. */ @@ -222,13 +224,119 @@ static int histsave_stack_pos = 0; static zlong histfile_linect; +/* save history context */ + +/**/ +void +hist_context_save(struct hist_stack *hs, int toplevel) +{ + if (toplevel) { + /* top level, make this version visible to ZLE */ + zle_chline = chline; + /* ensure line stored is NULL-terminated */ + if (hptr) + *hptr = '\0'; + } + hs->histactive = histactive; + hs->histdone = histdone; + hs->stophist = stophist; + hs->hline = chline; + hs->hptr = hptr; + hs->chwords = chwords; + hs->chwordlen = chwordlen; + hs->chwordpos = chwordpos; + hs->hgetc = hgetc; + hs->hungetc = hungetc; + hs->hwaddc = hwaddc; + hs->hwbegin = hwbegin; + hs->hwend = hwend; + hs->addtoline = addtoline; + hs->hlinesz = hlinesz; + /* + * We save and restore the command stack with history + * as it's visible to the user interactively, so if + * we're preserving history state we'll continue to + * show the current set of commands from input. + */ + hs->cstack = cmdstack; + hs->csp = cmdsp; + + stophist = 0; + chline = NULL; + hptr = NULL; + histactive = 0; + cmdstack = (unsigned char *)zalloc(CMDSTACKSZ); + cmdsp = 0; +} + +/* restore history context */ + +/**/ +void +hist_context_restore(const struct hist_stack *hs, int toplevel) +{ + if (toplevel) { + /* Back to top level: don't need special ZLE value */ + DPUTS(hs->hline != zle_chline, "BUG: Ouch, wrong chline for ZLE"); + zle_chline = NULL; + } + histactive = hs->histactive; + histdone = hs->histdone; + stophist = hs->stophist; + chline = hs->hline; + hptr = hs->hptr; + chwords = hs->chwords; + chwordlen = hs->chwordlen; + chwordpos = hs->chwordpos; + hgetc = hs->hgetc; + hungetc = hs->hungetc; + hwaddc = hs->hwaddc; + hwbegin = hs->hwbegin; + hwend = hs->hwend; + addtoline = hs->addtoline; + hlinesz = hs->hlinesz; + if (cmdstack) + zfree(cmdstack, CMDSTACKSZ); + cmdstack = hs->cstack; + cmdsp = hs->csp; +} + +/* + * Mark that the current level of history is within a word whatever + * characters turn up, or turn that mode off. This is used for nested + * parsing of substitutions. + * + * The caller takes care only to turn this on or off at the start + * or end of recursive use of the same mode, so a single flag is + * good enough here. + */ + +/**/ +void +hist_in_word(int yesno) +{ + if (yesno) + histactive |= HA_INWORD; + else + histactive &= ~HA_INWORD; +} + /* add a character to the current history word */ static void ihwaddc(int c) { /* Only if history line exists and lexing has not finished. */ - if (chline && !(errflag || lexstop)) { + if (chline && !(errflag || lexstop) && + /* + * If we're reading inside a word for command substitution + * we allow the lexer to expand aliases but don't deal + * with them here. Note matching code in ihungetc(). + * TBD: it might be neater to deal with all aliases in this + * fashion as we never need the expansion in the history + * line, only in the lexer and above. + */ + (inbufflags & (INP_ALIAS|INP_HIST)) != INP_ALIAS) { /* 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 * @@ -287,7 +395,8 @@ ihgetc(void) c = histsubchar(c); if (c < 0) { /* bad expansion */ - errflag = lexstop = 1; + lexstop = 1; + errflag |= ERRFLAG_ERROR; return ' '; } } @@ -366,6 +475,7 @@ getsubsargs(char *subline, int *gbalp, int *cflagp) zsfree(hsubl); hsubl = ptr1; } else if (!hsubl) { /* fail silently on this */ + zsfree(ptr1); zsfree(ptr2); return 0; } @@ -415,9 +525,20 @@ histsubchar(int c) static zlong mev = -1; char *buf, *ptr; char *sline; + int lexraw_mark; Histent ehist; size_t buflen; + /* + * If accumulating raw input for use in command substitution, + * we don't want the history text, so mark it for later removal. + * It would be better to do this at a level above the history + * and below the lexer --- but there isn't one. + * + * Include the character we are attempting to substitute. + */ + lexraw_mark = zshlex_raw_mark(-1); + /* look, no goto's */ if (isfirstch && c == hatchar) { int gbal = 0; @@ -721,7 +842,7 @@ histsubchar(int c) noerrs = 1; parse_subst_string(sline); noerrs = one; - errflag = oef; + errflag = oef | (errflag & ERRFLAG_INT); remnulargs(sline); untokenize(sline); } @@ -751,6 +872,8 @@ histsubchar(int c) } } + zshlex_raw_back_to_mark(lexraw_mark); + /* * Push the expanded value onto the input stack, * marking this as a history word for purposes of the alias stack. @@ -789,11 +912,16 @@ ihungetc(int c) zlemetall--; exlast++; } - DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); - hptr--; - DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); - qbang = (c == bangchar && stophist < 2 && - hptr > chline && hptr[-1] == '\\'); + if ((inbufflags & (INP_ALIAS|INP_HIST)) != INP_ALIAS) { + DPUTS(hptr <= chline, "BUG: hungetc attempted at buffer start"); + hptr--; + DPUTS(*hptr != (char) c, "BUG: wrong character in hungetc() "); + qbang = (c == bangchar && stophist < 2 && + hptr > chline && hptr[-1] == '\\'); + } else { + /* No active bangs in aliases */ + qbang = 0; + } if (doit) inungetc(c); if (!qbang) @@ -813,6 +941,11 @@ strinbeg(int dohist) strin++; hbegin(dohist); lexinit(); + /* + * Also initialise some variables owned by the parser but + * used for communication between the parser and lexer. + */ + init_parse_status(); } /* done reading a string */ @@ -880,7 +1013,8 @@ hbegin(int dohist) char *hf; isfirstln = isfirstch = 1; - errflag = histdone = 0; + errflag &= ~ERRFLAG_ERROR; + histdone = 0; if (!dohist) stophist = 2; else if (dohist != 2) @@ -1110,8 +1244,11 @@ static void putoldhistentryontop(short keep_going) { static Histent next = NULL; - Histent he = keep_going? next : hist_ring->down; - next = he->down; + Histent he = (keep_going || !hist_ring) ? next : hist_ring->down; + if (he) + next = he->down; + else + return; if (isset(HISTEXPIREDUPSFIRST) && !(he->node.flags & HIST_DUP)) { static zlong max_unique_ct = 0; if (!keep_going) @@ -1151,7 +1288,7 @@ prepnexthistent(void) freehistnode(&hist_ring->node); } - if (histlinect < histsiz) { + if (histlinect < histsiz || !hist_ring) { he = (Histent)zshcalloc(sizeof *he); if (!hist_ring) hist_ring = he->up = he->down = he; @@ -1395,28 +1532,17 @@ hend(Eprog prog) return !(flag & HISTFLAG_NOEXEC || errflag); } -/* Gives current expansion word if not last word before chwordpos. */ - -/**/ -int hwgetword = -1; - /* begin a word */ /**/ void ihwbegin(int offset) { - if (stophist == 2) + if (stophist == 2 || (histactive & HA_INWORD) || + (inbufflags & (INP_ALIAS|INP_HIST)) == INP_ALIAS) return; if (chwordpos%2) chwordpos--; /* make sure we're on a word start, not end */ - /* If we're expanding an alias, we should overwrite the expansion - * in the history. - */ - if ((inbufflags & INP_ALIAS) && !(inbufflags & INP_HIST)) - hwgetword = chwordpos; - else - hwgetword = -1; chwords[chwordpos++] = hptr - chline + offset; } @@ -1426,7 +1552,8 @@ ihwbegin(int offset) void ihwend(void) { - if (stophist == 2) + if (stophist == 2 || (histactive & HA_INWORD) || + (inbufflags & (INP_ALIAS|INP_HIST)) == INP_ALIAS) return; if (chwordpos%2 && chline) { /* end of word reached and we've already begun a word */ @@ -1437,13 +1564,6 @@ ihwend(void) (chwordlen += 32) * sizeof(short)); } - 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 */ - hptr = chline + chwords[chwordpos ? chwordpos - 1 : 0]; - } } else { /* scrub that last word, it doesn't exist */ chwordpos--; @@ -1467,17 +1587,17 @@ histbackword(void) static void hwget(char **startptr) { - int pos = hwgetword > -1 ? hwgetword : chwordpos - 2; + int pos = chwordpos - 2; #ifdef DEBUG /* debugging only */ - if (hwgetword == -1 && !chwordpos) { + if (!chwordpos) { /* no words available */ DPUTS(1, "BUG: hwget() called with no words"); *startptr = ""; return; - } - else if (hwgetword == -1 && chwordpos%2) { + } + else if (chwordpos%2) { DPUTS(1, "BUG: hwget() called in middle of word"); *startptr = ""; return; @@ -1499,9 +1619,9 @@ hwrep(char *rep) if (!strcmp(rep, start)) return; - + hptr = start; - chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2; + chwordpos = chwordpos - 2; hwbegin(0); qbang = 1; while (*rep) @@ -1529,7 +1649,6 @@ hgetline(void) /* reset line */ hptr = chline; chwordpos = 0; - hwgetword = -1; return ret; } @@ -1699,11 +1818,12 @@ int chrealpath(char **junkptr) { char *str; -#ifdef HAVE_CANONICALIZE_FILE_NAME +#ifdef HAVE_REALPATH +# ifdef REALPATH_ACCEPTS_NULL char *lastpos, *nonreal, *real; -#else -# ifdef HAVE_REALPATH - char *lastpos, *nonreal, real[PATH_MAX]; +# else + char *lastpos, *nonreal, pathbuf[PATH_MAX]; + char *real = pathbuf; # endif #endif @@ -1714,7 +1834,7 @@ chrealpath(char **junkptr) if (!chabspath(junkptr)) return 0; -#if !defined(HAVE_REALPATH) && !defined(HAVE_CANONICALIZE_FILE_NAME) +#ifndef HAVE_REALPATH return 1; #else /* @@ -1730,29 +1850,21 @@ chrealpath(char **junkptr) nonreal = lastpos + 1; while (! -#ifdef HAVE_CANONICALIZE_FILE_NAME - /* - * This is a GNU extension to realpath(); it's the - * same as calling realpath() with a NULL second argument - * which uses malloc() to get memory. The alternative - * interface is easier to test for, however. - */ - (real = canonicalize_file_name(*junkptr)) +#ifdef REALPATH_ACCEPTS_NULL + /* realpath() with a NULL second argument uses malloc() to get + * memory so we don't need to worry about overflowing PATH_MAX */ + (real = realpath(*junkptr, NULL)) #else realpath(*junkptr, real) #endif ) { - if (errno == EINVAL || errno == ELOOP || - errno == ENAMETOOLONG || errno == ENOMEM) + if (errno == EINVAL || errno == ENOMEM) return 0; -#ifdef HAVE_CANONICALIZE_FILE_NAME - if (!real) - return 0; -#endif - if (nonreal == *junkptr) { - *real = '\0'; +#ifndef REALPATH_ACCEPTS_NULL + real = NULL; +#endif break; } @@ -1768,11 +1880,15 @@ chrealpath(char **junkptr) str++; } - *junkptr = metafy(str = bicat(real, nonreal), -1, META_HEAPDUP); - zsfree(str); -#ifdef HAVE_CANONICALIZE_FILE_NAME - free(real); + if (real) { + *junkptr = metafy(str = bicat(real, nonreal), -1, META_HEAPDUP); + zsfree(str); +#ifdef REALPATH_ACCEPTS_NULL + free(real); #endif + } else { + *junkptr = metafy(nonreal, lastpos - nonreal + 1, META_HEAPDUP); + } #endif return 1; @@ -2139,10 +2255,10 @@ getargs(Histent elist, int arg1, int arg2) } /**/ -int +static int quote(char **tr) { - char *ptr, *rptr, **str = (char **)tr; + char *ptr, *rptr, **str = tr; int len = 3; int inquotes = 0; @@ -2176,7 +2292,6 @@ quote(char **tr) *rptr++ = *ptr; *rptr++ = '\''; *rptr++ = 0; - str[1] = NULL; return 0; } @@ -2184,7 +2299,7 @@ quote(char **tr) static int quotebreak(char **tr) { - char *ptr, *rptr, **str = (char **)tr; + char *ptr, *rptr, **str = tr; int len = 3; for (ptr = *str; *ptr; ptr++, len++) @@ -2378,12 +2493,40 @@ readhistfile(char *fn, int err, int readflags) || (hist_ignore_all_dups && newflags & hist_skip_flags)) newflags |= HIST_MAKEUNIQUE; while (fpos = ftell(in), (l = readhistline(0, &buf, &bufsiz, in))) { - char *pt = buf; + char *pt; + int remeta = 0; if (l < 0) { zerr("corrupt history file %s", fn); break; } + + /* + * Handle the special case that we're reading from an + * old shell with fewer meta characters, so we need to + * metafy some more. (It's not clear why the history + * file is metafied at all; some would say this is plain + * stupid. But we're stuck with it now without some + * hairy workarounds for compatibility). + * + * This is rare so doesn't need to be that efficient; just + * allocate space off the heap. + */ + for (pt = buf; *pt; pt++) { + if (*pt == Meta && pt[1]) + pt++; + else if (imeta(*pt)) { + remeta = 1; + break; + } + } + if (remeta) { + unmetafy(buf, &remeta); + pt = metafy(buf, remeta, META_USEHEAP); + } else { + pt = buf; + } + if (*pt == ':') { pt++; stim = zstrtol(pt, NULL, 0); @@ -2443,8 +2586,6 @@ readhistfile(char *fn, int err, int readflags) start = pt; uselex = isset(HISTLEXWORDS) && !(readflags & HFILE_FAST); histsplitwords(pt, &words, &nwords, &nwordpos, uselex); - if (uselex) - freeheap(); he->nwords = nwordpos/2; if (he->nwords) { @@ -2457,6 +2598,14 @@ readhistfile(char *fn, int err, int readflags) freehistnode(&he->node); curhist--; } + /* + * Do this last out of paranoia in case use of + * heap is disguised... + */ + if (uselex || remeta) + freeheap(); + if (errflag & ERRFLAG_INT) + break; } if (start && readflags & HFILE_USE_OPTIONS) { zsfree(lasthist.text); @@ -2488,7 +2637,8 @@ static int flockhistfile(char *fn, int keep_trying) { struct flock lck; - int ctr = keep_trying ? 9 : 0; + long sleep_us = 0x10000; /* about 67 ms */ + time_t end_time; if (flock_fd >= 0) return 0; /* already locked */ @@ -2501,13 +2651,22 @@ flockhistfile(char *fn, int keep_trying) lck.l_start = 0; lck.l_len = 0; /* lock the whole file */ + /* + * Timeout is ten seconds. + */ + end_time = time(NULL) + (time_t)10; while (fcntl(flock_fd, F_SETLKW, &lck) == -1) { - if (--ctr < 0) { + if (!keep_trying || time(NULL) >= end_time || + /* + * Randomise wait to minimise clashes with shells exiting at + * the same time. + */ + !zsleep_random(sleep_us, end_time)) { close(flock_fd); flock_fd = -1; return 1; } - sleep(1); + sleep_us <<= 1; } return 0; @@ -2743,7 +2902,7 @@ savehistfile(char *fn, int err, int writeflags) static int lockhistct; static int -checklocktime(char *lockfile, time_t then) +checklocktime(char *lockfile, long *sleep_usp, time_t then) { time_t now = time(NULL); @@ -2753,9 +2912,19 @@ checklocktime(char *lockfile, time_t then) return -1; } - if (now - then < 10) - sleep(1); - else + if (now - then < 10) { + /* + * To give the effect of a gradually increasing backoff, + * we'll sleep a period based on the time we've spent so far. + */ + DPUTS(now < then, "time flowing backwards through history"); + /* + * Randomise to minimise clashes with shells exiting at the same + * time. + */ + (void)zsleep_random(*sleep_usp, then + 10); + *sleep_usp <<= 1; + } else unlink(lockfile); return 0; @@ -2772,6 +2941,7 @@ lockhistfile(char *fn, int keep_trying) { int ct = lockhistct; int ret = 0; + long sleep_us = 0x10000; /* about 67 ms */ if (!fn && !(fn = getsparam("HISTFILE"))) return 1; @@ -2799,7 +2969,8 @@ lockhistfile(char *fn, int keep_trying) #ifdef HAVE_LINK # ifdef HAVE_SYMLINK sprintf(pidbuf, "/pid-%ld/host-", (long)mypid); - lnk = bicat(pidbuf, getsparam("HOST")); + lnk = getsparam("HOST"); + lnk = bicat(pidbuf, lnk ? lnk : ""); /* We'll abuse fd as our success flag. */ while ((fd = symlink(lnk, lockfile)) < 0) { if (errno != EEXIST) { @@ -2814,7 +2985,7 @@ lockhistfile(char *fn, int keep_trying) continue; break; } - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2842,7 +3013,7 @@ lockhistfile(char *fn, int keep_trying) continue; ret = 2; } else { - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2870,7 +3041,7 @@ lockhistfile(char *fn, int keep_trying) ret = 2; break; } - if (checklocktime(lockfile, sb.st_mtime) < 0) { + if (checklocktime(lockfile, &sleep_us, sb.st_mtime) < 0) { ret = 1; break; } @@ -2988,7 +3159,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) opts[RCQUOTES] = 0; addedx = 0; noerrs = 1; - lexsave(); + zcontext_save(); lexflags = flags | LEXFLAGS_ACTIVE; /* * Are we handling comments? @@ -3108,7 +3279,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) * double quotes. Whitespace in the middle is * similarly retained, so just add the parentheses back. */ - p = tricat("((", tokstr, "))"); + p = zhtricat("((", tokstr, "))"); } break; @@ -3162,7 +3333,7 @@ bufferwords(LinkList list, char *buf, int *index, int flags) got = 1; cur = num - 1; } - } while (tok != ENDINPUT && tok != LEXERR); + } while (tok != ENDINPUT && tok != LEXERR && !(errflag & ERRFLAG_INT)); if (buf && tok == LEXERR && tokstr && *tokstr) { int plen; untokenize((p = dupstring(tokstr))); @@ -3182,10 +3353,10 @@ bufferwords(LinkList list, char *buf, int *index, int flags) noaliases = ona; strinend(); inpop(); - errflag = 0; + errflag &= ~ERRFLAG_ERROR; nocomments = onc; noerrs = ne; - lexrestore(); + zcontext_restore(); zlemetacs = ocs; zlemetall = oll; wb = owb; @@ -3233,11 +3404,14 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, char *start = lineptr; if (uselex) { - LinkList wordlist = bufferwords(NULL, lineptr, NULL, - LEXFLAGS_COMMENTS_KEEP); + LinkList wordlist; LinkNode wordnode; int nwords_max; + wordlist = bufferwords(NULL, lineptr, NULL, + LEXFLAGS_COMMENTS_KEEP); + if (errflag) + return; nwords_max = 2 * countlinknodes(wordlist); if (nwords_max > nwords) { *nwordsp = nwords = nwords_max; @@ -3329,7 +3503,8 @@ histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp, if (*lptr == *wptr || (*lptr == '!' && *wptr == '|')) { lptr++; - wptr++; + if (!*++wptr) + break; } else if (lptr[0] == '\\' && lptr[1] == '\n') { /* |