diff -r 000000000000 -r 2e3d3ce01487 openenvutils/commandshell/shell/src/hist.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openenvutils/commandshell/shell/src/hist.c Tue Feb 02 10:12:00 2010 +0200 @@ -0,0 +1,2479 @@ +//hist.c - history expansion +// +// © Portions Copyright (c) Symbian Software Ltd 2007. All rights reserved. +// +/* + * This file is part of zsh, the Z shell. + * + * Copyright (c) 1992-1997 Paul Falstad + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Paul Falstad or the Zsh Development Group be liable + * to any party for direct, indirect, special, incidental, or consequential + * damages arising out of the use of this software and its documentation, + * even if Paul Falstad and the Zsh Development Group have been advised of + * the possibility of such damage. + * + * Paul Falstad and the Zsh Development Group specifically disclaim any + * warranties, including, but not limited to, the implied warranties of + * merchantability and fitness for a particular purpose. The software + * provided hereunder is on an "as is" basis, and Paul Falstad and the + * Zsh Development Group have no obligation to provide maintenance, + * support, updates, enhancements, or modifications. + * + */ + +#include "zsh.mdh" +#include "hist.pro" + +#ifdef __SYMBIAN32__ +#ifdef __WINSCW__ +#pragma warn_unusedarg off +#pragma warn_possunwant off +#endif//__WINSCW__ +#endif//__SYMBIAN32__ + + +/* Functions to call for getting/ungetting a character and for history + * word control. */ + +/**/ +mod_export int (*hgetc) _((void)); + +/**/ +void (*hungetc) _((int)); + +/**/ +void (*hwaddc) _((int)); + +/**/ +void (*hwbegin) _((int)); + +/**/ +void (*hwend) _((void)); + +/**/ +void (*addtoline) _((int)); + +/* != 0 means history substitution is turned off */ + +/**/ +mod_export int stophist; + +/* if != 0, we are expanding the current line */ + +/**/ +mod_export int expanding; + +/* these are used to modify the cursor position during expansion */ + +/**/ +mod_export int excs, exlast; + +/* + * Current history event number + * + * Note on curhist: with history inactive, this points to the + * last line actually added to the history list. With history active, + * the line does not get added to the list until hend(), if at all. + * However, curhist is incremented to reflect the current line anyway + * and a temporary history entry is inserted while the user is editing. + * If the resulting line was not added to the list, a flag is set so + * that curhist will be decremented in hbegin(). + */ + +/**/ +mod_export zlong curhist; + +/**/ +struct histent curline; + +/* current line count of allocated history entries */ + +/**/ +zlong histlinect; + +/* The history lines are kept in a hash, and also doubly-linked in a ring */ + +/**/ +HashTable histtab; +/**/ +mod_export Histent hist_ring; + +/* capacity of history lists */ + +/**/ +zlong histsiz; + +/* desired history-file size (in lines) */ + +/**/ +zlong savehistsiz; + +/* if = 1, we have performed history substitution on the current line * + * if = 2, we have used the 'p' modifier */ + +/**/ +int histdone; + +/* state of the history mechanism */ + +/**/ +int histactive; + +/* Current setting of the associated option, but sometimes also includes + * the setting of the HIST_SAVE_NO_DUPS option. */ + +/**/ +int hist_ignore_all_dups; + +/* What flags (if any) we should skip when moving through the history */ + +/**/ +mod_export int hist_skip_flags; + +/* Bits of histactive variable */ +#define HA_ACTIVE (1<<0) /* History mechanism is active */ +#define HA_NOSTORE (1<<1) /* Don't store the line when finished */ +#define HA_NOINC (1<<2) /* Don't store, curhist not incremented */ + +/* Array of word beginnings and endings in current history line. */ + +/**/ +short *chwords; + +/* Max, actual position in chwords. + * nwords = chwordpos/2 because we record beginning and end of words. + */ + +/**/ +int chwordlen, chwordpos; + +/* the last l for s/l/r/ history substitution */ + +/**/ +char *hsubl; + +/* the last r for s/l/r/ history substitution */ + +/**/ +char *hsubr; + +/* pointer into the history line */ + +/**/ +mod_export char *hptr; + +/* the current history line */ + +/**/ +mod_export char *chline; + +/* true if the last character returned by hgetc was an escaped bangchar * + * if it is set and NOBANGHIST is unset hwaddc escapes bangchars */ + +/**/ +int qbang; + +/* max size of histline */ + +/**/ +int hlinesz; + +/* default event (usually curhist-1, that is, "!!") */ + +static zlong defev; + +/* 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)) { + /* 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 * + * 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. */ + hwaddc('\\'); + *hptr++ = c; + + /* Resize history line if necessary */ + if (hptr - chline >= hlinesz) { + int oldsiz = hlinesz; + + chline = realloc(chline, hlinesz = oldsiz + 64); + hptr = chline + oldsiz; + } + } +} + +/* This function adds a character to the zle input line. It is used when * + * zsh expands history (see doexpandhist() in zle_tricky.c). It also * + * calculates the new cursor position after the expansion. It is called * + * from hgetc() and from gettok() in lex.c for characters in comments. */ + +/**/ +void +iaddtoline(int c) +{ + if (!expanding || lexstop) + return; + if (qbang && c == bangchar && stophist < 2) { + exlast--; + spaceinline(1); + line[cs++] = '\\'; + } + if (excs > cs) { + excs += 1 + inbufct - exlast; + if (excs < cs) + /* this case could be handled better but it is * + * so rare that it does not worth it */ + excs = cs; + } + exlast = inbufct; + spaceinline(1); + line[cs++] = itok(c) ? ztokens[c - Pound] : c; +} + +static int +ihgetc(void) +{ + int c = ingetc(); + + qbang = 0; + if (!stophist && !(inbufflags & INP_ALIAS)) { + /* If necessary, expand history characters. */ + c = histsubchar(c); + if (c < 0) { + /* bad expansion */ + errflag = lexstop = 1; + return ' '; + } + } + if ((inbufflags & INP_HIST) && !stophist) { + /* the current character c came from a history expansion * + * (inbufflags & INP_HIST) and history is not disabled * + * (e.g. we are not inside single quotes). In that case, \! * + * should be treated as ! (since this \! came from a previous * + * history line where \ was used to escape the bang). So if * + * c == '\\' we fetch one more character to see if it's a bang, * + * and if it is not, we unget it and reset c back to '\\' */ + qbang = 0; + if (c == '\\' && !(qbang = (c = ingetc()) == bangchar)) + safeinungetc(c), c = '\\'; + } else if (stophist || (inbufflags & INP_ALIAS)) + /* If the result is a bangchar which came from history or alias * + * expansion, we treat it as an escaped bangchar, unless history * + * is disabled. If stophist == 1 it only means that history is * + * temporarily disabled by a !" which won't appear in in the * + * history, so we still have an escaped bang. stophist > 1 if * + * history is disabled with NOBANGHIST or by someone else (e.g. * + * when the lexer scans single quoted text). */ + qbang = c == bangchar && (stophist < 2); + hwaddc(c); + addtoline(c); + + return c; +} + +/**/ +static void +safeinungetc(int c) +{ + if (lexstop) + lexstop = 0; + else + inungetc(c); +} + +/**/ +void +herrflush(void) +{ + inpopalias(); + + while (!lexstop && inbufct && !strin) + hwaddc(ingetc()); +} + +/* extract :s/foo/bar/ delimiters and arguments */ + +/**/ +static int +getsubsargs(char *subline) +{ + int del; + char *ptr1, *ptr2; + + del = ingetc(); + ptr1 = hdynread2(del); + if (!ptr1) + return 1; + ptr2 = hdynread2(del); + if (strlen(ptr1)) { + zsfree(hsubl); + hsubl = ptr1; + } + zsfree(hsubr); + hsubr = ptr2; + if (hsubl && !strstr(subline, hsubl)) { + herrflush(); + zerr("substitution failed", NULL, 0); + return 1; + } + return 0; +} + +/* Get the maximum no. of words for a history entry. */ + +/**/ +static int +getargc(Histent ehist) +{ + return ehist->nwords ? ehist->nwords-1 : 0; +} + +/* Perform history substitution, returning the next character afterwards. */ + +/**/ +static int +histsubchar(int c) +{ + int farg, evset = -1, larg, argc, cflag = 0, bflag = 0; + zlong ev; + static int marg = -1; + static zlong mev = -1; + char buf[256], *ptr; + char *sline; + Histent ehist; + + /* look, no goto's */ + if (isfirstch && c == hatchar) { + /* Line begins ^foo^bar */ + isfirstch = 0; + inungetc(hatchar); + if (!(ehist = gethist(defev)) + || !(sline = getargs(ehist, 0, getargc(ehist))) + || getsubsargs(sline) || !hsubl) + return -1; + subst(&sline, hsubl, hsubr, 0); + } else { + /* Line doesn't begin ^foo^bar */ + if (c != ' ') + isfirstch = 0; +#ifndef __SYMBIAN32__ + if (c == '\\') { + int g = ingetc(); + + if (g != bangchar) + safeinungetc(g); + else { + qbang = 1; + return bangchar; + } + } +#endif //__SYMBIAN32__ + if (c != bangchar) + return c; + *hptr = '\0'; + if ((c = ingetc()) == '{') { + bflag = cflag = 1; + c = ingetc(); + } + if (c == '\"') { + stophist = 1; + return ingetc(); + } + if ((!cflag && inblank(c)) || c == '=' || c == '(' || lexstop) { + safeinungetc(c); + return bangchar; + } + cflag = 0; + ptr = buf; + + /* get event number */ + + queue_signals(); + if (c == '?') { + for (;;) { + c = ingetc(); + if (c == '?' || c == '\n' || lexstop) + break; + else + *ptr++ = c; + } + if (c != '\n' && !lexstop) + c = ingetc(); + *ptr = '\0'; + mev = ev = hconsearch(hsubl = ztrdup(buf), &marg); + evset = 0; + if (ev == -1) { + herrflush(); + unqueue_signals(); + zerr("no such event: %s", buf, 0); + return -1; + } + } else { + zlong t0; + + for (;;) { + if (inblank(c) || c == ';' || c == ':' || c == '^' || + c == '$' || c == '*' || c == '%' || c == '}' || + c == '\'' || c == '"' || c == '`' || lexstop) + break; + if (ptr != buf) { + if (c == '-') + break; + if ((idigit(buf[0]) || buf[0] == '-') && !idigit(c)) + break; + } + *ptr++ = c; + if (c == '#' || c == bangchar) { + c = ingetc(); + break; + } + c = ingetc(); + } + *ptr = 0; + if (!*buf) { + if (c != '%') { + if (isset(CSHJUNKIEHISTORY)) + ev = addhistnum(curhist,-1,HIST_FOREIGN); + else + ev = defev; + if (c == ':' && evset == -1) + evset = 0; + else + evset = 1; + } else { + if (marg != -1) + ev = mev; + else + ev = defev; + evset = 0; + } + } else if ((t0 = zstrtol(buf, NULL, 10))) { + ev = (t0 < 0) ? addhistnum(curhist,t0,HIST_FOREIGN) : t0; + evset = 1; + } else if ((unsigned)*buf == bangchar) { + ev = addhistnum(curhist,-1,HIST_FOREIGN); + evset = 1; + } else if (*buf == '#') { + ev = curhist; + evset = 1; + } else if ((ev = hcomsearch(buf)) == -1) { + herrflush(); + unqueue_signals(); + zerr("event not found: %s", buf, 0); + return -1; + } else + evset = 1; + } + + /* get the event */ + + if (!(ehist = gethist(defev = ev))) { + unqueue_signals(); + return -1; + } + /* extract the relevant arguments */ + + argc = getargc(ehist); + if (c == ':') { + cflag = 1; + c = ingetc(); + if (c == '%' && marg != -1) { + if (!evset) { + ehist = gethist(defev = mev); + argc = getargc(ehist); + } else { + herrflush(); + unqueue_signals(); + zerr("Ambiguous history reference", NULL, 0); + return -1; + } + + } + } + if (c == '*') { + farg = 1; + larg = argc; + cflag = 0; + } else { + inungetc(c); + larg = farg = getargspec(argc, marg, evset); + if (larg == -2) { + unqueue_signals(); + return -1; + } + if (farg != -1) + cflag = 0; + c = ingetc(); + if (c == '*') { + cflag = 0; + larg = argc; + } else if (c == '-') { + cflag = 0; + larg = getargspec(argc, marg, evset); + if (larg == -2) { + unqueue_signals(); + return -1; + } + if (larg == -1) + larg = argc - 1; + } else + inungetc(c); + } + if (farg == -1) + farg = 0; + if (larg == -1) + larg = argc; + if (!(sline = getargs(ehist, farg, larg))) { + unqueue_signals(); + return -1; + } + unqueue_signals(); + } + + /* do the modifiers */ + + for (;;) { + c = (cflag) ? ':' : ingetc(); + cflag = 0; + if (c == ':') { + int gbal = 0; + + if ((c = ingetc()) == 'g') { + gbal = 1; + c = ingetc(); + } + switch (c) { + case 'p': + histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC; + break; + case 'h': + if (!remtpath(&sline)) { + herrflush(); + zerr("modifier failed: h", NULL, 0); + return -1; + } + break; + case 'e': + if (!rembutext(&sline)) { + herrflush(); + zerr("modifier failed: e", NULL, 0); + return -1; + } + break; + case 'r': + if (!remtext(&sline)) { + herrflush(); + zerr("modifier failed: r", NULL, 0); + return -1; + } + break; + case 't': + if (!remlpaths(&sline)) { + herrflush(); + zerr("modifier failed: t", NULL, 0); + return -1; + } + break; + case 's': + if (getsubsargs(sline)) + return -1; /* fall through */ + case '&': + if (hsubl && hsubr) + subst(&sline, hsubl, hsubr, gbal); + else { + herrflush(); + zerr("no previous substitution", NULL, 0); + return -1; + } + break; + case 'q': + quote(&sline); + break; + case 'Q': + { + int one = noerrs, oef = errflag; + + noerrs = 1; + parse_subst_string(sline); + noerrs = one; + errflag = oef; + remnulargs(sline); + untokenize(sline); + } + break; + case 'x': + quotebreak(&sline); + break; + case 'l': + downcase(&sline); + break; + case 'u': + upcase(&sline); + break; + default: + herrflush(); + zerr("illegal modifier: %c", NULL, c); + return -1; + } + } else { + if (c != '}' || !bflag) + inungetc(c); + if (c != '}' && bflag) { + zerr("'}' expected", NULL, 0); + return -1; + } + break; + } + } + + /* + * Push the expanded value onto the input stack, + * marking this as a history word for purposes of the alias stack. + */ + + lexstop = 0; + /* this function is called only called from hgetc and only if * + * !(inbufflags & INP_ALIAS). History expansion should never be * + * done with INP_ALIAS (to prevent recursive history expansion and * + * histoty expansion of aliases). Escapes are not removed here. * + * This is now handled in hgetc. */ + inpush(sline, INP_HIST, NULL); /* sline from heap, don't free */ + histdone |= HISTFLAG_DONE; + if (isset(HISTVERIFY)) + histdone |= HISTFLAG_NOEXEC | HISTFLAG_RECALL; + + /* Don't try and re-expand line. */ + return ingetc(); +} + +/* unget a char and remove it from chline. It can only be used * + * to unget a character returned by hgetc. */ + +static void +ihungetc(int c) +{ + int doit = 1; + + while (!lexstop && !errflag) { + if (hptr[-1] != (char) c && stophist < 4 && + hptr > chline + 1 && hptr[-1] == '\n' && hptr[-2] == '\\') + hungetc('\n'), hungetc('\\'); + + if (expanding) { + cs--; + ll--; + 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 (doit) + inungetc(c); + if (!qbang) + return; + doit = !stophist && ((inbufflags & INP_HIST) || + !(inbufflags & INP_ALIAS)); + c = '\\'; + } +} + +/* begin reading a string */ + +/**/ +mod_export void +strinbeg(int dohist) +{ + strin++; + hbegin(dohist); + lexinit(); +} + +/* done reading a string */ + +/**/ +mod_export void +strinend(void) +{ + hend(NULL); + DPUTS(!strin, "BUG: strinend() called without strinbeg()"); + strin--; + isfirstch = 1; + histdone = 0; +} + +/* dummy functions to use instead of hwaddc(), hwbegin(), and hwend() when + * they aren't needed */ + +static void +nohw(UNUSED(int c)) +{ +} + +static void +nohwe(void) +{ +} + +/* these functions handle adding/removing curline to/from the hist_ring */ + +static void +linkcurline(void) +{ + if (!hist_ring) + hist_ring = curline.up = curline.down = &curline; + else { + curline.up = hist_ring; + curline.down = hist_ring->down; + hist_ring->down = hist_ring->down->up = &curline; + hist_ring = &curline; + } + curline.histnum = ++curhist; +} + +static void +unlinkcurline(void) +{ + curline.up->down = curline.down; + curline.down->up = curline.up; + if (hist_ring == &curline) { + if (!histlinect) + hist_ring = NULL; + else + hist_ring = curline.up; + } + curhist--; +} + +/* initialize the history mechanism */ + +/**/ +mod_export void +hbegin(int dohist) +{ + isfirstln = isfirstch = 1; + errflag = histdone = 0; + if (!dohist) + stophist = 2; + else if (dohist != 2) + stophist = (!interact || unset(SHINSTDIN)) ? 2 : 0; + else + stophist = 0; + if (stophist == 2 || (inbufflags & INP_ALIAS)) { + chline = hptr = NULL; + hlinesz = 0; + chwords = NULL; + chwordlen = 0; + hgetc = ingetc; + hungetc = inungetc; + hwaddc = nohw; + hwbegin = nohw; + hwend = nohwe; + addtoline = nohw; + } else { + chline = hptr = zshcalloc(hlinesz = 64); + chwords = zalloc((chwordlen = 64) * sizeof(short)); + hgetc = ihgetc; + hungetc = ihungetc; + hwaddc = ihwaddc; + hwbegin = ihwbegin; + hwend = ihwend; + addtoline = iaddtoline; + if (!isset(BANGHIST)) + stophist = 4; + } + chwordpos = 0; + + if (hist_ring && !hist_ring->ftim) + hist_ring->ftim = time(NULL); + if ((dohist == 2 || (interact && isset(SHINSTDIN))) && !strin) { + histactive = HA_ACTIVE; + attachtty(mypgrp); + linkcurline(); + defev = addhistnum(curhist, -1, HIST_FOREIGN); + } else + histactive = HA_ACTIVE | HA_NOINC; +} + +/**/ +void +histreduceblanks(void) +{ + int i, len, pos, needblank, spacecount = 0; + + if (isset(HISTIGNORESPACE)) + while (chline[spacecount] == ' ') spacecount++; + + for (i = 0, len = spacecount; i < chwordpos; i += 2) { + len += chwords[i+1] - chwords[i] + + (i > 0 && chwords[i] > chwords[i-1]); + } + if (chline[len] == '\0') + return; + + for (i = 0, pos = spacecount; i < chwordpos; i += 2) { + len = chwords[i+1] - chwords[i]; + needblank = (i < chwordpos-2 && chwords[i+2] > chwords[i+1]); + if (pos != chwords[i]) { + memcpy(chline + pos, chline + chwords[i], len + needblank); + chwords[i] = pos; + chwords[i+1] = chwords[i] + len; + } + pos += len + needblank; + } + chline[pos] = '\0'; +} + +/**/ +void +histremovedups(void) +{ + Histent he, next; + for (he = hist_ring; he; he = next) { + next = up_histent(he); + if (he->flags & HIST_DUP) + freehistnode((HashNode)he); + } +} + +/**/ +mod_export zlong +addhistnum(zlong hl, int n, int xflags) +{ + int dir = n < 0? -1 : n > 0? 1 : 0; + Histent he = gethistent(hl, dir); + + if (!he) + return 0; + if (he->histnum != hl) + n -= dir; + if (n) + he = movehistent(he, n, xflags); + if (!he) + return dir < 0? firsthist() - 1 : curhist + 1; + return he->histnum; +} + +/**/ +mod_export Histent +movehistent(Histent he, int n, int xflags) +{ + while (n < 0) { + if (!(he = up_histent(he))) + return NULL; + if (!(he->flags & xflags)) + n++; + } + while (n > 0) { + if (!(he = down_histent(he))) + return NULL; + if (!(he->flags & xflags)) + n--; + } + checkcurline(he); + return he; +} + +/**/ +mod_export Histent +up_histent(Histent he) +{ + return he->up == hist_ring? NULL : he->up; +} + +/**/ +mod_export Histent +down_histent(Histent he) +{ + return he == hist_ring? NULL : he->down; +} + +/**/ +mod_export Histent +gethistent(zlong ev, int nearmatch) +{ + Histent he; + + if (!hist_ring) + return NULL; + + if (ev - hist_ring->down->histnum < hist_ring->histnum - ev) { + for (he = hist_ring->down; he->histnum < ev; he = he->down) ; + if (he->histnum != ev) { + if (nearmatch == 0 + || (nearmatch < 0 && (he = up_histent(he)) == NULL)) + return NULL; + } + } + else { + for (he = hist_ring; he->histnum > ev; he = he->up) ; + if (he->histnum != ev) { + if (nearmatch == 0 + || (nearmatch > 0 && (he = down_histent(he)) == NULL)) + return NULL; + } + } + + checkcurline(he); + return he; +} + +static void +putoldhistentryontop(short keep_going) +{ + static Histent next = NULL; + Histent he = keep_going? next : hist_ring->down; + next = he->down; + if (isset(HISTEXPIREDUPSFIRST) && !(he->flags & HIST_DUP)) { + static zlong max_unique_ct = 0; + if (!keep_going) + max_unique_ct = savehistsiz; + do { + if (max_unique_ct-- <= 0 || he == hist_ring) { + max_unique_ct = 0; + he = hist_ring->down; + next = hist_ring; + break; + } + he = next; + next = he->down; + } while (!(he->flags & HIST_DUP)); + } + if (he != hist_ring->down) { + he->up->down = he->down; + he->down->up = he->up; + he->up = hist_ring; + he->down = hist_ring->down; + hist_ring->down = he->down->up = he; + } + hist_ring = he; +} + +/**/ +Histent +prepnexthistent(void) +{ + Histent he; + int curline_in_ring = hist_ring == &curline; + + if (curline_in_ring) + unlinkcurline(); + if (hist_ring && hist_ring->flags & HIST_TMPSTORE) { + curhist--; + freehistnode((HashNode)hist_ring); + } + + if (histlinect < histsiz) { + he = (Histent)zshcalloc(sizeof *he); + if (!hist_ring) + hist_ring = he->up = he->down = he; + else { + he->up = hist_ring; + he->down = hist_ring->down; + hist_ring->down = he->down->up = he; + hist_ring = he; + } + histlinect++; + } + else { + putoldhistentryontop(0); + freehistdata(hist_ring, 0); + he = hist_ring; + } + he->histnum = ++curhist; + if (curline_in_ring) + linkcurline(); + return he; +} + +/* A helper function for hend() */ + +static int +should_ignore_line(Eprog prog) +{ + if (isset(HISTIGNORESPACE)) { + if (*chline == ' ' || aliasspaceflag) + return 1; + } + + if (!prog) + return 0; + + if (isset(HISTNOFUNCTIONS)) { + Wordcode pc = prog->prog; + wordcode code = *pc; + if (wc_code(code) == WC_LIST && WC_LIST_TYPE(code) & Z_SIMPLE + && wc_code(pc[2]) == WC_FUNCDEF) + return 1; + } + + if (isset(HISTNOSTORE)) { + char *b = getjobtext(prog, NULL); + int saw_builtin; + if (*b == 'b' && strncmp(b,"builtin ",8) == 0) { + b += 8; + saw_builtin = 1; + } else + saw_builtin = 0; + if (*b == 'h' && strncmp(b,"history",7) == 0 && (!b[7] || b[7] == ' ') + && (saw_builtin || !shfunctab->getnode(shfunctab,"history"))) + return 1; + if (*b == 'r' && (!b[1] || b[1] == ' ') + && (saw_builtin || !shfunctab->getnode(shfunctab,"r"))) + return 1; + if (*b == 'f' && b[1] == 'c' && b[2] == ' ' && b[3] == '-' + && (saw_builtin || !shfunctab->getnode(shfunctab,"fc"))) { + b += 3; + do { + if (*++b == 'l') + return 1; + } while (ialpha(*b)); + } + } + + return 0; +} + +/* say we're done using the history mechanism */ + +/**/ +mod_export int +hend(Eprog prog) +{ + int flag, save = 1; + char *hf; + + DPUTS(stophist != 2 && !(inbufflags & INP_ALIAS) && !chline, + "BUG: chline is NULL in hend()"); + queue_signals(); + if (histdone & HISTFLAG_SETTY) + settyinfo(&shttyinfo); + if (!(histactive & HA_NOINC)) + unlinkcurline(); + if (histactive & (HA_NOSTORE|HA_NOINC)) { + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + histactive = 0; + unqueue_signals(); + return 1; + } + if (hist_ignore_all_dups != isset(HISTIGNOREALLDUPS) + && (hist_ignore_all_dups = isset(HISTIGNOREALLDUPS)) != 0) + histremovedups(); + /* For history sharing, lock history file once for both read and write */ + hf = getsparam("HISTFILE"); + if (isset(SHAREHISTORY) && lockhistfile(hf, 0)) { + readhistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST); + curline.histnum = curhist+1; + } + flag = histdone; + histdone = 0; + if (hptr < chline + 1) + save = 0; + else { + *hptr = '\0'; + if (hptr[-1] == '\n') { + if (chline[1]) { + *--hptr = '\0'; + } else + save = 0; + } + if (chwordpos <= 2) + save = 0; + else if (should_ignore_line(prog)) + save = -1; + } + if (flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) { + char *ptr; + + ptr = ztrdup(chline); + if ((flag & (HISTFLAG_DONE | HISTFLAG_RECALL)) == HISTFLAG_DONE) { + zputs(ptr, shout); + fputc('\n', shout); + fflush(shout); + } + if (flag & HISTFLAG_RECALL) { + zpushnode(bufstack, ptr); + save = 0; + } else + zsfree(ptr); + } + if (save || *chline == ' ') { + Histent he; + for (he = hist_ring; he && he->flags & HIST_FOREIGN; + he = up_histent(he)) ; + if (he && he->flags & HIST_TMPSTORE) { + if (he == hist_ring) + curline.histnum = curhist--; + freehistnode((HashNode)he); + } + } + if (save) { + Histent he; + int newflags; + +#ifdef DEBUG + /* debugging only */ + if (chwordpos%2) { + hwend(); + DPUTS(1, "BUG: uncompleted line in history"); + } +#endif + /* get rid of pesky \n which we've already nulled out */ + if (chwordpos > 1 && !chline[chwords[chwordpos-2]]) { + chwordpos -= 2; + /* strip superfluous blanks, if desired */ + if (isset(HISTREDUCEBLANKS)) + histreduceblanks(); + } + newflags = save > 0? 0 : HIST_TMPSTORE; + if ((isset(HISTIGNOREDUPS) || isset(HISTIGNOREALLDUPS)) && save > 0 + && hist_ring && histstrcmp(chline, hist_ring->text) == 0) { + /* This history entry compares the same as the previous. + * In case minor changes were made, we overwrite the + * previous one with the current one. This also gets the + * timestamp right. Perhaps, preserve the HIST_OLD flag. + */ + he = hist_ring; + newflags |= he->flags & HIST_OLD; /* Avoid re-saving */ + freehistdata(he, 0); + curline.histnum = curhist; + } else + he = prepnexthistent(); + + he->text = ztrdup(chline); + he->stim = time(NULL); + he->ftim = 0L; + he->flags = newflags; + + if ((he->nwords = chwordpos/2)) { + he->words = (short *)zalloc(chwordpos * sizeof(short)); + memcpy(he->words, chwords, chwordpos * sizeof(short)); + } + if (!(newflags & HIST_TMPSTORE)) + addhistnode(histtab, he->text, he); + } + zfree(chline, hlinesz); + zfree(chwords, chwordlen*sizeof(short)); + chline = NULL; + histactive = 0; + if (isset(SHAREHISTORY)? histfileIsLocked() : isset(INCAPPENDHISTORY)) + savehistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST); + unlockhistfile(hf); /* It's OK to call this even if we aren't locked */ + unqueue_signals(); + 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) + 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; +} + +/* add a word to the history List */ + +/**/ +void +ihwend(void) +{ + if (stophist == 2) + return; + if (chwordpos%2 && chline) { + /* end of word reached and we've already begun a word */ + if (hptr > chline + chwords[chwordpos-1]) { + chwords[chwordpos++] = hptr - chline; + if (chwordpos >= chwordlen) { + chwords = (short *) realloc(chwords, + (chwordlen += 32) * + sizeof(short)); + } + if (hwgetword > -1) { + /* 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--; + } + } +} + +/* Go back to immediately after the last word, skipping space. */ + +/**/ +void +histbackword(void) +{ + if (!(chwordpos%2) && chwordpos) + hptr = chline + chwords[chwordpos-1]; +} + +/* Get the start and end point of the current history word */ + +/**/ +static void +hwget(char **startptr) +{ + int pos = hwgetword > -1 ? hwgetword : chwordpos - 2; + +#ifdef DEBUG + /* debugging only */ + if (hwgetword == -1 && !chwordpos) { + /* no words available */ + DPUTS(1, "BUG: hwget() called with no words"); + *startptr = ""; + return; + } + else if (hwgetword == -1 && chwordpos%2) { + DPUTS(1, "BUG: hwget() called in middle of word"); + *startptr = ""; + return; + } +#endif + + *startptr = chline + chwords[pos]; + chline[chwords[++pos]] = '\0'; +} + +/* Replace the current history word with rep, if different */ + +/**/ +void +hwrep(char *rep) +{ + char *start; + hwget(&start); + + if (!strcmp(rep, start)) + return; + + hptr = start; + chwordpos = (hwgetword > -1) ? hwgetword : chwordpos - 2; + hwbegin(0); + qbang = 1; + while (*rep) + hwaddc(*rep++); + hwend(); +} + +/* Get the entire current line, deleting it in the history. */ + +/**/ +mod_export char * +hgetline(void) +{ + /* Currently only used by pushlineoredit(). + * It's necessary to prevent that from getting too pally with + * the history code. + */ + char *ret; + + if (!chline || hptr == chline) + return NULL; + *hptr = '\0'; + ret = dupstring(chline); + + /* reset line */ + hptr = chline; + chwordpos = 0; + hwgetword = -1; + + return ret; +} + +/* get an argument specification */ + +/**/ +static int +getargspec(int argc, int marg, int evset) +{ + int c, ret = -1; + + if ((c = ingetc()) == '0') + return 0; + if (idigit(c)) { + ret = 0; + while (idigit(c)) { + ret = ret * 10 + c - '0'; + c = ingetc(); + } + inungetc(c); + } else if (c == '^') + ret = 1; + else if (c == '$') + ret = argc; + else if (c == '%') { + if (evset) { + herrflush(); + zerr("Ambiguous history reference", NULL, 0); + return -2; + } + if (marg == -1) { + herrflush(); + zerr("%% with no previous word matched", NULL, 0); + return -2; + } + ret = marg; + } else + inungetc(c); + return ret; +} + +/* do ?foo? search */ + +/**/ +static zlong +hconsearch(char *str, int *marg) +{ + int t1 = 0; + char *s; + Histent he; + + for (he = up_histent(hist_ring); he; he = up_histent(he)) { + if (he->flags & HIST_FOREIGN) + continue; + if ((s = strstr(he->text, str))) { + int pos = s - he->text; + while (t1 < he->nwords && he->words[2*t1] <= pos) + t1++; + *marg = t1 - 1; + return he->histnum; + } + } + return -1; +} + +/* do !foo search */ + +/**/ +zlong +hcomsearch(char *str) +{ + Histent he; + int len = strlen(str); + + for (he = up_histent(hist_ring); he; he = up_histent(he)) { + if (he->flags & HIST_FOREIGN) + continue; + if (strncmp(he->text, str, len) == 0) + return he->histnum; + } + return -1; +} + +/* various utilities for : modifiers */ + +/**/ +int +remtpath(char **junkptr) +{ + char *str = strend(*junkptr); + + /* ignore trailing slashes */ + while (str >= *junkptr && IS_DIRSEP(*str)) + --str; + /* skip filename */ + while (str >= *junkptr && !IS_DIRSEP(*str)) + --str; + if (str < *junkptr) { + if (IS_DIRSEP(**junkptr)) + *junkptr = dupstring ("/"); + else + *junkptr = dupstring ("."); + + return 0; + } + /* repeated slashes are considered like a single slash */ + while (str > *junkptr && IS_DIRSEP(str[-1])) + --str; + /* never erase the root slash */ + if (str == *junkptr) { + ++str; + /* Leading doubled slashes (`//') have a special meaning on cygwin + and some old flavor of UNIX, so we do not assimilate them to + a single slash. However a greater number is ok to squeeze. */ + if (IS_DIRSEP(*str) && !IS_DIRSEP(str[1])) + ++str; + } + *str = '\0'; + return 1; +} + +/**/ +int +remtext(char **junkptr) +{ + char *str; + + for (str = strend(*junkptr); str >= *junkptr && !IS_DIRSEP(*str); --str) + if (*str == '.') { + *str = '\0'; + return 1; + } + return 0; +} + +/**/ +int +rembutext(char **junkptr) +{ + char *str; + + for (str = strend(*junkptr); str >= *junkptr && !IS_DIRSEP(*str); --str) + if (*str == '.') { + *junkptr = dupstring(str + 1); /* .xx or xx? */ + return 1; + } + /* no extension */ + *junkptr = dupstring (""); + return 0; +} + +/**/ +mod_export int +remlpaths(char **junkptr) +{ + char *str = strend(*junkptr); + + if (IS_DIRSEP(*str)) { + /* remove trailing slashes */ + while (str >= *junkptr && IS_DIRSEP(*str)) + --str; + str[1] = '\0'; + } + for (; str >= *junkptr; --str) + if (IS_DIRSEP(*str)) { + *str = '\0'; + *junkptr = dupstring(str + 1); + return 1; + } + return 0; +} + +/**/ +int +makeuppercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tuupper(*str); + return 1; +} + +/**/ +int +makelowercase(char **junkptr) +{ + char *str = *junkptr; + + for (; *str; str++) + *str = tulower(*str); + return 1; +} + +/**/ +int +makecapitals(char **junkptr) +{ + char *str = *junkptr; + + for (; *str;) { + for (; *str && !ialnum(*str); str++); + if (*str) + *str = tuupper(*str), str++; + for (; *str && ialnum(*str); str++) + *str = tulower(*str); + } + return 1; +} + +/**/ +void +subst(char **strptr, char *in, char *out, int gbal) +{ + char *str = *strptr, *instr = *strptr, *substcut, *sptr, *oldstr; + int off, inlen, outlen; + + if (!*in) + in = str, gbal = 0; + if (!(substcut = (char *)strstr(str, in))) + return; + inlen = strlen(in); + sptr = convamps(out, in, inlen); + outlen = strlen(sptr); + + do { + *substcut = '\0'; + off = substcut - *strptr + outlen; + substcut += inlen; + *strptr = tricat(oldstr = *strptr, sptr, substcut); + if (oldstr != instr) + zsfree(oldstr); + str = (char *)*strptr + off; + } while (gbal && (substcut = (char *)strstr(str, in))); +} + +/**/ +static char * +convamps(char *out, char *in, int inlen) +{ + char *ptr, *ret, *pp; + int slen, sdup = 0; + + for (ptr = out, slen = 0; *ptr; ptr++, slen++) + if (*ptr == '\\') + ptr++, sdup = 1; + else if (*ptr == '&') + slen += inlen - 1, sdup = 1; + if (!sdup) + return out; + ret = pp = (char *) zhalloc(slen + 1); + for (ptr = out; *ptr; ptr++) + if (*ptr == '\\') + *pp++ = *++ptr; + else if (*ptr == '&') { + strcpy(pp, in); + pp += inlen; + } else + *pp++ = *ptr; + *pp = '\0'; + return ret; +} + +/**/ +mod_export void +checkcurline(Histent he) +{ + if (he->histnum == curhist && (histactive & HA_ACTIVE)) { + curline.text = chline; + curline.nwords = chwordpos/2; + curline.words = chwords; + } +} + +/**/ +mod_export Histent +quietgethist(int ev) +{ + return gethistent(ev, GETHIST_EXACT); +} + +/**/ +static Histent +gethist(int ev) +{ + Histent ret; + + ret = quietgethist(ev); + if (!ret) { + herrflush(); + zerr("no such event: %d", NULL, ev); + } + return ret; +} + +/**/ +static char * +getargs(Histent elist, int arg1, int arg2) +{ + short *words = elist->words; + int pos1, nwords = elist->nwords; + + if (arg2 < arg1 || arg1 >= nwords || arg2 >= nwords) { + /* remember, argN is indexed from 0, nwords is total no. of words */ + herrflush(); + zerr("no such word in event", NULL, 0); + return NULL; + } + + pos1 = words[2*arg1]; + return dupstrpfx(elist->text + pos1, words[2*arg2+1] - pos1); +} + +/**/ +void +upcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tuupper(*pp); +} + +/**/ +void +downcase(char **x) +{ + char *pp = *(char **)x; + + for (; *pp; pp++) + *pp = tulower(*pp); +} + +/**/ +int +quote(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + int inquotes = 0; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') { + len += 3; + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') + len += 2; + ptr = *str; + *str = rptr = (char *) zhalloc(len); + *rptr++ = '\''; + for (; *ptr; ptr++) + if (*ptr == '\'') { + if (!inquotes) + inquotes = 1; + else + inquotes = 0; + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + } else if (inblank(*ptr) && !inquotes && ptr[-1] != '\\') { + *rptr++ = '\''; + *rptr++ = *ptr; + *rptr++ = '\''; + } else + *rptr++ = *ptr; + *rptr++ = '\''; + *rptr++ = 0; + str[1] = NULL; + return 0; +} + +/**/ +static int +quotebreak(char **tr) +{ + char *ptr, *rptr, **str = (char **)tr; + int len = 3; + + for (ptr = *str; *ptr; ptr++, len++) + if (*ptr == '\'') + len += 3; + else if (inblank(*ptr)) + len += 2; + ptr = *str; + *str = rptr = (char *) zhalloc(len); + *rptr++ = '\''; + for (; *ptr;) + if (*ptr == '\'') { + *rptr++ = '\''; + *rptr++ = '\\'; + *rptr++ = '\''; + *rptr++ = '\''; + ptr++; + } else if (inblank(*ptr)) { + *rptr++ = '\''; + *rptr++ = *ptr++; + *rptr++ = '\''; + } else + *rptr++ = *ptr++; + *rptr++ = '\''; + *rptr++ = '\0'; + return 0; +} + +/* read an arbitrary amount of data into a buffer until stop is found */ + +#if 0 /**/ +char * +hdynread(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') { + inungetc('\n'); + zerr("delimiter expected", NULL, 0); + zfree(buf, bsiz); + return NULL; + } + return buf; +} +#endif + +/**/ +static char * +hdynread2(int stop) +{ + int bsiz = 256, ct = 0, c; + char *buf = (char *)zalloc(bsiz), *ptr; + + ptr = buf; + while ((c = ingetc()) != stop && c != '\n' && !lexstop) { + if (c == '\n') { + inungetc(c); + break; + } + if (c == '\\') + c = ingetc(); + *ptr++ = c; + if (++ct == bsiz) { + buf = realloc(buf, bsiz *= 2); + ptr = buf + ct; + } + } + *ptr = 0; + if (c == '\n') + inungetc('\n'); + return buf; +} + +/**/ +void +inithist(void) +{ + createhisttable(); +} + +/**/ +void +resizehistents(void) +{ + if (histlinect > histsiz) { + /* The reason we don't just call freehistnode(hist_ring->down) is + * so that we can honor the HISTEXPIREDUPSFIRST setting. */ + putoldhistentryontop(0); + freehistnode((HashNode)hist_ring); + while (histlinect > histsiz) { + putoldhistentryontop(1); + freehistnode((HashNode)hist_ring); + } + } +} + +/* Remember the last line in the history file so we can find it again. */ +static struct histfile_stats { + char *text; + time_t stim, mtim; + off_t fpos, fsiz; + zlong next_write_ev; +} lasthist; + +static struct histsave { + struct histfile_stats lasthist; + char *histfile; + HashTable histtab; + Histent hist_ring; + zlong curhist; + zlong histlinect; + zlong histsiz; + zlong savehistsiz; + int locallevel; +} *histsave_stack; +static int histsave_stack_size = 0; +static int histsave_stack_pos = 0; + +static zlong histfile_linect; + +static int +readhistline(int start, char **bufp, int *bufsiz, FILE *in) +{ + char *buf = *bufp; + if (fgets(buf + start, *bufsiz - start, in)) { + int len = start + strlen(buf + start); + if (len == start) + return -1; + if (buf[len - 1] != '\n') { + if (!feof(in)) { + if (len < (*bufsiz) - 1) + return -1; + *bufp = zrealloc(buf, 2 * (*bufsiz)); + *bufsiz = 2 * (*bufsiz); + return readhistline(len, bufp, bufsiz, in); + } + } + else { + buf[len - 1] = '\0'; + if (len > 1 && buf[len - 2] == '\\') { + buf[--len - 1] = '\n'; + if (!feof(in)) + return readhistline(len, bufp, bufsiz, in); + } + } + return len; + } + return 0; +} + +/**/ +void +readhistfile(char *fn, int err, int readflags) +{ + char *buf, *start = NULL; + FILE *in; + Histent he; + time_t stim, ftim, tim = time(NULL); + off_t fpos; + short *wordlist; + struct stat sb; + int nwordpos, nwordlist, bufsiz; + int searching, newflags, l; + + if (!fn && !(fn = getsparam("HISTFILE"))) + return; + if (readflags & HFILE_FAST) { + if (stat(unmeta(fn), &sb) < 0 + || (lasthist.fsiz == sb.st_size && lasthist.mtim == sb.st_mtime) + || !lockhistfile(fn, 0)) + return; + lasthist.fsiz = sb.st_size; + lasthist.mtim = sb.st_mtime; + } + else if (!lockhistfile(fn, 1)) + return; + if ((in = fopen(unmeta(fn), "r"))) { + nwordlist = 64; + wordlist = (short *)zalloc(nwordlist*sizeof(short)); + bufsiz = 1024; + buf = zalloc(bufsiz); + + if (readflags & HFILE_FAST && lasthist.text) { + if (lasthist.fpos < lasthist.fsiz) { + fseek(in, lasthist.fpos, 0); + searching = 1; + } + else { + histfile_linect = 0; + searching = -1; + } + } else + searching = 0; + + newflags = HIST_OLD | HIST_READ; + if (readflags & HFILE_FAST) + newflags |= HIST_FOREIGN; + if (readflags & HFILE_SKIPOLD + || (hist_ignore_all_dups && newflags & hist_skip_flags)) + newflags |= HIST_MAKEUNIQUE; + while (fpos = ftell(in), (l = readhistline(0, &buf, &bufsiz, in))) { + char *pt = buf; + + if (l < 0) { + zerr("corrupt history file %s", fn, 0); + break; + } + if (*pt == ':') { + pt++; + stim = zstrtol(pt, NULL, 0); + for (; *pt != ':' && *pt; pt++); + if (*pt) { + pt++; + ftim = zstrtol(pt, NULL, 0); + for (; *pt != ';' && *pt; pt++); + if (*pt) + pt++; + } else + ftim = stim; + } else { + if (*pt == '\\' && pt[1] == ':') + pt++; + stim = ftim = 0; + } + + if (searching) { + if (searching > 0) { + if (stim == lasthist.stim + && histstrcmp(pt, lasthist.text) == 0) + searching = 0; + else { + fseek(in, 0, 0); + histfile_linect = 0; + searching = -1; + } + continue; + } + else if (stim < lasthist.stim) { + histfile_linect++; + continue; + } + searching = 0; + } + + if (readflags & HFILE_USE_OPTIONS) { + histfile_linect++; + lasthist.fpos = fpos; + lasthist.stim = stim; + } + + he = prepnexthistent(); + he->text = ztrdup(pt); + he->flags = newflags; + if ((he->stim = stim) == 0) + he->stim = he->ftim = tim; + else if (ftim < stim) + he->ftim = stim + ftim; + else + he->ftim = ftim; + + /* Divide up the words. We don't know how it lexes, + so just look for white-space. + */ + nwordpos = 0; + start = pt; + do { + while (inblank(*pt)) + pt++; + if (*pt) { + if (nwordpos >= nwordlist) + wordlist = (short *) realloc(wordlist, + (nwordlist += 64)*sizeof(short)); + wordlist[nwordpos++] = pt - start; + while (*pt && !inblank(*pt)) + pt++; + wordlist[nwordpos++] = pt - start; + } + } while (*pt); + + he->nwords = nwordpos/2; + if (he->nwords) { + he->words = (short *)zalloc(nwordpos*sizeof(short)); + memcpy(he->words, wordlist, nwordpos*sizeof(short)); + } else + he->words = (short *)NULL; + addhistnode(histtab, he->text, he); + if (he->flags & HIST_DUP) { + freehistnode((HashNode)he); + curhist--; + } + } + if (start && readflags & HFILE_USE_OPTIONS) { + zsfree(lasthist.text); + lasthist.text = ztrdup(start); + } + zfree(wordlist, nwordlist*sizeof(short)); + zfree(buf, bufsiz); + + fclose(in); + } else if (err) + zerr("can't read history file %s", fn, 0); + + unlockhistfile(fn); +} + +/**/ +void +savehistfile(char *fn, int err, int writeflags) +{ + char *t, *start = NULL; + FILE *out; + Histent he; + zlong xcurhist = curhist - !!(histactive & HA_ACTIVE); + int extended_history = isset(EXTENDEDHISTORY); + + if (!interact || savehistsiz <= 0 || !hist_ring + || (!fn && !(fn = getsparam("HISTFILE")))) + return; + if (writeflags & HFILE_FAST) { + he = gethistent(lasthist.next_write_ev, GETHIST_DOWNWARD); + while (he && he->flags & HIST_OLD) { + lasthist.next_write_ev = he->histnum + 1; + he = down_histent(he); + } + if (!he || !lockhistfile(fn, 0)) + return; + if (histfile_linect > savehistsiz + savehistsiz / 5) + writeflags &= ~HFILE_FAST; + } + else { + if (!lockhistfile(fn, 1)) + return; + he = hist_ring->down; + } + if (writeflags & HFILE_USE_OPTIONS) { + if (isset(APPENDHISTORY) || isset(INCAPPENDHISTORY) + || isset(SHAREHISTORY)) + writeflags |= HFILE_APPEND | HFILE_SKIPOLD; + else + histfile_linect = 0; + if (isset(HISTSAVENODUPS)) + writeflags |= HFILE_SKIPDUPS; + if (isset(SHAREHISTORY)) + extended_history = 1; + } + if (writeflags & HFILE_APPEND) { + out = fdopen(open(unmeta(fn), + O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600), "a"); + } + else { + out = fdopen(open(unmeta(fn), + O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600), "w"); + } + if (out) { + for (; he && he->histnum <= xcurhist; he = down_histent(he)) { + if ((writeflags & HFILE_SKIPDUPS && he->flags & HIST_DUP) + || (writeflags & HFILE_SKIPFOREIGN && he->flags & HIST_FOREIGN) + || he->flags & HIST_TMPSTORE) + continue; + if (writeflags & HFILE_SKIPOLD) { + if (he->flags & HIST_OLD) + continue; + he->flags |= HIST_OLD; + if (writeflags & HFILE_USE_OPTIONS) + lasthist.next_write_ev = he->histnum + 1; + } + if (writeflags & HFILE_USE_OPTIONS) { + lasthist.fpos = ftell(out); + lasthist.stim = he->stim; + histfile_linect++; + } + t = start = he->text; + if (extended_history) { + fprintf(out, ": %ld:%ld;", (long)he->stim, + he->ftim? (long)(he->ftim - he->stim) : 0L); + } else if (*t == ':') + fputc('\\', out); + + for (; *t; t++) { + if (*t == '\n') + fputc('\\', out); + fputc(*t, out); + } + fputc('\n', out); + } + if (start && writeflags & HFILE_USE_OPTIONS) { + struct stat sb; + fflush(out); + if (fstat(fileno(out), &sb) == 0) { + lasthist.fsiz = sb.st_size; + lasthist.mtim = sb.st_mtime; + } + zsfree(lasthist.text); + lasthist.text = ztrdup(start); + } + fclose(out); + + if (writeflags & HFILE_SKIPOLD + && !(writeflags & (HFILE_FAST | HFILE_NO_REWRITE))) { + int remember_histactive = histactive; + + /* Zeroing histactive avoids unnecessary munging of curline. */ + histactive = 0; + /* The NULL leaves HISTFILE alone, preserving fn's value. */ + pushhiststack(NULL, savehistsiz, savehistsiz, -1); + + hist_ignore_all_dups |= isset(HISTSAVENODUPS); + readhistfile(fn, err, 0); + hist_ignore_all_dups = isset(HISTIGNOREALLDUPS); + if (histlinect) + savehistfile(fn, err, 0); + + pophiststack(); + histactive = remember_histactive; + } + } else if (err) + zerr("can't write history file %s", fn, 0); + + unlockhistfile(fn); +} + +static int lockhistct; + +/**/ +int +lockhistfile(char *fn, int keep_trying) +{ + int ct = lockhistct; + + if (!fn && !(fn = getsparam("HISTFILE"))) + return 0; + if (!lockhistct++) { + struct stat sb; + int fd; + char *lockfile; +#ifdef HAVE_LINK + char *tmpfile; +#endif + + lockfile = bicat(unmeta(fn), ".LOCK"); +#ifdef HAVE_LINK + if ((fd = gettempfile(fn, 0, &tmpfile)) >= 0) { + FILE *out = fdopen(fd, "w"); + if (out) { + fprintf(out, "%ld %s\n", (long)getpid(), getsparam("HOST")); + fclose(out); + } else + close(fd); + while (link(tmpfile, lockfile) < 0) { + if (errno != EEXIST || !keep_trying) + ; + else if (stat(lockfile, &sb) < 0) { + if (errno == ENOENT) + continue; + } + else { + if (time(NULL) - sb.st_mtime < 10) + sleep(1); + else + unlink(lockfile); + continue; + } + lockhistct--; + break; + } + unlink(tmpfile); + free(tmpfile); + } +#else /* not HAVE_LINK */ + while ((fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0) { + if (errno != EEXIST || !keep_trying) + break; + if (stat(lockfile, &sb) < 0) { + if (errno == ENOENT) + continue; + break; + } + if (time(NULL) - sb.st_mtime < 10) + sleep(1); + else + unlink(lockfile); + } + if (fd < 0) + lockhistct--; + else { + FILE *out = fdopen(fd, "w"); + if (out) { + fprintf(out, "%ld %s\n", (long)mypid, getsparam("HOST")); + fclose(out); + } else + close(fd); + } +#endif /* not HAVE_LINK */ + free(lockfile); + } + return ct != lockhistct; +} + +/* Unlock the history file if this corresponds to the last nested lock + * request. If we don't have the file locked, just return. + */ + +/**/ +void +unlockhistfile(char *fn) +{ + if (!fn && !(fn = getsparam("HISTFILE"))) + return; + if (--lockhistct) { + if (lockhistct < 0) + lockhistct = 0; + } + else { + char *lockfile; + fn = unmeta(fn); + lockfile = zalloc(strlen(fn) + 5 + 1); + sprintf(lockfile, "%s.LOCK", fn); + unlink(lockfile); + free(lockfile); + } +} + +/**/ +int +histfileIsLocked(void) +{ + return lockhistct > 0; +} + +/* Get the words in the current buffer. Using the lexer. */ + +/**/ +mod_export LinkList +bufferwords(LinkList list, char *buf, int *index) +{ + int num = 0, cur = -1, got = 0, ne = noerrs, ocs = cs, oll = ll; + int owb = wb, owe = we, oadx = addedx, ozp = zleparse, onc = nocomments; + int ona = noaliases; + char *p; + + if (!list) + list = newlinklist(); + + zleparse = 1; + addedx = 0; + noerrs = 1; + lexsave(); + if (buf) { + int l = strlen(buf); + + p = (char *) zhalloc(l + 2); + memcpy(p, buf, l); + p[l] = ' '; + p[l + 1] = '\0'; + inpush(p, 0, NULL); + cs = strlen(p) + 1; + nocomments = 1; + } else if (!isfirstln && chline) { + p = (char *) zhalloc(hptr - chline + ll + 2); + memcpy(p, chline, hptr - chline); + memcpy(p + (hptr - chline), line, ll); + p[(hptr - chline) + ll] = ' '; + p[(hptr - chline) + ll + 1] = '\0'; + inpush(p, 0, NULL); + cs += hptr - chline; + } else { + p = (char *) zhalloc(ll + 2); + memcpy(p, line, ll); + p[ll] = ' '; + p[ll + 1] = '\0'; + inpush(p, 0, NULL); + } + ll = strlen(p); + if (cs) + cs--; + strinbeg(0); + noaliases = 1; + do { + if (incond) + incond = 1 + (tok != DINBRACK && tok != INPAR && + tok != DBAR && tok != DAMPER && + tok != BANG); + ctxtlex(); + if (tok == ENDINPUT || tok == LEXERR) + break; + if (tokstr && *tokstr) { + untokenize((p = dupstring(tokstr))); + addlinknode(list, p); + num++; + } else if (buf) { + if (IS_REDIROP(tok) && tokfd >= 0) { + char b[20]; + + sprintf(b, "%d%s", tokfd, tokstrings[tok]); + addlinknode(list, dupstring(b)); + num++; + } else if (tok != NEWLIN) { + addlinknode(list, dupstring(tokstrings[tok])); + num++; + } + } + if (!got && !zleparse) { + got = 1; + cur = num - 1; + } + } while (tok != ENDINPUT && tok != LEXERR); + if (buf && tok == LEXERR && tokstr && *tokstr) { + int plen; + untokenize((p = dupstring(tokstr))); + plen = strlen(p); + /* + * Strip the space we added for lexing but which won't have + * been swallowed by the lexer because we aborted early. + * The test is paranoia. + */ + if (plen && p[plen-1] == ' ' && (plen == 1 || p[plen-2] != Meta)) + p[plen - 1] = '\0'; + addlinknode(list, p); + num++; + } + if (cur < 0 && num) + cur = num - 1; + noaliases = ona; + strinend(); + inpop(); + errflag = 0; + zleparse = ozp; + nocomments = onc; + noerrs = ne; + lexrestore(); + cs = ocs; + ll = oll; + wb = owb; + we = owe; + addedx = oadx; + + if (index) + *index = cur; + + return list; +} + +/* 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 + * environment; if it is NULL, HISTFILE will not be changed, not even by the + * pop function (this functionality is used internally to rewrite the current + * history file without affecting pointers into the environment). + */ + +/**/ +int +pushhiststack(char *hf, zlong hs, zlong shs, int level) +{ + struct histsave *h; + int curline_in_ring = (histactive & HA_ACTIVE) && hist_ring == &curline; + + if (histsave_stack_pos == histsave_stack_size) { + histsave_stack_size += 5; + histsave_stack = zrealloc(histsave_stack, + histsave_stack_size * sizeof (struct histsave)); + } + + if (curline_in_ring) + unlinkcurline(); + + h = &histsave_stack[histsave_stack_pos++]; + + h->lasthist = lasthist; + if (hf) { + if ((h->histfile = getsparam("HISTFILE")) != NULL && *h->histfile) + h->histfile = ztrdup(h->histfile); + else + h->histfile = ""; + } else + h->histfile = NULL; + h->histtab = histtab; + h->hist_ring = hist_ring; + h->curhist = curhist; + h->histlinect = histlinect; + h->histsiz = histsiz; + h->savehistsiz = savehistsiz; + h->locallevel = level; + + memset(&lasthist, 0, sizeof lasthist); + if (hf) { + if (*hf) + setsparam("HISTFILE", ztrdup(hf)); + else + unsetparam("HISTFILE"); + } + hist_ring = NULL; + curhist = histlinect = 0; + histsiz = hs; + savehistsiz = shs; + inithist(); /* sets histtab */ + + if (curline_in_ring) + linkcurline(); + + return histsave_stack_pos; +} + + +/**/ +int +pophiststack(void) +{ + struct histsave *h; + int curline_in_ring = (histactive & HA_ACTIVE) && hist_ring == &curline; + + if (histsave_stack_pos == 0) + return 0; + + if (curline_in_ring) + unlinkcurline(); + + deletehashtable(histtab); + zsfree(lasthist.text); + + h = &histsave_stack[--histsave_stack_pos]; + + lasthist = h->lasthist; + if (h->histfile) { + if (*h->histfile) + setsparam("HISTFILE", h->histfile); + else + unsetparam("HISTFILE"); + } + histtab = h->histtab; + hist_ring = h->hist_ring; + curhist = h->curhist; + histlinect = h->histlinect; + histsiz = h->histsiz; + savehistsiz = h->savehistsiz; + + if (curline_in_ring) + linkcurline(); + + return histsave_stack_pos + 1; +} + +/* If pop_through > 0, pop all array items >= the 1-relative index value. + * If pop_through <= 0, pop (-1)*pop_through levels off the stack. + * If the (new) top of stack is from a higher locallevel, auto-pop until + * it is not. + */ + +/**/ +int +saveandpophiststack(int pop_through, int writeflags) +{ + if (pop_through <= 0) { + pop_through += histsave_stack_pos + 1; + if (pop_through <= 0) + pop_through = 1; + } + while (pop_through > 1 + && histsave_stack[pop_through-2].locallevel > locallevel) + pop_through--; + if (histsave_stack_pos < pop_through) + return 0; + do { + if (!nohistsave) + savehistfile(NULL, 1, writeflags); + pophiststack(); + } while (histsave_stack_pos >= pop_through); + return 1; +}