mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-11-04 07:21:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			3536 lines
		
	
	
	
		
			78 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3536 lines
		
	
	
	
		
			78 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * hist.c - history expansion
 | 
						|
 *
 | 
						|
 * 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"
 | 
						|
 | 
						|
/* 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().
 | 
						|
 *
 | 
						|
 * Note curhist is passed to zle on variable length argument list:
 | 
						|
 * type must match that retrieved in zle_main_entry.
 | 
						|
 */
 | 
						|
 
 | 
						|
/**/
 | 
						|
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_NOINC	(1<<1)	/* 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;
 | 
						|
 | 
						|
/*
 | 
						|
 * The current history line as seen by ZLE.
 | 
						|
 * We modify chline for use in other contexts while ZLE may
 | 
						|
 * still be running; ZLE should see only the top-level value.
 | 
						|
 *
 | 
						|
 * To avoid having to modify this every time we modify chline,
 | 
						|
 * we set it when we push the stack, and unset it when we pop
 | 
						|
 * the appropriate value off the stack.  As it's never modified
 | 
						|
 * on the stack this is the only maintainance we ever do on it.
 | 
						|
 * In return, ZLE has to check both zle_chline and (if that's
 | 
						|
 * NULL) chline to get the current value.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export char *zle_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;
 | 
						|
 | 
						|
/* 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;
 | 
						|
 | 
						|
/* 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 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.                */
 | 
						|
	    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--;
 | 
						|
	zleentry(ZLE_CMD_ADD_TO_LINE, '\\');
 | 
						|
    }
 | 
						|
    if (excs > zlemetacs) {
 | 
						|
	excs += 1 + inbufct - exlast;
 | 
						|
	if (excs < zlemetacs)
 | 
						|
	    /* this case could be handled better but it is    *
 | 
						|
	     * so rare that it does not worth it              */
 | 
						|
	    excs = zlemetacs;
 | 
						|
    }
 | 
						|
    exlast = inbufct;
 | 
						|
    zleentry(ZLE_CMD_ADD_TO_LINE, 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 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
 | 
						|
 *
 | 
						|
 * The first character expected is the first delimiter.
 | 
						|
 * The arguments are stored in the hsubl and hsubr variables.
 | 
						|
 *
 | 
						|
 * subline is the part of the command line to be matched.
 | 
						|
 *
 | 
						|
 * If a ':' was found but was not followed by a 'G',
 | 
						|
 * *cflagp is set to 1 and the input is backed up to the
 | 
						|
 * character following the colon.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
getsubsargs(char *subline, int *gbalp, int *cflagp)
 | 
						|
{
 | 
						|
    int del, follow;
 | 
						|
    char *ptr1, *ptr2;
 | 
						|
 | 
						|
    del = ingetc();
 | 
						|
    ptr1 = hdynread2(del);
 | 
						|
    if (!ptr1)
 | 
						|
	return 1;
 | 
						|
    ptr2 = hdynread2(del);
 | 
						|
    if (strlen(ptr1)) {
 | 
						|
	zsfree(hsubl);
 | 
						|
	hsubl = ptr1;
 | 
						|
    } else if (!hsubl) {		/* fail silently on this */
 | 
						|
	zsfree(ptr2);
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
    zsfree(hsubr);
 | 
						|
    hsubr = ptr2;
 | 
						|
    follow = ingetc();
 | 
						|
    if (follow == ':') {
 | 
						|
	follow = ingetc();
 | 
						|
	if (follow == 'G')
 | 
						|
	    *gbalp = 1;
 | 
						|
	else {
 | 
						|
	    inungetc(follow);
 | 
						|
	    *cflagp = 1;
 | 
						|
	}
 | 
						|
    } else
 | 
						|
	inungetc(follow);
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Get the maximum no. of words for a history entry. */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
getargc(Histent ehist)
 | 
						|
{
 | 
						|
    return ehist->nwords ? ehist->nwords-1 : 0;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
substfailed(void)
 | 
						|
{
 | 
						|
    herrflush();
 | 
						|
    zerr("substitution failed");
 | 
						|
    return -1;
 | 
						|
}
 | 
						|
 | 
						|
/* 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, *ptr;
 | 
						|
    char *sline;
 | 
						|
    Histent ehist;
 | 
						|
    size_t buflen;
 | 
						|
 | 
						|
    /* look, no goto's */
 | 
						|
    if (isfirstch && c == hatchar) {
 | 
						|
	int gbal = 0;
 | 
						|
 | 
						|
	/* Line begins ^foo^bar */
 | 
						|
	isfirstch = 0;
 | 
						|
	inungetc(hatchar);
 | 
						|
	if (!(ehist = gethist(defev))
 | 
						|
	    || !(sline = getargs(ehist, 0, getargc(ehist))))
 | 
						|
	    return -1;
 | 
						|
 | 
						|
	if (getsubsargs(sline, &gbal, &cflag))
 | 
						|
	    return substfailed();
 | 
						|
	if (!hsubl)
 | 
						|
	    return -1;
 | 
						|
	if (subst(&sline, hsubl, hsubr, gbal))
 | 
						|
	    return substfailed();
 | 
						|
    } else {
 | 
						|
	/* Line doesn't begin ^foo^bar */
 | 
						|
	if (c != ' ')
 | 
						|
	    isfirstch = 0;
 | 
						|
	if (c == '\\') {
 | 
						|
	    int g = ingetc();
 | 
						|
 | 
						|
	    if (g != bangchar)
 | 
						|
		safeinungetc(g);
 | 
						|
	    else {
 | 
						|
		qbang = 1;
 | 
						|
		return bangchar;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	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 = zhalloc(buflen = 265);
 | 
						|
 | 
						|
	/* get event number */
 | 
						|
 | 
						|
	queue_signals();
 | 
						|
	if (c == '?') {
 | 
						|
	    for (;;) {
 | 
						|
		c = ingetc();
 | 
						|
		if (c == '?' || c == '\n' || lexstop)
 | 
						|
		    break;
 | 
						|
		else {
 | 
						|
		    *ptr++ = c;
 | 
						|
		    if (ptr == buf + buflen) {
 | 
						|
			buf = hrealloc(buf, buflen, 2 * buflen);
 | 
						|
			ptr = buf + buflen;
 | 
						|
			buflen *= 2;
 | 
						|
		    }
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    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);
 | 
						|
		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 (ptr == buf + buflen) {
 | 
						|
		    buf = hrealloc(buf, buflen, 2 * buflen);
 | 
						|
		    ptr = buf + buflen;
 | 
						|
		    buflen *= 2;
 | 
						|
		}
 | 
						|
		if (c == '#' || c == bangchar) {
 | 
						|
		    c = ingetc();
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		c = ingetc();
 | 
						|
	    }
 | 
						|
	    if (ptr == buf &&
 | 
						|
		(c == '}' ||  c == ';' || c == '\'' || c == '"' || c == '`')) {
 | 
						|
	      /* Neither event nor word designator, no expansion */
 | 
						|
	      safeinungetc(c);
 | 
						|
	      return bangchar;
 | 
						|
	    }
 | 
						|
	    *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);
 | 
						|
		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");
 | 
						|
		    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();
 | 
						|
		if (c != 's' && c != '&') {
 | 
						|
		    zerr("'s' or '&' modifier expected after 'g'");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    switch (c) {
 | 
						|
	    case 'p':
 | 
						|
		histdone = HISTFLAG_DONE | HISTFLAG_NOEXEC;
 | 
						|
		break;
 | 
						|
	    case 'a':
 | 
						|
		if (!chabspath(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: a");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	    case 'A':
 | 
						|
		if (!chrealpath(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: A");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 'c':
 | 
						|
		if (!(sline = equalsubstr(sline, 0, 0))) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: c");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 'h':
 | 
						|
		if (!remtpath(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: h");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 'e':
 | 
						|
		if (!rembutext(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: e");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 'r':
 | 
						|
		if (!remtext(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: r");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 't':
 | 
						|
		if (!remlpaths(&sline)) {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("modifier failed: t");
 | 
						|
		    return -1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    case 's':
 | 
						|
		if (getsubsargs(sline, &gbal, &cflag))
 | 
						|
		    return -1; /* fall through */
 | 
						|
	    case '&':
 | 
						|
		if (hsubl && hsubr) {
 | 
						|
		    if (subst(&sline, hsubl, hsubr, gbal))
 | 
						|
			return substfailed();
 | 
						|
		} else {
 | 
						|
		    herrflush();
 | 
						|
		    zerr("no previous substitution");
 | 
						|
		    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':
 | 
						|
		sline = casemodify(sline, CASMOD_LOWER);
 | 
						|
		break;
 | 
						|
	    case 'u':
 | 
						|
		sline = casemodify(sline, CASMOD_UPPER);
 | 
						|
		break;
 | 
						|
	    default:
 | 
						|
		herrflush();
 | 
						|
		zerr("illegal modifier: %c", c);
 | 
						|
		return -1;
 | 
						|
	    }
 | 
						|
	} else {
 | 
						|
	    if (c != '}' || !bflag)
 | 
						|
		inungetc(c);
 | 
						|
	    if (c != '}' && bflag) {
 | 
						|
		zerr("'}' expected");
 | 
						|
		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) {
 | 
						|
	    zlemetacs--;
 | 
						|
	    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 (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)
 | 
						|
{
 | 
						|
    char *hf;
 | 
						|
 | 
						|
    isfirstln = isfirstch = 1;
 | 
						|
    errflag = histdone = 0;
 | 
						|
    if (!dohist)
 | 
						|
	stophist = 2;
 | 
						|
    else if (dohist != 2)
 | 
						|
	stophist = (!interact || unset(SHINSTDIN)) ? 2 : 0;
 | 
						|
    else
 | 
						|
	stophist = 0;
 | 
						|
    /*
 | 
						|
     * 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;
 | 
						|
	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 && !strin)
 | 
						|
	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;
 | 
						|
 | 
						|
    hf = getsparam("HISTFILE");
 | 
						|
    /*
 | 
						|
     * For INCAPPENDHISTORYTIME, when interactive, save the history here
 | 
						|
     * as it gives a better estimate of the times of commands.
 | 
						|
     *
 | 
						|
     * If INCAPPENDHISTORY is also set we've already done it.
 | 
						|
     *
 | 
						|
     * If SHAREHISTORY is also set continue to do so in the
 | 
						|
     * standard place, because that's safer about reading and
 | 
						|
     * rewriting history atomically.
 | 
						|
     *
 | 
						|
     * The histsave_stack_pos test won't usually fail here.
 | 
						|
     * We need to test the opposite for the hend() case because we
 | 
						|
     * need to save in the history file we've switched to, but then
 | 
						|
     * we pop immediately after that so the variable becomes zero.
 | 
						|
     * We will already have saved the line and restored the history
 | 
						|
     * so that (correctly) nothing happens here.  But it shows
 | 
						|
     * I thought about it.
 | 
						|
     */
 | 
						|
    if (isset(INCAPPENDHISTORYTIME) && !isset(SHAREHISTORY) &&
 | 
						|
	!isset(INCAPPENDHISTORY) &&
 | 
						|
	!(histactive & HA_NOINC) && !strin && histsave_stack_pos == 0)
 | 
						|
	savehistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST);
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
void
 | 
						|
histreduceblanks(void)
 | 
						|
{
 | 
						|
    int i, len, pos, needblank, spacecount = 0, trunc_ok;
 | 
						|
    char *lastptr, *ptr;
 | 
						|
 | 
						|
    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;
 | 
						|
 | 
						|
    /* Remember where the delimited words end */
 | 
						|
    if (chwordpos)
 | 
						|
	lastptr = chline + chwords[chwordpos-1];
 | 
						|
    else
 | 
						|
	lastptr = chline;
 | 
						|
 | 
						|
    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]) {
 | 
						|
	    memmove(chline + pos, chline + chwords[i], len + needblank);
 | 
						|
	    chwords[i] = pos;
 | 
						|
	    chwords[i+1] = chwords[i] + len;
 | 
						|
	}
 | 
						|
	pos += len + needblank;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * A terminating comment isn't recorded as a word.
 | 
						|
     * Only truncate the line if just whitespace remains.
 | 
						|
     */
 | 
						|
    trunc_ok = 1;
 | 
						|
    for (ptr = lastptr; *ptr; ptr++) {
 | 
						|
	if (!inblank(*ptr)) {
 | 
						|
	    trunc_ok = 0;
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    if (trunc_ok) {
 | 
						|
	chline[pos] = '\0';
 | 
						|
    } else {
 | 
						|
	ptr = chline + pos;
 | 
						|
	while ((*ptr++ = *lastptr++))
 | 
						|
	    ;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
void
 | 
						|
histremovedups(void)
 | 
						|
{
 | 
						|
    Histent he, next;
 | 
						|
    for (he = hist_ring; he; he = next) {
 | 
						|
	next = up_histent(he);
 | 
						|
	if (he->node.flags & HIST_DUP)
 | 
						|
	    freehistnode(&he->node);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
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->node.flags & xflags))
 | 
						|
	    n++;
 | 
						|
    }
 | 
						|
    while (n > 0) {
 | 
						|
	if (!(he = down_histent(he)))
 | 
						|
	    return NULL;
 | 
						|
	if (!(he->node.flags & xflags))
 | 
						|
	    n--;
 | 
						|
    }
 | 
						|
    checkcurline(he);
 | 
						|
    return he;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export Histent
 | 
						|
up_histent(Histent he)
 | 
						|
{
 | 
						|
    return !he || 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 || !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)
 | 
						|
	    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->node.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->node.flags & HIST_TMPSTORE) {
 | 
						|
	curhist--;
 | 
						|
	freehistnode(&hist_ring->node);
 | 
						|
    }
 | 
						|
 | 
						|
    if (histlinect < histsiz || !hist_ring) {
 | 
						|
	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)
 | 
						|
{
 | 
						|
    LinkList hookargs = newlinklist();
 | 
						|
    int flag, hookret, stack_pos = histsave_stack_pos;
 | 
						|
    /*
 | 
						|
     * save:
 | 
						|
     * 0: don't save
 | 
						|
     * 1: save normally
 | 
						|
     * -1: save temporarily, delete after next line
 | 
						|
     * -2: save internally but mark for not writing
 | 
						|
     */
 | 
						|
    int 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_NOINC) {
 | 
						|
	zfree(chline, hlinesz);
 | 
						|
	zfree(chwords, chwordlen*sizeof(short));
 | 
						|
	chline = hptr = NULL;
 | 
						|
	chwords = NULL;
 | 
						|
	histactive = 0;
 | 
						|
	unqueue_signals();
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    if (hist_ignore_all_dups != isset(HISTIGNOREALLDUPS)
 | 
						|
     && (hist_ignore_all_dups = isset(HISTIGNOREALLDUPS)) != 0)
 | 
						|
	histremovedups();
 | 
						|
 | 
						|
    if (hptr) {
 | 
						|
	/*
 | 
						|
	 * Added the following in case the test "hptr < chline + 1"
 | 
						|
	 * is more than just paranoia.
 | 
						|
	 */
 | 
						|
	DPUTS(hptr < chline, "History end pointer off start of line");
 | 
						|
	*hptr = '\0';
 | 
						|
    }
 | 
						|
    addlinknode(hookargs, "zshaddhistory");
 | 
						|
    addlinknode(hookargs, chline);
 | 
						|
    callhookfunc("zshaddhistory", hookargs, 1, &hookret);
 | 
						|
    /* 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 {
 | 
						|
	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;
 | 
						|
	else if (hookret == 2)
 | 
						|
	    save = -2;
 | 
						|
	else if (hookret)
 | 
						|
	    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->node.flags & HIST_FOREIGN;
 | 
						|
	     he = up_histent(he)) ;
 | 
						|
	if (he && he->node.flags & HIST_TMPSTORE) {
 | 
						|
	    if (he == hist_ring)
 | 
						|
		curline.histnum = curhist--;
 | 
						|
	    freehistnode(&he->node);
 | 
						|
	}
 | 
						|
    }
 | 
						|
    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();
 | 
						|
	}
 | 
						|
	if (save == -1)
 | 
						|
	    newflags = HIST_TMPSTORE;
 | 
						|
	else if (save == -2)
 | 
						|
	    newflags = HIST_NOWRITE;
 | 
						|
	else
 | 
						|
	    newflags = 0;
 | 
						|
	if ((isset(HISTIGNOREDUPS) || isset(HISTIGNOREALLDUPS)) && save > 0
 | 
						|
	 && hist_ring && histstrcmp(chline, hist_ring->node.nam) == 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->node.flags & HIST_OLD; /* Avoid re-saving */
 | 
						|
	    freehistdata(he, 0);
 | 
						|
	    curline.histnum = curhist;
 | 
						|
	} else
 | 
						|
	    he = prepnexthistent();
 | 
						|
 | 
						|
	he->node.nam = ztrdup(chline);
 | 
						|
	he->stim = time(NULL);
 | 
						|
	he->ftim = 0L;
 | 
						|
	he->node.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->node.nam, he);
 | 
						|
    }
 | 
						|
    zfree(chline, hlinesz);
 | 
						|
    zfree(chwords, chwordlen*sizeof(short));
 | 
						|
    chline = hptr = NULL;
 | 
						|
    chwords = NULL;
 | 
						|
    histactive = 0;
 | 
						|
    /*
 | 
						|
     * For normal INCAPPENDHISTORY case and reasoning, see hbegin().
 | 
						|
     */
 | 
						|
    if (isset(SHAREHISTORY) ? histfileIsLocked() :
 | 
						|
	(isset(INCAPPENDHISTORY) || (isset(INCAPPENDHISTORYTIME) &&
 | 
						|
				     histsave_stack_pos != 0)))
 | 
						|
	savehistfile(hf, 0, HFILE_USE_OPTIONS | HFILE_FAST);
 | 
						|
    unlockhistfile(hf); /* It's OK to call this even if we aren't locked */
 | 
						|
    /*
 | 
						|
     * No good reason for the user to push the history more than once, but
 | 
						|
     * it's easy to be tidy...
 | 
						|
     */
 | 
						|
    while (histsave_stack_pos > stack_pos)
 | 
						|
	pophiststack();
 | 
						|
    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 &&
 | 
						|
		(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--;
 | 
						|
	}
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* 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");
 | 
						|
	    return -2;
 | 
						|
	}
 | 
						|
	if (marg == -1) {
 | 
						|
	    herrflush();
 | 
						|
	    zerr("%% with no previous word matched");
 | 
						|
	    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->node.flags & HIST_FOREIGN)
 | 
						|
	    continue;
 | 
						|
	if ((s = strstr(he->node.nam, str))) {
 | 
						|
	    int pos = s - he->node.nam;
 | 
						|
	    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->node.flags & HIST_FOREIGN)
 | 
						|
	    continue;
 | 
						|
	if (strncmp(he->node.nam, str, len) == 0)
 | 
						|
	    return he->histnum;
 | 
						|
    }
 | 
						|
    return -1;
 | 
						|
}
 | 
						|
 | 
						|
/* various utilities for : modifiers */
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
chabspath(char **junkptr)
 | 
						|
{
 | 
						|
    char *current, *dest;
 | 
						|
 | 
						|
    if (!**junkptr)
 | 
						|
	return 1;
 | 
						|
 | 
						|
    if (**junkptr != '/') {
 | 
						|
	*junkptr = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", *junkptr);
 | 
						|
    }
 | 
						|
 | 
						|
    current = *junkptr;
 | 
						|
    dest = *junkptr;
 | 
						|
 | 
						|
#ifdef HAVE_SUPERROOT
 | 
						|
    while (*current == '/' && current[1] == '.' && current[2] == '.' &&
 | 
						|
	   (!current[3] || current[3] == '/')) {
 | 
						|
	*dest++ = '/';
 | 
						|
	*dest++ = '.';
 | 
						|
	*dest++ = '.';
 | 
						|
	current += 3;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    for (;;) {
 | 
						|
	if (*current == '/') {
 | 
						|
#ifdef __CYGWIN__
 | 
						|
	    if (current == *junkptr && current[1] == '/')
 | 
						|
		*dest++ = *current++;
 | 
						|
#endif
 | 
						|
	    *dest++ = *current++;
 | 
						|
	    while (*current == '/')
 | 
						|
		current++;
 | 
						|
	} else if (!*current) {
 | 
						|
	    while (dest > *junkptr + 1 && dest[-1] == '/')
 | 
						|
		dest--;
 | 
						|
	    *dest = '\0';
 | 
						|
	    break;
 | 
						|
	} else if (current[0] == '.' && current[1] == '.' &&
 | 
						|
		   (!current[2] || current[2] == '/')) {
 | 
						|
		if (current == *junkptr || dest == *junkptr) {
 | 
						|
		    *dest++ = '.';
 | 
						|
		    *dest++ = '.';
 | 
						|
		    current += 2;
 | 
						|
		} else if (dest > *junkptr + 2 &&
 | 
						|
			   !strncmp(dest - 3, "../", 3)) {
 | 
						|
		    *dest++ = '.';
 | 
						|
		    *dest++ = '.';
 | 
						|
		    current += 2;
 | 
						|
		} else if (dest > *junkptr + 1) {
 | 
						|
		    *dest = '\0';
 | 
						|
		    for (dest--;
 | 
						|
			 dest > *junkptr + 1 && dest[-1] != '/';
 | 
						|
			 dest--);
 | 
						|
		    if (dest[-1] != '/')
 | 
						|
			dest--;
 | 
						|
		    current += 2;
 | 
						|
		    if (*current == '/')
 | 
						|
			current++;
 | 
						|
		} else if (dest == *junkptr + 1) {
 | 
						|
		    /* This might break with Cygwin's leading double slashes? */
 | 
						|
		    current += 2;
 | 
						|
		} else {
 | 
						|
		    return 0;
 | 
						|
		}
 | 
						|
	} else if (current[0] == '.' && (current[1] == '/' || !current[1])) {
 | 
						|
	     while (*++current == '/');
 | 
						|
	} else {
 | 
						|
	    while (*current != '/' && *current != '\0')
 | 
						|
		if ((*dest++ = *current++) == Meta)
 | 
						|
		    *dest++ = *current++;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
chrealpath(char **junkptr)
 | 
						|
{
 | 
						|
    char *str;
 | 
						|
#ifdef HAVE_CANONICALIZE_FILE_NAME
 | 
						|
    char *lastpos, *nonreal, *real;
 | 
						|
#else
 | 
						|
# ifdef HAVE_REALPATH
 | 
						|
    char *lastpos, *nonreal, real[PATH_MAX];
 | 
						|
# endif
 | 
						|
#endif
 | 
						|
 | 
						|
    if (!**junkptr)
 | 
						|
	return 1;
 | 
						|
 | 
						|
    /* Notice that this means ..'s are applied before symlinks are resolved! */
 | 
						|
    if (!chabspath(junkptr))
 | 
						|
	return 0;
 | 
						|
 | 
						|
#if !defined(HAVE_REALPATH) && !defined(HAVE_CANONICALIZE_FILE_NAME)
 | 
						|
    return 1;
 | 
						|
#else
 | 
						|
    /*
 | 
						|
     * Notice that this means you cannot pass relative paths into this
 | 
						|
     * function!
 | 
						|
     */
 | 
						|
    if (**junkptr != '/')
 | 
						|
	return 0;
 | 
						|
 | 
						|
    unmetafy(*junkptr, NULL);
 | 
						|
 | 
						|
    lastpos = strend(*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))
 | 
						|
#else
 | 
						|
	   realpath(*junkptr, real)
 | 
						|
#endif
 | 
						|
	) {
 | 
						|
	if (errno == EINVAL || errno == ELOOP ||
 | 
						|
	    errno == ENAMETOOLONG || errno == ENOMEM)
 | 
						|
	    return 0;
 | 
						|
 | 
						|
#ifdef HAVE_CANONICALIZE_FILE_NAME
 | 
						|
	if (!real)
 | 
						|
	    return 0;
 | 
						|
#endif
 | 
						|
 | 
						|
	if (nonreal == *junkptr) {
 | 
						|
	    *real = '\0';
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
 | 
						|
	while (*nonreal != '/' && nonreal >= *junkptr)
 | 
						|
	    nonreal--;
 | 
						|
	*nonreal = '\0';
 | 
						|
    }
 | 
						|
 | 
						|
    str = nonreal;
 | 
						|
    while (str <= lastpos) {
 | 
						|
	if (*str == '\0')
 | 
						|
	    *str = '/';
 | 
						|
	str++;
 | 
						|
    }
 | 
						|
 | 
						|
    *junkptr = metafy(str = bicat(real, nonreal), -1, META_HEAPDUP);
 | 
						|
    zsfree(str);
 | 
						|
#ifdef HAVE_CANONICALIZE_FILE_NAME
 | 
						|
    free(real);
 | 
						|
#endif
 | 
						|
#endif
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
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;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Return modified version of str from the heap with modification
 | 
						|
 * according to one of the CASMOD_* types defined in zsh.h; CASMOD_NONE
 | 
						|
 * is not handled, for obvious reasons.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
char *
 | 
						|
casemodify(char *str, int how)
 | 
						|
{
 | 
						|
    char *str2 = zhalloc(2 * strlen(str) + 1);
 | 
						|
    char *ptr2 = str2;
 | 
						|
    int nextupper = 1;
 | 
						|
 | 
						|
#ifdef MULTIBYTE_SUPPORT
 | 
						|
    if (isset(MULTIBYTE)) {
 | 
						|
	VARARR(char, mbstr, MB_CUR_MAX);
 | 
						|
	mbstate_t ps;
 | 
						|
 | 
						|
	mb_metacharinit();
 | 
						|
	memset(&ps, 0, sizeof(ps));
 | 
						|
	while (*str) {
 | 
						|
	    wint_t wc;
 | 
						|
	    int len = mb_metacharlenconv(str, &wc), mod = 0, len2;
 | 
						|
	    /*
 | 
						|
	     * wc is set to WEOF if the start of str couldn't be
 | 
						|
	     * converted.  Presumably WEOF doesn't match iswlower(), but
 | 
						|
	     * better be safe.
 | 
						|
	     */
 | 
						|
	    if (wc == WEOF) {
 | 
						|
		while (len--)
 | 
						|
		    *ptr2++ = *str++;
 | 
						|
		/* not alphanumeric */
 | 
						|
		nextupper = 1;
 | 
						|
		continue;
 | 
						|
	    }
 | 
						|
	    switch (how) {
 | 
						|
	    case CASMOD_LOWER:
 | 
						|
		if (iswupper(wc)) {
 | 
						|
		    wc = towlower(wc);
 | 
						|
		    mod = 1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	    case CASMOD_UPPER:
 | 
						|
		if (iswlower(wc)) {
 | 
						|
		    wc = towupper(wc);
 | 
						|
		    mod = 1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	    case CASMOD_CAPS:
 | 
						|
	    default:		/* shuts up compiler */
 | 
						|
		if (IS_COMBINING(wc))
 | 
						|
			break;
 | 
						|
		if (!iswalnum(wc))
 | 
						|
		    nextupper = 1;
 | 
						|
		else if (nextupper) {
 | 
						|
		    if (iswlower(wc)) {
 | 
						|
			wc = towupper(wc);
 | 
						|
			mod = 1;
 | 
						|
		    }
 | 
						|
		    nextupper = 0;
 | 
						|
		} else if (iswupper(wc)) {
 | 
						|
		    wc = towlower(wc);
 | 
						|
		    mod = 1;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (mod && (len2 = wcrtomb(mbstr, wc, &ps)) > 0) {
 | 
						|
		char *mbptr;
 | 
						|
 | 
						|
		for (mbptr = mbstr; mbptr < mbstr + len2; mbptr++) {
 | 
						|
		    if (imeta(STOUC(*mbptr))) {
 | 
						|
			*ptr2++ = Meta;
 | 
						|
			*ptr2++ = *mbptr ^ 32;
 | 
						|
		    } else
 | 
						|
			*ptr2++ = *mbptr;
 | 
						|
		}
 | 
						|
		str += len;
 | 
						|
	    } else {
 | 
						|
		while (len--)
 | 
						|
		    *ptr2++ = *str++;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
    else
 | 
						|
#endif
 | 
						|
	while (*str) {
 | 
						|
	    int c;
 | 
						|
	    if (*str == Meta) {
 | 
						|
		c = str[1] ^ 32;
 | 
						|
		str += 2;
 | 
						|
	    } else
 | 
						|
		c = *str++;
 | 
						|
	    switch (how) {
 | 
						|
	    case CASMOD_LOWER:
 | 
						|
		if (isupper(c))
 | 
						|
		    c = tolower(c);
 | 
						|
		break;
 | 
						|
 | 
						|
	    case CASMOD_UPPER:
 | 
						|
		if (islower(c))
 | 
						|
		    c = toupper(c);
 | 
						|
		break;
 | 
						|
 | 
						|
	    case CASMOD_CAPS:
 | 
						|
	    default:		/* shuts up compiler */
 | 
						|
		if (!ialnum(c))
 | 
						|
		    nextupper = 1;
 | 
						|
		else if (nextupper) {
 | 
						|
		    if (islower(c))
 | 
						|
			c = toupper(c);
 | 
						|
		    nextupper = 0;
 | 
						|
		} else if (isupper(c))
 | 
						|
		    c = tolower(c);
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (imeta(c)) {
 | 
						|
		*ptr2++ = Meta;
 | 
						|
		*ptr2++ = c ^ 32;
 | 
						|
	    } else
 | 
						|
		*ptr2++ = c;
 | 
						|
	}
 | 
						|
    *ptr2 = '\0';
 | 
						|
    return str2;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Substitute "in" for "out" in "*strptr" and update "*strptr".
 | 
						|
 * If "gbal", do global substitution.
 | 
						|
 *
 | 
						|
 * This returns a result from the heap.  There seems to have
 | 
						|
 * been some confusion on this point.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
subst(char **strptr, char *in, char *out, int gbal)
 | 
						|
{
 | 
						|
    char *str = *strptr, *substcut, *sptr;
 | 
						|
    int off, inlen, outlen;
 | 
						|
 | 
						|
    if (!*in)
 | 
						|
	in = str, gbal = 0;
 | 
						|
 | 
						|
    if (isset(HISTSUBSTPATTERN)) {
 | 
						|
	int fl = SUB_LONG|SUB_REST|SUB_RETFAIL;
 | 
						|
	char *oldin = in;
 | 
						|
	if (gbal)
 | 
						|
	    fl |= SUB_GLOBAL;
 | 
						|
	if (*in == '#' || *in == Pound) {
 | 
						|
	    /* anchor at head, flag needed if SUB_END is also set */
 | 
						|
	    fl |= SUB_START;
 | 
						|
	    in++;
 | 
						|
	}
 | 
						|
	if (*in == '%') {
 | 
						|
	    /* anchor at tail */
 | 
						|
	    in++;
 | 
						|
	    fl |= SUB_END;
 | 
						|
	}
 | 
						|
	if (in == oldin) {
 | 
						|
	    /* no anchor, substring match */
 | 
						|
	    fl |= SUB_SUBSTR;
 | 
						|
	}
 | 
						|
	if (in == str)
 | 
						|
	    in = dupstring(in);
 | 
						|
	if (parse_subst_string(in) || errflag)
 | 
						|
	    return 1;
 | 
						|
	if (parse_subst_string(out) || errflag)
 | 
						|
	    return 1;
 | 
						|
	singsub(&in);
 | 
						|
	if (getmatch(strptr, in, fl, 1, out))
 | 
						|
	    return 0;
 | 
						|
    } else {
 | 
						|
	if ((substcut = (char *)strstr(str, in))) {
 | 
						|
	    inlen = strlen(in);
 | 
						|
	    sptr = convamps(out, in, inlen);
 | 
						|
	    outlen = strlen(sptr);
 | 
						|
 | 
						|
	    do {
 | 
						|
		*substcut = '\0';
 | 
						|
		off = substcut - *strptr + outlen;
 | 
						|
		substcut += inlen;
 | 
						|
		*strptr = zhtricat(*strptr, sptr, substcut);
 | 
						|
		str = (char *)*strptr + off;
 | 
						|
	    } while (gbal && (substcut = (char *)strstr(str, in)));
 | 
						|
 | 
						|
	    return 0;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
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.node.nam = 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", 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");
 | 
						|
	return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    pos1 = words[2*arg1];
 | 
						|
    return dupstrpfx(elist->node.nam + pos1, words[2*arg2+1] - pos1);
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
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");
 | 
						|
	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 == '\\')
 | 
						|
	    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(&hist_ring->node);
 | 
						|
	while (histlinect > histsiz) {
 | 
						|
	    putoldhistentryontop(1);
 | 
						|
	    freehistnode(&hist_ring->node);
 | 
						|
	}
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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 *words;
 | 
						|
    struct stat sb;
 | 
						|
    int nwordpos, nwords, bufsiz;
 | 
						|
    int searching, newflags, l, ret, uselex;
 | 
						|
 | 
						|
    if (!fn && !(fn = getsparam("HISTFILE")))
 | 
						|
	return;
 | 
						|
    if (stat(unmeta(fn), &sb) < 0 ||
 | 
						|
	sb.st_size == 0)
 | 
						|
	return;
 | 
						|
    if (readflags & HFILE_FAST) {
 | 
						|
	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;
 | 
						|
    } else if ((ret = lockhistfile(fn, 1))) {
 | 
						|
	if (ret == 2) {
 | 
						|
	    zwarn("locking failed for %s: %e: reading anyway", fn, errno);
 | 
						|
	} else {
 | 
						|
	    zerr("locking failed for %s: %e", fn, errno);
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    if ((in = fopen(unmeta(fn), "r"))) {
 | 
						|
	nwords = 64;
 | 
						|
	words = (short *)zalloc(nwords*sizeof(short));
 | 
						|
	bufsiz = 1024;
 | 
						|
	buf = zalloc(bufsiz);
 | 
						|
 | 
						|
	pushheap();
 | 
						|
	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);
 | 
						|
		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->node.nam = ztrdup(pt);
 | 
						|
	    he->node.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.
 | 
						|
	     */
 | 
						|
	    start = pt;
 | 
						|
	    uselex = isset(HISTLEXWORDS) && !(readflags & HFILE_FAST);
 | 
						|
	    histsplitwords(pt, &words, &nwords, &nwordpos, uselex);
 | 
						|
	    if (uselex)
 | 
						|
		freeheap();
 | 
						|
 | 
						|
	    he->nwords = nwordpos/2;
 | 
						|
	    if (he->nwords) {
 | 
						|
		he->words = (short *)zalloc(nwordpos*sizeof(short));
 | 
						|
		memcpy(he->words, words, nwordpos*sizeof(short));
 | 
						|
	    } else
 | 
						|
		he->words = (short *)NULL;
 | 
						|
	    addhistnode(histtab, he->node.nam, he);
 | 
						|
	    if (he->node.flags & HIST_DUP) {
 | 
						|
		freehistnode(&he->node);
 | 
						|
		curhist--;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	if (start && readflags & HFILE_USE_OPTIONS) {
 | 
						|
	    zsfree(lasthist.text);
 | 
						|
	    lasthist.text = ztrdup(start);
 | 
						|
	}
 | 
						|
	zfree(words, nwords*sizeof(short));
 | 
						|
	zfree(buf, bufsiz);
 | 
						|
 | 
						|
	popheap();
 | 
						|
	fclose(in);
 | 
						|
    } else if (err)
 | 
						|
	zerr("can't read history file %s", fn);
 | 
						|
 | 
						|
    unlockhistfile(fn);
 | 
						|
 | 
						|
    if (zleactive)
 | 
						|
	zleentry(ZLE_CMD_SET_HIST_LINE, curhist);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
static int flock_fd = -1;
 | 
						|
 | 
						|
/*
 | 
						|
 * Lock file using fcntl().  Return 0 on success, 1 on failure of
 | 
						|
 * locking mechanism, 2 on permanent failure (e.g. permission).
 | 
						|
 */
 | 
						|
 | 
						|
static int
 | 
						|
flockhistfile(char *fn, int keep_trying)
 | 
						|
{
 | 
						|
    struct flock lck;
 | 
						|
    int ctr = keep_trying ? 9 : 0;
 | 
						|
 | 
						|
    if (flock_fd >= 0)
 | 
						|
	return 0; /* already locked */
 | 
						|
 | 
						|
    if ((flock_fd = open(unmeta(fn), O_RDWR | O_NOCTTY)) < 0)
 | 
						|
	return errno == ENOENT ? 0 : 2; /* "successfully" locked missing file */
 | 
						|
 | 
						|
    lck.l_type = F_WRLCK;
 | 
						|
    lck.l_whence = SEEK_SET;
 | 
						|
    lck.l_start = 0;
 | 
						|
    lck.l_len = 0;  /* lock the whole file */
 | 
						|
 | 
						|
    while (fcntl(flock_fd, F_SETLKW, &lck) == -1) {
 | 
						|
	if (--ctr < 0) {
 | 
						|
	    close(flock_fd);
 | 
						|
	    flock_fd = -1;
 | 
						|
	    return 1;
 | 
						|
	}
 | 
						|
	sleep(1);
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/**/
 | 
						|
void
 | 
						|
savehistfile(char *fn, int err, int writeflags)
 | 
						|
{
 | 
						|
    char *t, *tmpfile, *start = NULL;
 | 
						|
    FILE *out;
 | 
						|
    Histent he;
 | 
						|
    zlong xcurhist = curhist - !!(histactive & HA_ACTIVE);
 | 
						|
    int extended_history = isset(EXTENDEDHISTORY);
 | 
						|
    int ret;
 | 
						|
 | 
						|
    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->node.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)) {
 | 
						|
	    zerr("locking failed for %s: %e", fn, errno);
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
	he = hist_ring->down;
 | 
						|
    }
 | 
						|
    if (writeflags & HFILE_USE_OPTIONS) {
 | 
						|
	if (isset(APPENDHISTORY) || isset(INCAPPENDHISTORY)
 | 
						|
	 || isset(INCAPPENDHISTORYTIME) || isset(SHAREHISTORY))
 | 
						|
	    writeflags |= HFILE_APPEND | HFILE_SKIPOLD;
 | 
						|
	else
 | 
						|
	    histfile_linect = 0;
 | 
						|
	if (isset(HISTSAVENODUPS))
 | 
						|
	    writeflags |= HFILE_SKIPDUPS;
 | 
						|
	if (isset(SHAREHISTORY))
 | 
						|
	    extended_history = 1;
 | 
						|
    }
 | 
						|
    errno = 0;
 | 
						|
    if (writeflags & HFILE_APPEND) {
 | 
						|
	int fd = open(unmeta(fn), O_CREAT | O_WRONLY | O_APPEND | O_NOCTTY, 0600);
 | 
						|
	tmpfile = NULL;
 | 
						|
	out = fd >= 0 ? fdopen(fd, "a") : NULL;
 | 
						|
    } else if (!isset(HISTSAVEBYCOPY)) {
 | 
						|
	int fd = open(unmeta(fn), O_CREAT | O_WRONLY | O_TRUNC | O_NOCTTY, 0600);
 | 
						|
	tmpfile = NULL;
 | 
						|
	out = fd >= 0 ? fdopen(fd, "w") : NULL;
 | 
						|
    } else {
 | 
						|
	tmpfile = bicat(unmeta(fn), ".new");
 | 
						|
	if (unlink(tmpfile) < 0 && errno != ENOENT)
 | 
						|
	    out = NULL;
 | 
						|
	else {
 | 
						|
	    struct stat sb;
 | 
						|
	    int old_exists = stat(unmeta(fn), &sb) == 0;
 | 
						|
	    uid_t euid = geteuid();
 | 
						|
 | 
						|
	    if (old_exists
 | 
						|
#if defined HAVE_FCHMOD && defined HAVE_FCHOWN
 | 
						|
	     && euid
 | 
						|
#endif
 | 
						|
	     && sb.st_uid != euid) {
 | 
						|
		free(tmpfile);
 | 
						|
		tmpfile = NULL;
 | 
						|
		if (err) {
 | 
						|
		    if (isset(APPENDHISTORY) || isset(INCAPPENDHISTORY)
 | 
						|
		     || isset(INCAPPENDHISTORYTIME) || isset(SHAREHISTORY))
 | 
						|
			zerr("rewriting %s would change its ownership -- skipped", fn);
 | 
						|
		    else
 | 
						|
			zerr("rewriting %s would change its ownership -- history not saved", fn);
 | 
						|
		    err = 0; /* Don't report a generic error below. */
 | 
						|
		}
 | 
						|
		out = NULL;
 | 
						|
	    } else {
 | 
						|
		int fd = open(tmpfile, O_CREAT | O_WRONLY | O_EXCL, 0600);
 | 
						|
		if (fd >=0) {
 | 
						|
		    out = fdopen(fd, "w");
 | 
						|
		    if (!out)
 | 
						|
			close(fd);
 | 
						|
		} else
 | 
						|
		    out = NULL;
 | 
						|
	    }
 | 
						|
 | 
						|
#ifdef HAVE_FCHMOD
 | 
						|
	    if (old_exists && out) {
 | 
						|
#ifdef HAVE_FCHOWN
 | 
						|
		if (fchown(fileno(out), sb.st_uid, sb.st_gid) < 0) {} /* IGNORE FAILURE */
 | 
						|
#endif
 | 
						|
		if (fchmod(fileno(out), sb.st_mode) < 0) {} /* IGNORE FAILURE */
 | 
						|
	    }
 | 
						|
#endif
 | 
						|
	}
 | 
						|
    }
 | 
						|
    if (out) {
 | 
						|
	char *history_ignore;
 | 
						|
	Patprog histpat = NULL;
 | 
						|
 | 
						|
	pushheap();
 | 
						|
 | 
						|
	if ((history_ignore = getsparam("HISTORY_IGNORE")) != NULL) {
 | 
						|
	    tokenize(history_ignore = dupstring(history_ignore));
 | 
						|
	    remnulargs(history_ignore);
 | 
						|
	    histpat = patcompile(history_ignore, 0, NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	ret = 0;
 | 
						|
	for (; he && he->histnum <= xcurhist; he = down_histent(he)) {
 | 
						|
	    int count_backslashes = 0;
 | 
						|
 | 
						|
	    if ((writeflags & HFILE_SKIPDUPS && he->node.flags & HIST_DUP)
 | 
						|
	     || (writeflags & HFILE_SKIPFOREIGN && he->node.flags & HIST_FOREIGN)
 | 
						|
	     || he->node.flags & HIST_TMPSTORE)
 | 
						|
		continue;
 | 
						|
	    if (histpat &&
 | 
						|
		pattry(histpat, metafy(he->node.nam, -1, META_HEAPDUP))) {
 | 
						|
		continue;
 | 
						|
	    }
 | 
						|
	    if (writeflags & HFILE_SKIPOLD) {
 | 
						|
		if (he->node.flags & (HIST_OLD|HIST_NOWRITE))
 | 
						|
		    continue;
 | 
						|
		he->node.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->node.nam;
 | 
						|
	    if (extended_history) {
 | 
						|
		ret = fprintf(out, ": %ld:%ld;", (long)he->stim,
 | 
						|
			      he->ftim? (long)(he->ftim - he->stim) : 0L);
 | 
						|
	    } else if (*t == ':')
 | 
						|
		ret = fputc('\\', out);
 | 
						|
 | 
						|
	    for (; ret >= 0 && *t; t++) {
 | 
						|
		if (*t == '\n')
 | 
						|
		    if ((ret = fputc('\\', out)) < 0)
 | 
						|
			break;
 | 
						|
		if (*t == '\\')
 | 
						|
		    count_backslashes++;
 | 
						|
		else
 | 
						|
		    count_backslashes = 0;
 | 
						|
		if ((ret = fputc(*t, out)) < 0)
 | 
						|
		    break;
 | 
						|
	    }
 | 
						|
	    if (ret < 0)
 | 
						|
	    	break;
 | 
						|
	    if (count_backslashes && (count_backslashes % 2 == 0))
 | 
						|
		if ((ret = fputc(' ', out)) < 0)
 | 
						|
		    break;
 | 
						|
	    if (ret < 0 || (ret = fputc('\n', out)) < 0)
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	if (ret >= 0 && start && writeflags & HFILE_USE_OPTIONS) {
 | 
						|
	    struct stat sb;
 | 
						|
	    if ((ret = fflush(out)) >= 0) {
 | 
						|
		if (fstat(fileno(out), &sb) == 0) {
 | 
						|
		    lasthist.fsiz = sb.st_size;
 | 
						|
		    lasthist.mtim = sb.st_mtime;
 | 
						|
		}
 | 
						|
		zsfree(lasthist.text);
 | 
						|
		lasthist.text = ztrdup(start);
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	if (fclose(out) < 0 && ret >= 0)
 | 
						|
	    ret = -1;
 | 
						|
	if (ret >= 0) {
 | 
						|
	    if (tmpfile) {
 | 
						|
		if (rename(tmpfile, unmeta(fn)) < 0) {
 | 
						|
		    zerr("can't rename %s.new to $HISTFILE", fn);
 | 
						|
		    ret = -1;
 | 
						|
		    err = 0;
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
		} else {
 | 
						|
		    /* We renamed over the locked HISTFILE, so close fd.
 | 
						|
		     * If we do more writing, we'll get a lock then. */
 | 
						|
		    if (flock_fd >= 0) {
 | 
						|
			close(flock_fd);
 | 
						|
			flock_fd = -1;
 | 
						|
		    }
 | 
						|
#endif
 | 
						|
		}
 | 
						|
	    }
 | 
						|
 | 
						|
	    if (ret >= 0 && 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;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	popheap();
 | 
						|
    } else
 | 
						|
	ret = -1;
 | 
						|
 | 
						|
    if (ret < 0 && err) {
 | 
						|
	if (tmpfile)
 | 
						|
	    zerr("failed to write history file %s.new: %e", fn, errno);
 | 
						|
	else
 | 
						|
	    zerr("failed to write history file %s: %e", fn, errno);
 | 
						|
    }
 | 
						|
    if (tmpfile)
 | 
						|
	free(tmpfile);
 | 
						|
 | 
						|
    unlockhistfile(fn);
 | 
						|
}
 | 
						|
 | 
						|
static int lockhistct;
 | 
						|
 | 
						|
static int
 | 
						|
checklocktime(char *lockfile, time_t then)
 | 
						|
{
 | 
						|
    time_t now = time(NULL);
 | 
						|
 | 
						|
    if (now + 10 < then) {
 | 
						|
	/* File is more than 10 seconds in the future? */
 | 
						|
	errno = EEXIST;
 | 
						|
	return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (now - then < 10)
 | 
						|
	sleep(1);
 | 
						|
    else
 | 
						|
	unlink(lockfile);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Lock history file.  Return 0 on success, 1 on failure to lock this
 | 
						|
 * time, 2 on permanent failure (e.g. permission).
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
lockhistfile(char *fn, int keep_trying)
 | 
						|
{
 | 
						|
    int ct = lockhistct;
 | 
						|
    int ret = 0;
 | 
						|
 | 
						|
    if (!fn && !(fn = getsparam("HISTFILE")))
 | 
						|
	return 1;
 | 
						|
 | 
						|
    if (!lockhistct++) {
 | 
						|
	struct stat sb;
 | 
						|
	int fd;
 | 
						|
	char *lockfile;
 | 
						|
#ifdef HAVE_LINK
 | 
						|
# ifdef HAVE_SYMLINK
 | 
						|
	char pidbuf[32], *lnk;
 | 
						|
# else
 | 
						|
	char *tmpfile;
 | 
						|
# endif
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
	if (isset(HISTFCNTLLOCK))
 | 
						|
	    return flockhistfile(fn, keep_trying);
 | 
						|
#endif
 | 
						|
 | 
						|
	lockfile = bicat(unmeta(fn), ".LOCK");
 | 
						|
	/* NOTE: only use symlink locking on a link()-having host in order to
 | 
						|
	 * avoid a change from open()-based locking to symlink()-based. */
 | 
						|
#ifdef HAVE_LINK
 | 
						|
# ifdef HAVE_SYMLINK
 | 
						|
	sprintf(pidbuf, "/pid-%ld/host-", (long)mypid);
 | 
						|
	lnk = bicat(pidbuf, getsparam("HOST"));
 | 
						|
	/* We'll abuse fd as our success flag. */
 | 
						|
	while ((fd = symlink(lnk, lockfile)) < 0) {
 | 
						|
	    if (errno != EEXIST) {
 | 
						|
		ret = 2;
 | 
						|
		break;
 | 
						|
	    } else if (!keep_trying) {
 | 
						|
		ret = 1;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (lstat(lockfile, &sb) < 0) {
 | 
						|
		if (errno == ENOENT)
 | 
						|
		    continue;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (checklocktime(lockfile, sb.st_mtime) < 0) {
 | 
						|
		ret = 1;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	if (fd < 0)
 | 
						|
	    lockhistct--;
 | 
						|
	free(lnk);
 | 
						|
# else /* not HAVE_SYMLINK */
 | 
						|
	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) {
 | 
						|
		    ret = 2;
 | 
						|
		    break;
 | 
						|
		} else if (!keep_trying) {
 | 
						|
		    ret = 1;
 | 
						|
		    break;
 | 
						|
		} else if (lstat(lockfile, &sb) < 0) {
 | 
						|
		    if (errno == ENOENT)
 | 
						|
			continue;
 | 
						|
		    ret = 2;
 | 
						|
		} else {
 | 
						|
		    if (checklocktime(lockfile, sb.st_mtime) < 0) {
 | 
						|
			ret = 1;
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		    continue;
 | 
						|
		}
 | 
						|
		lockhistct--;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    unlink(tmpfile);
 | 
						|
	    free(tmpfile);
 | 
						|
	}
 | 
						|
# endif /* not HAVE_SYMLINK */
 | 
						|
#else /* not HAVE_LINK */
 | 
						|
	while ((fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644)) < 0) {
 | 
						|
	    if (errno != EEXIST) {
 | 
						|
		ret = 2;
 | 
						|
		break;
 | 
						|
	    } else if (!keep_trying) {
 | 
						|
		ret = 1;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (lstat(lockfile, &sb) < 0) {
 | 
						|
		if (errno == ENOENT)
 | 
						|
		    continue;
 | 
						|
		ret = 2;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (checklocktime(lockfile, sb.st_mtime) < 0) {
 | 
						|
		ret = 1;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	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);
 | 
						|
    }
 | 
						|
 | 
						|
    if (ct == lockhistct) {
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
	if (flock_fd >= 0) {
 | 
						|
	    close(flock_fd);
 | 
						|
	    flock_fd = -1;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
	DPUTS(ret == 0, "BUG: return value non-zero on locking error");
 | 
						|
	return ret;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* 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);
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
	if (flock_fd >= 0) {
 | 
						|
	    close(flock_fd);
 | 
						|
	    flock_fd = -1;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
histfileIsLocked(void)
 | 
						|
{
 | 
						|
    return lockhistct > 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get the words in the current buffer. Using the lexer. 
 | 
						|
 *
 | 
						|
 * As far as I can make out, this is a gross hack based on a gross hack.
 | 
						|
 * When analysing lines from within zle, we tweak the metafied line
 | 
						|
 * positions (zlemetall and zlemetacs) directly in the lexer.  That's
 | 
						|
 * bad enough, but this function appears to be designed to be called
 | 
						|
 * from outside zle, pretending to be in zle and calling out, so
 | 
						|
 * we set zlemetall and zlemetacs locally and copy the current zle line,
 | 
						|
 * which may not even be valid at this point.
 | 
						|
 *
 | 
						|
 * However, I'm so confused it could simply be baking Bakewell tarts.
 | 
						|
 *
 | 
						|
 * list may be an existing linked list (off the heap), in which case
 | 
						|
 * it will be appended to; otherwise it will be created.
 | 
						|
 *
 | 
						|
 * If buf is set we will take input from that string, else we will
 | 
						|
 * attempt to use ZLE directly in a way they tell you not to do on all
 | 
						|
 * programming courses.
 | 
						|
 *
 | 
						|
 * If index is non-NULL, and input is from a string in ZLE, *index
 | 
						|
 * is set to the position of the end of the current editor word.
 | 
						|
 *
 | 
						|
 * flags is passed directly to lexflags, see lex.c, except that
 | 
						|
 * we 'or' in the bit LEXFLAGS_ACTIVE to make sure the variable
 | 
						|
 * is set.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export LinkList
 | 
						|
bufferwords(LinkList list, char *buf, int *index, int flags)
 | 
						|
{
 | 
						|
    int num = 0, cur = -1, got = 0, ne = noerrs;
 | 
						|
    int owb = wb, owe = we, oadx = addedx, onc = nocomments;
 | 
						|
    int ona = noaliases, ocs = zlemetacs, oll = zlemetall;
 | 
						|
    int forloop = 0, rcquotes = opts[RCQUOTES];
 | 
						|
    char *p, *addedspaceptr;
 | 
						|
 | 
						|
    if (!list)
 | 
						|
	list = newlinklist();
 | 
						|
 | 
						|
    /*
 | 
						|
     * With RC_QUOTES, 'foo '' bar' comes back as 'foo ' bar'.  That's
 | 
						|
     * not very useful.  As nothing in here requires the fully processed
 | 
						|
     * string expression, we just turn the option off for this function.
 | 
						|
     */
 | 
						|
    opts[RCQUOTES] = 0;
 | 
						|
    addedx = 0;
 | 
						|
    noerrs = 1;
 | 
						|
    lexsave();
 | 
						|
    lexflags = flags | LEXFLAGS_ACTIVE;
 | 
						|
    /*
 | 
						|
     * Are we handling comments?
 | 
						|
     */
 | 
						|
    nocomments = !(flags & (LEXFLAGS_COMMENTS_KEEP|
 | 
						|
			    LEXFLAGS_COMMENTS_STRIP));
 | 
						|
    if (buf) {
 | 
						|
	int l = strlen(buf);
 | 
						|
 | 
						|
	p = (char *) zhalloc(l + 2);
 | 
						|
	memcpy(p, buf, l);
 | 
						|
	/*
 | 
						|
	 * I'm sure this space is here for a reason, but it's
 | 
						|
	 * a pain in the neck:  when we get back a string that's
 | 
						|
	 * not finished it's very hard to tell if a space at the
 | 
						|
	 * end is this one or not.  We use two tricks below to
 | 
						|
	 * work around this.
 | 
						|
	 */
 | 
						|
	addedspaceptr = p + l;
 | 
						|
	*addedspaceptr = ' ';
 | 
						|
	addedspaceptr[1] = '\0';
 | 
						|
	inpush(p, 0, NULL);
 | 
						|
	zlemetall = strlen(p) ;
 | 
						|
	zlemetacs = zlemetall + 1;
 | 
						|
    } else {
 | 
						|
	int ll, cs;
 | 
						|
	char *linein;
 | 
						|
 | 
						|
	linein = zleentry(ZLE_CMD_GET_LINE, &ll, &cs);
 | 
						|
	zlemetall = ll + 1; /* length of line plus space added below */
 | 
						|
	zlemetacs = cs;
 | 
						|
 | 
						|
	if (!isfirstln && chline) {
 | 
						|
	    p = (char *) zhalloc(hptr - chline + ll + 2);
 | 
						|
	    memcpy(p, chline, hptr - chline);
 | 
						|
	    memcpy(p + (hptr - chline), linein, ll);
 | 
						|
	    addedspaceptr = p + (hptr - chline) + ll;
 | 
						|
	    *addedspaceptr = ' ';
 | 
						|
	    addedspaceptr[1] = '\0';
 | 
						|
	    inpush(p, 0, NULL);
 | 
						|
 | 
						|
	    /*
 | 
						|
	     * advance line length and character position over
 | 
						|
	     * prepended string.
 | 
						|
	     */
 | 
						|
	    zlemetall += hptr - chline;
 | 
						|
	    zlemetacs += hptr - chline;
 | 
						|
	} else {
 | 
						|
	    p = (char *) zhalloc(ll + 2);
 | 
						|
	    memcpy(p, linein, ll);
 | 
						|
	    addedspaceptr = p + ll;
 | 
						|
	    *addedspaceptr = ' ';
 | 
						|
	    p[zlemetall] = '\0';
 | 
						|
	    inpush(p, 0, NULL);
 | 
						|
	}
 | 
						|
	zsfree(linein);
 | 
						|
    }
 | 
						|
    if (zlemetacs)
 | 
						|
	zlemetacs--;
 | 
						|
    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 (tok == FOR) {
 | 
						|
	    /*
 | 
						|
	     * The way for (( expr1 ; expr2; expr3 )) is parsed is:
 | 
						|
	     * - a FOR tok
 | 
						|
	     * - a DINPAR with no tokstr
 | 
						|
	     * - two DINPARS with tokstr's expr1, expr2.
 | 
						|
	     * - a DOUTPAR with tokstr expr3.
 | 
						|
	     *
 | 
						|
	     * We'll decrement the variable forloop as we verify
 | 
						|
	     * the various stages.
 | 
						|
	     *
 | 
						|
	     * Don't ask me, ma'am, I'm just the programmer.
 | 
						|
	     */
 | 
						|
	    forloop = 5;
 | 
						|
	} else {
 | 
						|
	    switch (forloop) {
 | 
						|
	    case 1:
 | 
						|
		if (tok != DOUTPAR)
 | 
						|
		    forloop = 0;
 | 
						|
		break;
 | 
						|
 | 
						|
	    case 2:
 | 
						|
	    case 3:
 | 
						|
	    case 4:
 | 
						|
		if (tok != DINPAR)
 | 
						|
		    forloop = 0;
 | 
						|
		break;
 | 
						|
 | 
						|
	    default:
 | 
						|
		/* nothing to do */
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	if (tokstr) {
 | 
						|
	    switch (tok) {
 | 
						|
	    case ENVARRAY:
 | 
						|
		p = dyncat(tokstr, "=(");
 | 
						|
		break;
 | 
						|
 | 
						|
	    case DINPAR:
 | 
						|
		if (forloop) {
 | 
						|
		    /* See above. */
 | 
						|
		    p = dyncat(tokstr, ";");
 | 
						|
		} else {
 | 
						|
		    /*
 | 
						|
		     * Mathematical expressions analysed as a single
 | 
						|
		     * word.  That's correct because it behaves like
 | 
						|
		     * double quotes.  Whitespace in the middle is
 | 
						|
		     * similarly retained, so just add the parentheses back.
 | 
						|
		     */
 | 
						|
		    p = tricat("((", tokstr, "))");
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	    default:
 | 
						|
		p = dupstring(tokstr);
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    if (*p) {
 | 
						|
		untokenize(p);
 | 
						|
		if (ingetptr() == addedspaceptr + 1) {
 | 
						|
		    /*
 | 
						|
		     * Whoops, we've read past the space we added, probably
 | 
						|
		     * because we were expecting a terminator but when
 | 
						|
		     * it didn't turn up we shrugged our shoulders thinking
 | 
						|
		     * it might as well be a complete string anyway.
 | 
						|
		     * So remove the space.  C.f. below for the case
 | 
						|
		     * where the missing terminator caused a lex error.
 | 
						|
		     * We use the same paranoid test.
 | 
						|
		     */
 | 
						|
		    int plen = strlen(p);
 | 
						|
		    if (plen && p[plen-1] == ' ' &&
 | 
						|
			(plen == 1 || p[plen-2] != Meta))
 | 
						|
			p[plen-1] = '\0';
 | 
						|
		}
 | 
						|
		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 (forloop) {
 | 
						|
	    if (forloop == 1) {
 | 
						|
		/*
 | 
						|
		 * Final "))" of for loop to match opening,
 | 
						|
		 * since we've just added the preceding element.
 | 
						|
 		 */
 | 
						|
		addlinknode(list, dupstring("))"));
 | 
						|
	    }
 | 
						|
	    forloop--;
 | 
						|
	}
 | 
						|
	if (!got && !lexflags) {
 | 
						|
	    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;
 | 
						|
    nocomments = onc;
 | 
						|
    noerrs = ne;
 | 
						|
    lexrestore();
 | 
						|
    zlemetacs = ocs;
 | 
						|
    zlemetall = oll;
 | 
						|
    wb = owb;
 | 
						|
    we = owe;
 | 
						|
    addedx = oadx;
 | 
						|
    opts[RCQUOTES] = rcquotes;
 | 
						|
 | 
						|
    if (index)
 | 
						|
	*index = cur;
 | 
						|
 | 
						|
    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);
 | 
						|
	    char *lptr, *wptr = word;
 | 
						|
	    int loop_next = 0, skipping;
 | 
						|
 | 
						|
	    /* Skip stuff at the start of the word */
 | 
						|
	    for (;;) {
 | 
						|
		/*
 | 
						|
		 * Not really an oddity: "\\\n" is
 | 
						|
		 * removed from input as if whitespace.
 | 
						|
		 */
 | 
						|
		if (inblank(*lineptr))
 | 
						|
		    lineptr++;
 | 
						|
		else if (lineptr[0] == '\\' && lineptr[1] == '\n') {
 | 
						|
		    /*
 | 
						|
		     * Optimisation: we handle this in the loop below,
 | 
						|
		     * too.
 | 
						|
		     */
 | 
						|
		    lineptr += 2;
 | 
						|
		} else
 | 
						|
		    break;
 | 
						|
	    }
 | 
						|
	    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 {
 | 
						|
		    /*
 | 
						|
		     * Didn't get to the end of the word.
 | 
						|
		     * See what's amiss.
 | 
						|
		     */
 | 
						|
		    int bad = 0;
 | 
						|
		    /*
 | 
						|
		     * Oddity 1: newlines turn into semicolons.
 | 
						|
		     */
 | 
						|
		    if (!strcmp(wptr, ";"))
 | 
						|
		    {
 | 
						|
			loop_next = 2;
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		    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);
 | 
						|
#endif
 | 
						|
			lineptr = start;
 | 
						|
			nwordpos = 0;
 | 
						|
			uselex = 0;
 | 
						|
			loop_next = 1;
 | 
						|
		    }
 | 
						|
		}
 | 
						|
	    } while (skipping);
 | 
						|
	    if (loop_next) {
 | 
						|
		if (loop_next == 1)
 | 
						|
		    break;
 | 
						|
		continue;
 | 
						|
	    }
 | 
						|
	    /* Record position of current word... */
 | 
						|
	    words[nwordpos++] = lineptr - start;
 | 
						|
	    words[nwordpos++] = lptr - start;
 | 
						|
 | 
						|
	    /* ready for start of next word. */
 | 
						|
	    lineptr = lptr;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    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
 | 
						|
 * 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;
 | 
						|
    if (zleactive)
 | 
						|
	zleentry(ZLE_CMD_SET_HIST_LINE, curhist);
 | 
						|
    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;
 | 
						|
    if (zleactive)
 | 
						|
	zleentry(ZLE_CMD_SET_HIST_LINE, 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;
 | 
						|
}
 |