/* exp_clib.c - top-level functions in the expect C library, libexpect.a
Written by: Don Libes, libes@cme.nist.gov, NIST, 12/3/90
Design and implementation of this program was paid for by U.S. tax
dollars. Therefore it is public domain. However, the author and NIST
would appreciate credit if this program or parts of it are used.
*/
#include "expect_cf.h"
#include <stdio.h>
#include <setjmp.h>
#ifdef HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#include <sys/types.h>
#include <sys/ioctl.h>
#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#ifdef CRAY
# ifndef TCSETCTTY
# if defined(HAVE_TERMIOS)
# include <termios.h>
# else
# include <termio.h>
# endif
# endif
#endif
#ifdef HAVE_SYS_FCNTL_H
# include <sys/fcntl.h>
#else
# include <fcntl.h>
#endif
#ifdef HAVE_STRREDIR_H
#include <sys/strredir.h>
# ifdef SRIOCSREDIR
# undef TIOCCONS
# endif
#endif
#include <signal.h>
/*#include <memory.h> - deprecated - ANSI C moves them into string.h */
#include "string.h"
#include <errno.h>
#ifdef NO_STDLIB_H
/*
* Tcl's compat/stdlib.h
*/
/*
* stdlib.h --
*
* Declares facilities exported by the "stdlib" portion of
* the C library. This file isn't complete in the ANSI-C
* sense; it only declares things that are needed by Tcl.
* This file is needed even on many systems with their own
* stdlib.h (e.g. SunOS) because not all stdlib.h files
* declare all the procedures needed here (such as strtod).
*
* Copyright (c) 1991 The Regents of the University of California.
* Copyright (c) 1994 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* RCS: @(#) $Id: exp_clib.c,v 5.32 2003/09/05 19:01:56 andreas_kupries Exp $
*/
#ifndef _STDLIB
#define _STDLIB
#include <tcl.h>
extern void abort _ANSI_ARGS_((void));
extern double atof _ANSI_ARGS_((CONST char *string));
extern int atoi _ANSI_ARGS_((CONST char *string));
extern long atol _ANSI_ARGS_((CONST char *string));
extern char * calloc _ANSI_ARGS_((unsigned int numElements,
unsigned int size));
extern void exit _ANSI_ARGS_((int status));
extern int free _ANSI_ARGS_((char *blockPtr));
extern char * getenv _ANSI_ARGS_((CONST char *name));
extern char * malloc _ANSI_ARGS_((unsigned int numBytes));
extern void qsort _ANSI_ARGS_((VOID *base, int n, int size,
int (*compar)(CONST VOID *element1, CONST VOID
*element2)));
extern char * realloc _ANSI_ARGS_((char *ptr, unsigned int numBytes));
extern double strtod _ANSI_ARGS_((CONST char *string, char **endPtr));
extern long strtol _ANSI_ARGS_((CONST char *string, char **endPtr,
int base));
extern unsigned long strtoul _ANSI_ARGS_((CONST char *string,
char **endPtr, int base));
#endif /* _STDLIB */
/*
* end of Tcl's compat/stdlib.h
*/
#else
#include <stdlib.h> /* for malloc */
#endif
#include "expect.h"
#define TclRegError exp_TclRegError
/*
* regexp code - from tcl8.0.4/generic/regexp.c
*/
/*
* TclRegComp and TclRegExec -- TclRegSub is elsewhere
*
* Copyright (c) 1986 by University of Toronto.
* Written by Henry Spencer. Not derived from licensed software.
*
* Permission is granted to anyone to use this software for any
* purpose on any computer system, and to redistribute it freely,
* subject to the following restrictions:
*
* 1. The author is not responsible for the consequences of use of
* this software, no matter how awful, even if they arise
* from defects in it.
*
* 2. The origin of this software must not be misrepresented, either
* by explicit claim or by omission.
*
* 3. Altered versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* Beware that some of this code is subtly aware of the way operator
* precedence is structured in regular expressions. Serious changes in
* regular-expression syntax might require a total rethink.
*
* *** NOTE: this code has been altered slightly for use in Tcl: ***
* *** 1. Use ckalloc and ckfree instead of malloc and free. ***
* *** 2. Add extra argument to regexp to specify the real ***
* *** start of the string separately from the start of the ***
* *** current search. This is needed to search for multiple ***
* *** matches within a string. ***
* *** 3. Names have been changed, e.g. from regcomp to ***
* *** TclRegComp, to avoid clashes with other ***
* *** regexp implementations used by applications. ***
* *** 4. Added errMsg declaration and TclRegError procedure ***
* *** 5. Various lint-like things, such as casting arguments ***
* *** in procedure calls. ***
*
* *** NOTE: This code has been altered for use in MT-Sturdy Tcl ***
* *** 1. All use of static variables has been changed to access ***
* *** fields of a structure. ***
* *** 2. This in addition to changes to TclRegError makes the ***
* *** code multi-thread safe. ***
*
* RCS: @(#) $Id: exp_clib.c,v 5.32 2003/09/05 19:01:56 andreas_kupries Exp $
*/
#if 0
#include "tclInt.h"
#include "tclPort.h"
#endif
/*
* The variable below is set to NULL before invoking regexp functions
* and checked after those functions. If an error occurred then TclRegError
* will set the variable to point to a (static) error message. This
* mechanism unfortunately does not support multi-threading, but the
* procedures TclRegError and TclGetRegError can be modified to use
* thread-specific storage for the variable and thereby make the code
* thread-safe.
*/
static char *errMsg = NULL;
/*
* The "internal use only" fields in regexp.h are present to pass info from
* compile to execute that permits the execute phase to run lots faster on
* simple cases. They are:
*
* regstart char that must begin a match; '\0' if none obvious
* reganch is the match anchored (at beginning-of-line only)?
* regmust string (pointer into program) that match must include, or NULL
* regmlen length of regmust string
*
* Regstart and reganch permit very fast decisions on suitable starting points
* for a match, cutting down the work a lot. Regmust permits fast rejection
* of lines that cannot possibly match. The regmust tests are costly enough
* that TclRegComp() supplies a regmust only if the r.e. contains something
* potentially expensive (at present, the only such thing detected is * or +
* at the start of the r.e., which can involve a lot of backup). Regmlen is
* supplied because the test in TclRegExec() needs it and TclRegComp() is
* computing it anyway.
*/
/*
* Structure for regexp "program". This is essentially a linear encoding
* of a nondeterministic finite-state machine (aka syntax charts or
* "railroad normal form" in parsing technology). Each node is an opcode
* plus a "next" pointer, possibly plus an operand. "Next" pointers of
* all nodes except BRANCH implement concatenation; a "next" pointer with
* a BRANCH on both ends of it is connecting two alternatives. (Here we
* have one of the subtle syntax dependencies: an individual BRANCH (as
* opposed to a collection of them) is never concatenated with anything
* because of operator precedence.) The operand of some types of node is
* a literal string; for others, it is a node leading into a sub-FSM. In
* particular, the operand of a BRANCH node is the first node of the branch.
* (NB this is *not* a tree structure: the tail of the branch connects
* to the thing following the set of BRANCHes.) The opcodes are:
*/
/* definition number opnd? meaning */
#define END 0 /* no End of program. */
#define BOL 1 /* no Match "" at beginning of line. */
#define EOL 2 /* no Match "" at end of line. */
#define ANY 3 /* no Match any one character. */
#define ANYOF 4 /* str Match any character in this string. */
#define ANYBUT 5 /* str Match any character not in this string. */
#define BRANCH 6 /* node Match this alternative, or the next... */
#define BACK 7 /* no Match "", "next" ptr points backward. */
#define EXACTLY 8 /* str Match this string. */
#define NOTHING 9 /* no Match empty string. */
#define STAR 10 /* node Match this (simple) thing 0 or more times. */
#define PLUS 11 /* node Match this (simple) thing 1 or more times. */
#define OPEN 20 /* no Mark this point in input as start of #n. */
/* OPEN+1 is number 1, etc. */
#define CLOSE (OPEN+NSUBEXP) /* no Analogous to OPEN. */
/*
* Opcode notes:
*
* BRANCH The set of branches constituting a single choice are hooked
* together with their "next" pointers, since precedence prevents
* anything being concatenated to any individual branch. The
* "next" pointer of the last BRANCH in a choice points to the
* thing following the whole choice. This is also where the
* final "next" pointer of each individual branch points; each
* branch starts with the operand node of a BRANCH node.
*
* BACK Normal "next" pointers all implicitly point forward; BACK
* exists to make loop structures possible.
*
* STAR,PLUS '?', and complex '*' and '+', are implemented as circular
* BRANCH structures using BACK. Simple cases (one character
* per match) are implemented with STAR and PLUS for speed
* and to minimize recursive plunges.
*
* OPEN,CLOSE ...are numbered at compile time.
*/
/*
* A node is one char of opcode followed by two chars of "next" pointer.
* "Next" pointers are stored as two 8-bit pieces, high order first. The
* value is a positive offset from the opcode of the node containing it.
* An operand, if any, simply follows the node. (Note that much of the
* code generation knows about this implicit relationship.)
*
* Using two bytes for the "next" pointer is vast overkill for most things,
* but allows patterns to get big without disasters.
*/
#define OP(p) (*(p))
#define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
#define OPERAND(p) ((p) + 3)
/*
* See regmagic.h for one further detail of program structure.
*/
/*
* Utility definitions.
*/
#ifndef CHARBITS
#define UCHARAT(p) ((int)*(unsigned char *)(p))
#else
#define UCHARAT(p) ((int)*(p)&CHARBITS)
#endif
#define FAIL(m) { TclRegError(m); return(NULL); }
#define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?')
#define META "^$.[()|?+*\\"
/*
* Flags to be passed up and down.
*/
#define HASWIDTH 01 /* Known never to match null string. */
#define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */
#define SPSTART 04 /* Starts with * or +. */
#define WORST 0 /* Worst case. */
/*
* Global work variables for TclRegComp().
*/
struct regcomp_state {
char *regparse; /* Input-scan pointer. */
int regnpar; /* () count. */
char *regcode; /* Code-emit pointer; ®dummy = don't. */
long regsize; /* Code size. */
};
static char regdummy;
/*
* The first byte of the regexp internal "program" is actually this magic
* number; the start node begins in the second byte.
*/
#define MAGIC 0234
/*
* Forward declarations for TclRegComp()'s friends.
*/
static char * reg _ANSI_ARGS_((int paren, int *flagp,
struct regcomp_state *rcstate));
static char * regatom _ANSI_ARGS_((int *flagp,
struct regcomp_state *rcstate));
static char * regbranch _ANSI_ARGS_((int *flagp,
struct regcomp_state *rcstate));
static void regc _ANSI_ARGS_((int b,
struct regcomp_state *rcstate));
static void reginsert _ANSI_ARGS_((int op, char *opnd,
struct regcomp_state *rcstate));
static char * regnext _ANSI_ARGS_((char *p));
static char * regnode _ANSI_ARGS_((int op,
struct regcomp_state *rcstate));
static void regoptail _ANSI_ARGS_((char *p, char *val));
static char * regpiece _ANSI_ARGS_((int *flagp,
struct regcomp_state *rcstate));
static void regtail _ANSI_ARGS_((char *p, char *val));
#ifdef STRCSPN
static int strcspn _ANSI_ARGS_((char *s1, char *s2));
#endif
/*
- TclRegComp - compile a regular expression into internal code
*
* We can't allocate space until we know how big the compiled form will be,
* but we can't compile it (and thus know how big it is) until we've got a
* place to put the code. So we cheat: we compile it twice, once with code
* generation turned off and size counting turned on, and once "for real".
* This also means that we don't allocate space until we are sure that the
* thing really will compile successfully, and we never have to move the
* code and thus invalidate pointers into it. (Note that it has to be in
* one piece because free() must be able to free it all.)
*
* Beware that the optimization-preparation code in here knows about some
* of the structure of the compiled regexp.
*/
regexp *
TclRegComp(exp)
char *exp;
{
register regexp *r;
register char *scan;
register char *longest;
register int len;
int flags;
struct regcomp_state state;
struct regcomp_state *rcstate= &state;
if (exp == NULL)
FAIL("NULL argument");
/* First pass: determine size, legality. */
rcstate->regparse = exp;
rcstate->regnpar = 1;
rcstate->regsize = 0L;
rcstate->regcode = ®dummy;
regc(MAGIC, rcstate);
if (reg(0, &flags, rcstate) == NULL)
return(NULL);
/* Small enough for pointer-storage convention? */
if (rcstate->regsize >= 32767L) /* Probably could be 65535L. */
FAIL("regexp too big");
/* Allocate space. */
r = (regexp *)ckalloc(sizeof(regexp) + (unsigned)rcstate->regsize);
if (r == NULL)
FAIL("out of space");
/* Second pass: emit code. */
rcstate->regparse = exp;
rcstate->regnpar = 1;
rcstate->regcode = r->program;
regc(MAGIC, rcstate);
if (reg(0, &flags, rcstate) == NULL)
return(NULL);
/* Dig out information for optimizations. */
r->regstart = '\0'; /* Worst-case defaults. */
r->reganch = 0;
r->regmust = NULL;
r->regmlen = 0;
scan = r->program+1; /* First BRANCH. */
if (OP(regnext(scan)) == END) { /* Only one top-level choice. */
scan = OPERAND(scan);
/* Starting-point info. */
if (OP(scan) == EXACTLY)
r->regstart = *OPERAND(scan);
else if (OP(scan) == BOL)
r->reganch++;
/*
* If there's something expensive in the r.e., find the
* longest literal string that must appear and make it the
* regmust. Resolve ties in favor of later strings, since
* the regstart check works with the beginning of the r.e.
* and avoiding duplication strengthens checking. Not a
* strong reason, but sufficient in the absence of others.
*/
if (flags&SPSTART) {
longest = NULL;
len = 0;
for (; scan != NULL; scan = regnext(scan))
if (OP(scan) == EXACTLY && ((int) strlen(OPERAND(scan))) >= len) {
longest = OPERAND(scan);
len = strlen(OPERAND(scan));
}
r->regmust = longest;
r->regmlen = len;
}
}
return(r);
}
/*
- reg - regular expression, i.e. main body or parenthesized thing
*
* Caller must absorb opening parenthesis.
*
* Combining parenthesis handling with the base level of regular expression
* is a trifle forced, but the need to tie the tails of the branches to what
* follows makes it hard to avoid.
*/
static char *
reg(paren, flagp, rcstate)
int paren; /* Parenthesized? */
int *flagp;
struct regcomp_state *rcstate;
{
register char *ret;
register char *br;
register char *ender;
register int parno = 0;
int flags;
*flagp = HASWIDTH; /* Tentatively. */
/* Make an OPEN node, if parenthesized. */
if (paren) {
if (rcstate->regnpar >= NSUBEXP)
FAIL("too many ()");
parno = rcstate->regnpar;
rcstate->regnpar++;
ret = regnode(OPEN+parno,rcstate);
} else
ret = NULL;
/* Pick up the branches, linking them together. */
br = regbranch(&flags,rcstate);
if (br == NULL)
return(NULL);
if (ret != NULL)
regtail(ret, br); /* OPEN -> first. */
else
ret = br;
if (!(flags&HASWIDTH))
*flagp &= ~HASWIDTH;
*flagp |= flags&SPSTART;
while (*rcstate->regparse == '|') {
rcstate->regparse++;
br = regbranch(&flags,rcstate);
if (br == NULL)
return(NULL);
regtail(ret, br); /* BRANCH -> BRANCH. */
if (!(flags&HASWIDTH))
*flagp &= ~HASWIDTH;
*flagp |= flags&SPSTART;
}
/* Make a closing node, and hook it on the end. */
ender = regnode((paren) ? CLOSE+parno : END,rcstate);
regtail(ret, ender);
/* Hook the tails of the branches to the closing node. */
for (br = ret; br != NULL; br = regnext(br))
regoptail(br, ender);
/* Check for proper termination. */
if (paren && *rcstate->regparse++ != ')') {
FAIL("unmatched ()");
} else if (!paren && *rcstate->regparse != '\0') {
if (*rcstate->regparse == ')') {
FAIL("unmatched ()");
} else
FAIL("junk on end"); /* "Can't happen". */
/* NOTREACHED */
}
return(ret);
}
/*
- regbranch - one alternative of an | operator
*
* Implements the concatenation operator.
*/
static char *
regbranch(flagp, rcstate)
int *flagp;
struct regcomp_state *rcstate;
{
register char *ret;
register char *chain;
register char *latest;
int flags;
*flagp = WORST; /* Tentatively. */
ret = regnode(BRANCH,rcstate);
chain = NULL;
while (*rcstate->regparse != '\0' && *rcstate->regparse != '|' &&
*rcstate->regparse != ')') {
latest = regpiece(&flags, rcstate);
if (latest == NULL)
return(NULL);
*flagp |= flags&HASWIDTH;
if (chain == NULL) /* First piece. */
*flagp |= flags&SPSTART;
else
regtail(chain, latest);
chain = latest;
}
if (chain == NULL) /* Loop ran zero times. */
(void) regnode(NOTHING,rcstate);
return(ret);
}
/*
- regpiece - something followed by possible [*+?]
*
* Note that the branching code sequences used for ? and the general cases
* of * and + are somewhat optimized: they use the same NOTHING node as
* both the endmarker for their branch list and the body of the last branch.
* It might seem that this node could be dispensed with entirely, but the
* endmarker role is not redundant.
*/
static char *
regpiece(flagp, rcstate)
int *flagp;
struct regcomp_state *rcstate;
{
register char *ret;
register char op;
register char *next;
int flags;
ret = regatom(&flags,rcstate);
if (ret == NULL)
return(NULL);
op = *rcstate->regparse;
if (!ISMULT(op)) {
*flagp = flags;
return(ret);
}
if (!(flags&HASWIDTH) && op != '?')
FAIL("*+ operand could be empty");
*flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
if (op == '*' && (flags&SIMPLE))
reginsert(STAR, ret, rcstate);
else if (op == '*') {
/* Emit x* as (x&|), where & means "self". */
reginsert(BRANCH, ret, rcstate); /* Either x */
regoptail(ret, regnode(BACK,rcstate)); /* and loop */
regoptail(ret, ret); /* back */
regtail(ret, regnode(BRANCH,rcstate)); /* or */
regtail(ret, regnode(NOTHING,rcstate)); /* null. */
} else if (op == '+' && (flags&SIMPLE))
reginsert(PLUS, ret, rcstate);
else if (op == '+') {
/* Emit x+ as x(&|), where & means "self". */
next = regnode(BRANCH,rcstate); /* Either */
regtail(ret, next);
regtail(regnode(BACK,rcstate), ret); /* loop back */
regtail(next, regnode(BRANCH,rcstate)); /* or */
regtail(ret, regnode(NOTHING,rcstate)); /* null. */
} else if (op == '?') {
/* Emit x? as (x|) */
reginsert(BRANCH, ret, rcstate); /* Either x */
regtail(ret, regnode(BRANCH,rcstate)); /* or */
next = regnode(NOTHING,rcstate); /* null. */
regtail(ret, next);
regoptail(ret, next);
}
rcstate->regparse++;
if (ISMULT(*rcstate->regparse))
FAIL("nested *?+");
return(ret);
}
/*
- regatom - the lowest level
*
* Optimization: gobbles an entire sequence of ordinary characters so that
* it can turn them into a single node, which is smaller to store and
* faster to run. Backslashed characters are exceptions, each becoming a
* separate node; the code is simpler that way and it's not worth fixing.
*/
static char *
regatom(flagp, rcstate)
int *flagp;
struct regcomp_state *rcstate;
{
register char *ret;
int flags;
*flagp = WORST; /* Tentatively. */
switch (*rcstate->regparse++) {
case '^':
ret = regnode(BOL,rcstate);
break;
case '$':
ret = regnode(EOL,rcstate);
break;
case '.':
ret = regnode(ANY,rcstate);
*flagp |= HASWIDTH|SIMPLE;
break;
case '[': {
register int clss;
register int classend;
if (*rcstate->regparse == '^') { /* Complement of range. */
ret = regnode(ANYBUT,rcstate);
rcstate->regparse++;
} else
ret = regnode(ANYOF,rcstate);
if (*rcstate->regparse == ']' || *rcstate->regparse == '-')
regc(*rcstate->regparse++,rcstate);
while (*rcstate->regparse != '\0' && *rcstate->regparse != ']') {
if (*rcstate->regparse == '-') {
rcstate->regparse++;
if (*rcstate->regparse == ']' || *rcstate->regparse == '\0')
regc('-',rcstate);
else {
clss = UCHARAT(rcstate->regparse-2)+1;
classend = UCHARAT(rcstate->regparse);
if (clss > classend+1)
FAIL("invalid [] range");
for (; clss <= classend; clss++)
regc((char)clss,rcstate);
rcstate->regparse++;
}
} else
regc(*rcstate->regparse++,rcstate);
}
regc('\0',rcstate);
if (*rcstate->regparse != ']')
FAIL("unmatched []");
rcstate->regparse++;
*flagp |= HASWIDTH|SIMPLE;
}
break;
case '(':
ret = reg(1, &flags, rcstate);
if (ret == NULL)
return(NULL);
*flagp |= flags&(HASWIDTH|SPSTART);
break;
case '\0':
case '|':
case ')':
FAIL("internal urp"); /* Supposed to be caught earlier. */
/* NOTREACHED */
case '?':
case '+':
case '*':
FAIL("?+* follows nothing");
/* NOTREACHED */
case '\\':
if (*rcstate->regparse == '\0')
FAIL("trailing \\");
ret = regnode(EXACTLY,rcstate);
regc(*rcstate->regparse++,rcstate);
regc('\0',rcstate);
*flagp |= HASWIDTH|SIMPLE;
break;
default: {
register int len;
register char ender;
rcstate->regparse--;
len = strcspn(rcstate->regparse, META);
if (len <= 0)
FAIL("internal disaster");
ender = *(rcstate->regparse+len);
if (len > 1 && ISMULT(ender))
len--; /* Back off clear of ?+* operand. */
*flagp |= HASWIDTH;
if (len == 1)
*flagp |= SIMPLE;
ret = regnode(EXACTLY,rcstate);
while (len > 0) {
regc(*rcstate->regparse++,rcstate);
len--;
}
regc('\0',rcstate);
}
break;
}
return(ret);
}
/*
- regnode - emit a node
*/
static char * /* Location. */
regnode(op, rcstate)
int op;
struct regcomp_state *rcstate;
{
register char *ret;
register char *ptr;
ret = rcstate->regcode;
if (ret == ®dummy) {
rcstate->regsize += 3;
return(ret);
}
ptr = ret;
*ptr++ = (char)op;
*ptr++ = '\0'; /* Null "next" pointer. */
*ptr++ = '\0';
rcstate->regcode = ptr;
return(ret);
}
/*
- regc - emit (if appropriate) a byte of code
*/
static void
regc(b, rcstate)
int b;
struct regcomp_state *rcstate;
{
if (rcstate->regcode != ®dummy)
*rcstate->regcode++ = (char)b;
else
rcstate->regsize++;
}
/*
- reginsert - insert an operator in front of already-emitted operand
*
* Means relocating the operand.
*/
static void
reginsert(op, opnd, rcstate)
int op;
char *opnd;
struct regcomp_state *rcstate;
{
register char *src;
register char *dst;
register char *place;
if (rcstate->regcode == ®dummy) {
rcstate->regsize += 3;
return;
}
src = rcstate->regcode;
rcstate->regcode += 3;
dst = rcstate->regcode;
while (src > opnd)
*--dst = *--src;
place = opnd; /* Op node, where operand used to be. */
*place++ = (char)op;
*place++ = '\0';
*place = '\0';
}
/*
- regtail - set the next-pointer at the end of a node chain
*/
static void
regtail(p, val)
char *p;
char *val;
{
register char *scan;
register char *temp;
register int offset;
if (p == ®dummy)
return;
/* Find last node. */
scan = p;
for (;;) {
temp = regnext(scan);
if (temp == NULL)
break;
scan = temp;
}
if (OP(scan) == BACK)
offset = scan - val;
else
offset = val - scan;
*(scan+1) = (char)((offset>>8)&0377);
*(scan+2) = (char)(offset&0377);
}
/*
- regoptail - regtail on operand of first argument; nop if operandless
*/
static void
regoptail(p, val)
char *p;
char *val;
{
/* "Operandless" and "op != BRANCH" are synonymous in practice. */
if (p == NULL || p == ®dummy || OP(p) != BRANCH)
return;
regtail(OPERAND(p), val);
}
/*
* TclRegExec and friends
*/
/*
* Global work variables for TclRegExec().
*/
struct regexec_state {
char *reginput; /* String-input pointer. */
char *regbol; /* Beginning of input, for ^ check. */
char **regstartp; /* Pointer to startp array. */
char **regendp; /* Ditto for endp. */
};
/*
* Forwards.
*/
static int regtry _ANSI_ARGS_((regexp *prog, char *string,
struct regexec_state *restate));
static int regmatch _ANSI_ARGS_((char *prog,
struct regexec_state *restate));
static int regrepeat _ANSI_ARGS_((char *p,
struct regexec_state *restate));
#ifdef DEBUG
int regnarrate = 0;
void regdump _ANSI_ARGS_((regexp *r));
static char *regprop _ANSI_ARGS_((char *op));
#endif
/*
- TclRegExec - match a regexp against a string
*/
int
TclRegExec(prog, string, start)
register regexp *prog;
register char *string;
char *start;
{
register char *s;
struct regexec_state state;
struct regexec_state *restate= &state;
/* Be paranoid... */
if (prog == NULL || string == NULL) {
TclRegError("NULL parameter");
return(0);
}
/* Check validity of program. */
if (UCHARAT(prog->program) != MAGIC) {
TclRegError("corrupted program");
return(0);
}
/* If there is a "must appear" string, look for it. */
if (prog->regmust != NULL) {
s = string;
while ((s = strchr(s, prog->regmust[0])) != NULL) {
if (strncmp(s, prog->regmust, (size_t) prog->regmlen)
== 0)
break; /* Found it. */
s++;
}
if (s == NULL) /* Not present. */
return(0);
}
/* Mark beginning of line for ^ . */
restate->regbol = start;
/* Simplest case: anchored match need be tried only once. */
if (prog->reganch)
return(regtry(prog, string, restate));
/* Messy cases: unanchored match. */
s = string;
if (prog->regstart != '\0')
/* We know what char it must start with. */
while ((s = strchr(s, prog->regstart)) != NULL) {
if (regtry(prog, s, restate))
return(1);
s++;
}
else
/* We don't -- general case. */
do {
if (regtry(prog, s, restate))
return(1);
} while (*s++ != '\0');
/* Failure. */
return(0);
}
/*
- regtry - try match at specific point
*/
static int /* 0 failure, 1 success */
regtry(prog, string, restate)
regexp *prog;
char *string;
struct regexec_state *restate;
{
register int i;
register char **sp;
register char **ep;
restate->reginput = string;
restate->regstartp = prog->startp;
restate->regendp = prog->endp;
sp = prog->startp;
ep = prog->endp;
for (i = NSUBEXP; i > 0; i--) {
*sp++ = NULL;
*ep++ = NULL;
}
if (regmatch(prog->program + 1,restate)) {
prog->startp[0] = string;
prog->endp[0] = restate->reginput;
return(1);
} else
return(0);
}
/*
- regmatch - main matching routine
*
* Conceptually the strategy is simple: check to see whether the current
* node matches, call self recursively to see whether the rest matches,
* and then act accordingly. In practice we make some effort to avoid
* recursion, in particular by going through "ordinary" nodes (that don't
* need to know whether the rest of the match failed) by a loop instead of
* by recursion.
*/
static int /* 0 failure, 1 success */
regmatch(prog, restate)
char *prog;
struct regexec_state *restate;
{
register char *scan; /* Current node. */
char *next; /* Next node. */
scan = prog;
#ifdef DEBUG
if (scan != NULL && regnarrate)
fprintf(stderr, "%s(\n", regprop(scan));
#endif
while (scan != NULL) {
#ifdef DEBUG
if (regnarrate)
fprintf(stderr, "%s...\n", regprop(scan));
#endif
next = regnext(scan);
switch (OP(scan)) {
case BOL:
if (restate->reginput != restate->regbol) {
return 0;
}
break;
case EOL:
if (*restate->reginput != '\0') {
return 0;
}
break;
case ANY:
if (*restate->reginput == '\0') {
return 0;
}
restate->reginput++;
break;
case EXACTLY: {
register int len;
register char *opnd;
opnd = OPERAND(scan);
/* Inline the first character, for speed. */
if (*opnd != *restate->reginput) {
return 0 ;
}
len = strlen(opnd);
if (len > 1 && strncmp(opnd, restate->reginput, (size_t) len)
!= 0) {
return 0;
}
restate->reginput += len;
break;
}
case ANYOF:
if (*restate->reginput == '\0'
|| strchr(OPERAND(scan), *restate->reginput) == NULL) {
return 0;
}
restate->reginput++;
break;
case ANYBUT:
if (*restate->reginput == '\0'
|| strchr(OPERAND(scan), *restate->reginput) != NULL) {
return 0;
}
restate->reginput++;
break;
case NOTHING:
break;
case BACK:
break;
case OPEN+1:
case OPEN+2:
case OPEN+3:
case OPEN+4:
case OPEN+5:
case OPEN+6:
case OPEN+7:
case OPEN+8:
case OPEN+9: {
register int no;
register char *save;
doOpen:
no = OP(scan) - OPEN;
save = restate->reginput;
if (regmatch(next,restate)) {
/*
* Don't set startp if some later invocation of the
* same parentheses already has.
*/
if (restate->regstartp[no] == NULL) {
restate->regstartp[no] = save;
}
return 1;
} else {
return 0;
}
}
case CLOSE+1:
case CLOSE+2:
case CLOSE+3:
case CLOSE+4:
case CLOSE+5:
case CLOSE+6:
case CLOSE+7:
case CLOSE+8:
case CLOSE+9: {
register int no;
register char *save;
doClose:
no = OP(scan) - CLOSE;
save = restate->reginput;
if (regmatch(next,restate)) {
/*
* Don't set endp if some later
* invocation of the same parentheses
* already has.
*/
if (restate->regendp[no] == NULL)
restate->regendp[no] = save;
return 1;
} else {
return 0;
}
}
case BRANCH: {
register char *save;
if (OP(next) != BRANCH) { /* No choice. */
next = OPERAND(scan); /* Avoid recursion. */
} else {
do {
save = restate->reginput;
if (regmatch(OPERAND(scan),restate))
return(1);
restate->reginput = save;
scan = regnext(scan);
} while (scan != NULL && OP(scan) == BRANCH);
return 0;
}
break;
}
case STAR:
case PLUS: {
register char nextch;
register int no;
register char *save;
register int min;
/*
* Lookahead to avoid useless match attempts
* when we know what character comes next.
*/
nextch = '\0';
if (OP(next) == EXACTLY)
nextch = *OPERAND(next);
min = (OP(scan) == STAR) ? 0 : 1;
save = restate->reginput;
no = regrepeat(OPERAND(scan),restate);
while (no >= min) {
/* If it could work, try it. */
if (nextch == '\0' || *restate->reginput == nextch)
if (regmatch(next,restate))
return(1);
/* Couldn't or didn't -- back up. */
no--;
restate->reginput = save + no;
}
return(0);
}
case END:
return(1); /* Success! */
default:
if (OP(scan) > OPEN && OP(scan) < OPEN+NSUBEXP) {
goto doOpen;
} else if (OP(scan) > CLOSE && OP(scan) < CLOSE+NSUBEXP) {
goto doClose;
}
TclRegError("memory corruption");
return 0;
}
scan = next;
}
/*
* We get here only if there's trouble -- normally "case END" is
* the terminating point.
*/
TclRegError("corrupted pointers");
return(0);
}
/*
- regrepeat - repeatedly match something simple, report how many
*/
static int
regrepeat(p, restate)
char *p;
struct regexec_state *restate;
{
register int count = 0;
register char *scan;
register char *opnd;
scan = restate->reginput;
opnd = OPERAND(p);
switch (OP(p)) {
case ANY:
count = strlen(scan);
scan += count;
break;
case EXACTLY:
while (*opnd == *scan) {
count++;
scan++;
}
break;
case ANYOF:
while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
count++;
scan++;
}
break;
case ANYBUT:
while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
count++;
scan++;
}
break;
default: /* Oh dear. Called inappropriately. */
TclRegError("internal foulup");
count = 0; /* Best compromise. */
break;
}
restate->reginput = scan;
return(count);
}
/*
- regnext - dig the "next" pointer out of a node
*/
static char *
regnext(p)
register char *p;
{
register int offset;
if (p == ®dummy)
return(NULL);
offset = NEXT(p);
if (offset == 0)
return(NULL);
if (OP(p) == BACK)
return(p-offset);
else
return(p+offset);
}
#ifdef DEBUG
static char *regprop();
/*
- regdump - dump a regexp onto stdout in vaguely comprehensible form
*/
void
regdump(r)
regexp *r;
{
register char *s;
register char op = EXACTLY; /* Arbitrary non-END op. */
register char *next;
s = r->program + 1;
while (op != END) { /* While that wasn't END last time... */
op = OP(s);
printf("%2d%s", s-r->program, regprop(s)); /* Where, what. */
next = regnext(s);
if (next == NULL) /* Next ptr. */
printf("(0)");
else
printf("(%d)", (s-r->program)+(next-s));
s += 3;
if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
/* Literal string, where present. */
while (*s != '\0') {
putchar(*s);
s++;
}
s++;
}
putchar('\n');
}
/* Header fields of interest. */
if (r->regstart != '\0')
printf("start `%c' ", r->regstart);
if (r->reganch)
printf("anchored ");
if (r->regmust != NULL)
printf("must have \"%s\"", r->regmust);
printf("\n");
}
/*
- regprop - printable representation of opcode
*/
static char *
regprop(op)
char *op;
{
register char *p;
static char buf[50];
(void) strcpy(buf, ":");
switch (OP(op)) {
case BOL:
p = "BOL";
break;
case EOL:
p = "EOL";
break;
case ANY:
p = "ANY";
break;
case ANYOF:
p = "ANYOF";
break;
case ANYBUT:
p = "ANYBUT";
break;
case BRANCH:
p = "BRANCH";
break;
case EXACTLY:
p = "EXACTLY";
break;
case NOTHING:
p = "NOTHING";
break;
case BACK:
p = "BACK";
break;
case END:
p = "END";
break;
case OPEN+1:
case OPEN+2:
case OPEN+3:
case OPEN+4:
case OPEN+5:
case OPEN+6:
case OPEN+7:
case OPEN+8:
case OPEN+9:
sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
p = NULL;
break;
case CLOSE+1:
case CLOSE+2:
case CLOSE+3:
case CLOSE+4:
case CLOSE+5:
case CLOSE+6:
case CLOSE+7:
case CLOSE+8:
case CLOSE+9:
sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
p = NULL;
break;
case STAR:
p = "STAR";
break;
case PLUS:
p = "PLUS";
break;
default:
if (OP(op) > OPEN && OP(op) < OPEN+NSUBEXP) {
sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
p = NULL;
break;
} else if (OP(op) > CLOSE && OP(op) < CLOSE+NSUBEXP) {
sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
p = NULL;
} else {
TclRegError("corrupted opcode");
}
break;
}
if (p != NULL)
(void) strcat(buf, p);
return(buf);
}
#endif
/*
* The following is provided for those people who do not have strcspn() in
* their C libraries. They should get off their butts and do something
* about it; at least one public-domain implementation of those (highly
* useful) string routines has been published on Usenet.
*/
#ifdef STRCSPN
/*
* strcspn - find length of initial segment of s1 consisting entirely
* of characters not from s2
*/
static int
strcspn(s1, s2)
char *s1;
char *s2;
{
register char *scan1;
register char *scan2;
register int count;
count = 0;
for (scan1 = s1; *scan1 != '\0'; scan1++) {
for (scan2 = s2; *scan2 != '\0';) /* ++ moved down. */
if (*scan1 == *scan2++)
return(count);
count++;
}
return(count);
}
#endif
/*
*----------------------------------------------------------------------
*
* TclRegError --
*
* This procedure is invoked by the regexp code when an error
* occurs. It saves the error message so it can be seen by the
* code that called Spencer's code.
*
* Results:
* None.
*
* Side effects:
* The value of "string" is saved in "errMsg".
*
*----------------------------------------------------------------------
*/
void
exp_TclRegError(string)
char *string; /* Error message. */
{
errMsg = string;
}
char *
TclGetRegError()
{
return errMsg;
}
/*
* end of regexp definitions and code
*/
/*
* following stolen from tcl8.0.4/generic/tclPosixStr.c
*/
/*
*----------------------------------------------------------------------
*
* Tcl_ErrnoMsg --
*
* Return a human-readable message corresponding to a given
* errno value.
*
* Results:
* The return value is the standard POSIX error message for
* errno. This procedure is used instead of strerror because
* strerror returns slightly different values on different
* machines (e.g. different capitalizations), which cause
* problems for things such as regression tests. This procedure
* provides messages for most standard errors, then it calls
* strerror for things it doesn't understand.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static
char *
Tcl_ErrnoMsg(err)
int err; /* Error number (such as in errno variable). */
{
switch (err) {
#ifdef E2BIG
case E2BIG: return "argument list too long";
#endif
#ifdef EACCES
case EACCES: return "permission denied";
#endif
#ifdef EADDRINUSE
case EADDRINUSE: return "address already in use";
#endif
#ifdef EADDRNOTAVAIL
case EADDRNOTAVAIL: return "can't assign requested address";
#endif
#ifdef EADV
case EADV: return "advertise error";
#endif
#ifdef EAFNOSUPPORT
case EAFNOSUPPORT: return "address family not supported by protocol family";
#endif
#ifdef EAGAIN
case EAGAIN: return "resource temporarily unavailable";
#endif
#ifdef EALIGN
case EALIGN: return "EALIGN";
#endif
#if defined(EALREADY) && (!defined(EBUSY) || (EALREADY != EBUSY ))
case EALREADY: return "operation already in progress";
#endif
#ifdef EBADE
case EBADE: return "bad exchange descriptor";
#endif
#ifdef EBADF
case EBADF: return "bad file number";
#endif
#ifdef EBADFD
case EBADFD: return "file descriptor in bad state";
#endif
#ifdef EBADMSG
case EBADMSG: return "not a data message";
#endif
#ifdef EBADR
case EBADR: return "bad request descriptor";
#endif
#ifdef EBADRPC
case EBADRPC: return "RPC structure is bad";
#endif
#ifdef EBADRQC
case EBADRQC: return "bad request code";
#endif
#ifdef EBADSLT
case EBADSLT: return "invalid slot";
#endif
#ifdef EBFONT
case EBFONT: return "bad font file format";
#endif
#ifdef EBUSY
case EBUSY: return "file busy";
#endif
#ifdef ECHILD
case ECHILD: return "no children";
#endif
#ifdef ECHRNG
case ECHRNG: return "channel number out of range";
#endif
#ifdef ECOMM
case ECOMM: return "communication error on send";
#endif
#ifdef ECONNABORTED
case ECONNABORTED: return "software caused connection abort";
#endif
#ifdef ECONNREFUSED
case ECONNREFUSED: return "connection refused";
#endif
#ifdef ECONNRESET
case ECONNRESET: return "connection reset by peer";
#endif
#if defined(EDEADLK) && (!defined(EWOULDBLOCK) || (EDEADLK != EWOULDBLOCK))
case EDEADLK: return "resource deadlock avoided";
#endif
#if defined(EDEADLOCK) && (!defined(EDEADLK) || (EDEADLOCK != EDEADLK))
case EDEADLOCK: return "resource deadlock avoided";
#endif
#ifdef EDESTADDRREQ
case EDESTADDRREQ: return "destination address required";
#endif
#ifdef EDIRTY
case EDIRTY: return "mounting a dirty fs w/o force";
#endif
#ifdef EDOM
case EDOM: return "math argument out of range";
#endif
#ifdef EDOTDOT
case EDOTDOT: return "cross mount point";
#endif
#ifdef EDQUOT
case EDQUOT: return "disk quota exceeded";
#endif
#ifdef EDUPPKG
case EDUPPKG: return "duplicate package name";
#endif
#ifdef EEXIST
case EEXIST: return "file already exists";
#endif
#ifdef EFAULT
case EFAULT: return "bad address in system call argument";
#endif
#ifdef EFBIG
case EFBIG: return "file too large";
#endif
#ifdef EHOSTDOWN
case EHOSTDOWN: return "host is down";
#endif
#ifdef EHOSTUNREACH
case EHOSTUNREACH: return "host is unreachable";
#endif
#if defined(EIDRM) && (!defined(EINPROGRESS) || (EIDRM != EINPROGRESS))
case EIDRM: return "identifier removed";
#endif
#ifdef EINIT
case EINIT: return "initialization error";
#endif
#ifdef EINPROGRESS
case EINPROGRESS: return "operation now in progress";
#endif
#ifdef EINTR
case EINTR: return "interrupted system call";
#endif
#ifdef EINVAL
case EINVAL: return "invalid argument";
#endif
#ifdef EIO
case EIO: return "I/O error";
#endif
#ifdef EISCONN
case EISCONN: return "socket is already connected";
#endif
#ifdef EISDIR
case EISDIR: return "illegal operation on a directory";
#endif
#ifdef EISNAME
case EISNAM: return "is a name file";
#endif
#ifdef ELBIN
case ELBIN: return "ELBIN";
#endif
#ifdef EL2HLT
case EL2HLT: return "level 2 halted";
#endif
#ifdef EL2NSYNC
case EL2NSYNC: return "level 2 not synchronized";
#endif
#ifdef EL3HLT
case EL3HLT: return "level 3 halted";
#endif
#ifdef EL3RST
case EL3RST: return "level 3 reset";
#endif
#ifdef ELIBACC
case ELIBACC: return "can not access a needed shared library";
#endif
#ifdef ELIBBAD
case ELIBBAD: return "accessing a corrupted shared library";
#endif
#ifdef ELIBEXEC
case ELIBEXEC: return "can not exec a shared library directly";
#endif
#ifdef ELIBMAX
case ELIBMAX: return
"attempting to link in more shared libraries than system limit";
#endif
#ifdef ELIBSCN
case ELIBSCN: return ".lib section in a.out corrupted";
#endif
#ifdef ELNRNG
case ELNRNG: return "link number out of range";
#endif
#if defined(ELOOP) && (!defined(ENOENT) || (ELOOP != ENOENT))
case ELOOP: return "too many levels of symbolic links";
#endif
#ifdef EMFILE
case EMFILE: return "too many open files";
#endif
#ifdef EMLINK
case EMLINK: return "too many links";
#endif
#ifdef EMSGSIZE
case EMSGSIZE: return "message too long";
#endif
#ifdef EMULTIHOP
case EMULTIHOP: return "multihop attempted";
#endif
#ifdef ENAMETOOLONG
case ENAMETOOLONG: return "file name too long";
#endif
#ifdef ENAVAIL
case ENAVAIL: return "not available";
#endif
#ifdef ENET
case ENET: return "ENET";
#endif
#ifdef ENETDOWN
case ENETDOWN: return "network is down";
#endif
#ifdef ENETRESET
case ENETRESET: return "network dropped connection on reset";
#endif
#ifdef ENETUNREACH
case ENETUNREACH: return "network is unreachable";
#endif
#ifdef ENFILE
case ENFILE: return "file table overflow";
#endif
#ifdef ENOANO
case ENOANO: return "anode table overflow";
#endif
#if defined(ENOBUFS) && (!defined(ENOSR) || (ENOBUFS != ENOSR))
case ENOBUFS: return "no buffer space available";
#endif
#ifdef ENOCSI
case ENOCSI: return "no CSI structure available";
#endif
#if defined(ENODATA) && (!defined(ECONNREFUSED) || (ENODATA != ECONNREFUSED))
case ENODATA: return "no data available";
#endif
#ifdef ENODEV
case ENODEV: return "no such device";
#endif
#ifdef ENOENT
case ENOENT: return "no such file or directory";
#endif
#ifdef ENOEXEC
case ENOEXEC: return "exec format error";
#endif
#ifdef ENOLCK
case ENOLCK: return "no locks available";
#endif
#ifdef ENOLINK
case ENOLINK: return "link has be severed";
#endif
#ifdef ENOMEM
case ENOMEM: return "not enough memory";
#endif
#ifdef ENOMSG
case ENOMSG: return "no message of desired type";
#endif
#ifdef ENONET
case ENONET: return "machine is not on the network";
#endif
#ifdef ENOPKG
case ENOPKG: return "package not installed";
#endif
#ifdef ENOPROTOOPT
case ENOPROTOOPT: return "bad proocol option";
#endif
#ifdef ENOSPC
case ENOSPC: return "no space left on device";
#endif
#if defined(ENOSR) && (!defined(ENAMETOOLONG) || (ENAMETOOLONG != ENOSR))
case ENOSR: return "out of stream resources";
#endif
#if defined(ENOSTR) && (!defined(ENOTTY) || (ENOTTY != ENOSTR))
case ENOSTR: return "not a stream device";
#endif
#ifdef ENOSYM
case ENOSYM: return "unresolved symbol name";
#endif
#ifdef ENOSYS
case ENOSYS: return "function not implemented";
#endif
#ifdef ENOTBLK
case ENOTBLK: return "block device required";
#endif
#ifdef ENOTCONN
case ENOTCONN: return "socket is not connected";
#endif
#ifdef ENOTDIR
case ENOTDIR: return "not a directory";
#endif
#if defined(ENOTEMPTY) && (!defined(EEXIST) || (ENOTEMPTY != EEXIST))
case ENOTEMPTY: return "directory not empty";
#endif
#ifdef ENOTNAM
case ENOTNAM: return "not a name file";
#endif
#ifdef ENOTSOCK
case ENOTSOCK: return "socket operation on non-socket";
#endif
#ifdef ENOTSUP
case ENOTSUP: return "operation not supported";
#endif
#ifdef ENOTTY
case ENOTTY: return "inappropriate device for ioctl";
#endif
#ifdef ENOTUNIQ
case ENOTUNIQ: return "name not unique on network";
#endif
#ifdef ENXIO
case ENXIO: return "no such device or address";
#endif
#if defined(EOPNOTSUPP) && (!defined(ENOTSUP) || (ENOTSUP != EOPNOTSUPP))
case EOPNOTSUPP: return "operation not supported on socket";
#endif
#ifdef EPERM
case EPERM: return "not owner";
#endif
#if defined(EPFNOSUPPORT) && (!defined(ENOLCK) || (ENOLCK != EPFNOSUPPORT))
case EPFNOSUPPORT: return "protocol family not supported";
#endif
#ifdef EPIPE
case EPIPE: return "broken pipe";
#endif
#ifdef EPROCLIM
case EPROCLIM: return "too many processes";
#endif
#ifdef EPROCUNAVAIL
case EPROCUNAVAIL: return "bad procedure for program";
#endif
#ifdef EPROGMISMATCH
case EPROGMISMATCH: return "program version wrong";
#endif
#ifdef EPROGUNAVAIL
case EPROGUNAVAIL: return "RPC program not available";
#endif
#ifdef EPROTO
case EPROTO: return "protocol error";
#endif
#ifdef EPROTONOSUPPORT
case EPROTONOSUPPORT: return "protocol not suppored";
#endif
#ifdef EPROTOTYPE
case EPROTOTYPE: return "protocol wrong type for socket";
#endif
#ifdef ERANGE
case ERANGE: return "math result unrepresentable";
#endif
#if defined(EREFUSED) && (!defined(ECONNREFUSED) || (EREFUSED != ECONNREFUSED))
case EREFUSED: return "EREFUSED";
#endif
#ifdef EREMCHG
case EREMCHG: return "remote address changed";
#endif
#ifdef EREMDEV
case EREMDEV: return "remote device";
#endif
#ifdef EREMOTE
case EREMOTE: return "pathname hit remote file system";
#endif
#ifdef EREMOTEIO
case EREMOTEIO: return "remote i/o error";
#endif
#ifdef EREMOTERELEASE
case EREMOTERELEASE: return "EREMOTERELEASE";
#endif
#ifdef EROFS
case EROFS: return "read-only file system";
#endif
#ifdef ERPCMISMATCH
case ERPCMISMATCH: return "RPC version is wrong";
#endif
#ifdef ERREMOTE
case ERREMOTE: return "object is remote";
#endif
#ifdef ESHUTDOWN
case ESHUTDOWN: return "can't send afer socket shutdown";
#endif
#ifdef ESOCKTNOSUPPORT
case ESOCKTNOSUPPORT: return "socket type not supported";
#endif
#ifdef ESPIPE
case ESPIPE: return "invalid seek";
#endif
#ifdef ESRCH
case ESRCH: return "no such process";
#endif
#ifdef ESRMNT
case ESRMNT: return "srmount error";
#endif
#ifdef ESTALE
case ESTALE: return "stale remote file handle";
#endif
#ifdef ESUCCESS
case ESUCCESS: return "Error 0";
#endif
#if defined(ETIME) && (!defined(ELOOP) || (ETIME != ELOOP))
case ETIME: return "timer expired";
#endif
#if defined(ETIMEDOUT) && (!defined(ENOSTR) || (ETIMEDOUT != ENOSTR))
case ETIMEDOUT: return "connection timed out";
#endif
#ifdef ETOOMANYREFS
case ETOOMANYREFS: return "too many references: can't splice";
#endif
#ifdef ETXTBSY
case ETXTBSY: return "text file or pseudo-device busy";
#endif
#ifdef EUCLEAN
case EUCLEAN: return "structure needs cleaning";
#endif
#ifdef EUNATCH
case EUNATCH: return "protocol driver not attached";
#endif
#ifdef EUSERS
case EUSERS: return "too many users";
#endif
#ifdef EVERSION
case EVERSION: return "version mismatch";
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
case EWOULDBLOCK: return "operation would block";
#endif
#ifdef EXDEV
case EXDEV: return "cross-domain link";
#endif
#ifdef EXFULL
case EXFULL: return "message tables full";
#endif
default:
#ifdef NO_STRERROR
return "unknown POSIX error";
#else
return strerror(errno);
#endif
}
}
/*
* end of excerpt from tcl8.0.X/generic/tclPosixStr.c
*/
/*
* stolen from exp_log.c - this function is called from the Expect library
* but the one that the library supplies calls Tcl functions. So we supply
* our own.
*/
static
void
expDiagLogU(str)
char *str;
{
if (exp_is_debugging) {
fprintf(stderr,str);
if (exp_logfile) fprintf(exp_logfile,str);
}
}
/*
* expect-specific definitions and code
*/
#include "expect.h"
#include "exp_int.h"
/* exp_glob.c - expect functions for doing glob
*
* Based on Tcl's glob functions but modified to support anchors and to
* return information about the possibility of future matches
*
* Modifications by: Don Libes, NIST, 2/6/90
*/
/* The following functions implement expect's glob-style string
* matching Exp_StringMatch allow's implements the unanchored front
* (or conversely the '^') feature. Exp_StringMatch2 does the rest of
* the work.
*/
/* Exp_StringMatch2 --
*
* Like Tcl_StringMatch except that
* 1) returns number of characters matched, -1 if failed.
* (Can return 0 on patterns like "" or "$")
* 2) does not require pattern to match to end of string
* 3) much of code is stolen from Tcl_StringMatch
* 4) front-anchor is assumed (Tcl_StringMatch retries for non-front-anchor)
*/
static
int
Exp_StringMatch2(string,pattern)
register char *string; /* String. */
register char *pattern; /* Pattern, which may contain
* special characters. */
{
char c2;
int match = 0; /* # of chars matched */
while (1) {
/* If at end of pattern, success! */
if (*pattern == 0) {
return match;
}
/* If last pattern character is '$', verify that entire
* string has been matched.
*/
if ((*pattern == '$') && (pattern[1] == 0)) {
if (*string == 0) return(match);
else return(-1);
}
/* Check for a "*" as the next pattern character. It matches
* any substring. We handle this by calling ourselves
* recursively for each postfix of string, until either we
* match or we reach the end of the string.
*/
if (*pattern == '*') {
int head_len;
char *tail;
pattern += 1;
if (*pattern == 0) {
return(strlen(string)+match); /* DEL */
}
/* find longest match - switched to this on 12/31/93 */
head_len = strlen(string); /* length before tail */
tail = string + head_len;
while (head_len >= 0) {
int rc;
if (-1 != (rc = Exp_StringMatch2(tail, pattern))) {
return rc + match + head_len; /* DEL */
}
tail--;
head_len--;
}
return -1; /* DEL */
}
/*
* after this point, all patterns must match at least one
* character, so check this
*/
if (*string == 0) return -1;
/* Check for a "?" as the next pattern character. It matches
* any single character.
*/
if (*pattern == '?') {
goto thisCharOK;
}
/* Check for a "[" as the next pattern character. It is followed
* by a list of characters that are acceptable, or by a range
* (two characters separated by "-").
*/
if (*pattern == '[') {
pattern += 1;
while (1) {
if ((*pattern == ']') || (*pattern == 0)) {
return -1; /* was 0; DEL */
}
if (*pattern == *string) {
break;
}
if (pattern[1] == '-') {
c2 = pattern[2];
if (c2 == 0) {
return -1; /* DEL */
}
if ((*pattern <= *string) && (c2 >= *string)) {
break;
}
if ((*pattern >= *string) && (c2 <= *string)) {
break;
}
pattern += 2;
}
pattern += 1;
}
while (*pattern != ']') {
if (*pattern == 0) {
pattern--;
break;
}
pattern += 1;
}
goto thisCharOK;
}
/* If the next pattern character is backslash, strip it off
* so we do exact matching on the character that follows.
*/
if (*pattern == '\\') {
pattern += 1;
if (*pattern == 0) {
return -1;
}
}
/* There's no special character. Just make sure that the next
* characters of each string match.
*/
if (*pattern != *string) {
return -1;
}
thisCharOK: pattern += 1;
string += 1;
match++;
}
}
static
int /* returns # of chars that matched */
Exp_StringMatch(string, pattern,offset)
char *string;
char *pattern;
int *offset; /* offset from beginning of string where pattern matches */
{
char *s;
int sm; /* count of chars matched or -1 */
int caret = FALSE;
int star = FALSE;
*offset = 0;
if (pattern[0] == '^') {
caret = TRUE;
pattern++;
} else if (pattern[0] == '*') {
star = TRUE;
}
/*
* test if pattern matches in initial position.
* This handles front-anchor and 1st iteration of non-front-anchor.
* Note that 1st iteration must be tried even if string is empty.
*/
sm = Exp_StringMatch2(string,pattern);
if (sm >= 0) return(sm);
if (caret) return -1;
if (star) return -1;
if (*string == '\0') return -1;
for (s = string+1;*s;s++) {
sm = Exp_StringMatch2(s,pattern);
if (sm != -1) {
*offset = s-string;
return(sm);
}
}
return -1;
}
#define EXP_MATCH_MAX 2000
/* public */
char *exp_buffer = 0;
char *exp_buffer_end = 0;
char *exp_match = 0;
char *exp_match_end = 0;
int exp_match_max = EXP_MATCH_MAX; /* bytes */
int exp_full_buffer = FALSE; /* don't return on full buffer */
int exp_remove_nulls = TRUE;
int exp_timeout = 10; /* seconds */
int exp_pty_timeout = 5; /* seconds - see CRAY below */
int exp_autoallocpty = TRUE; /* if TRUE, we do allocation */
int exp_pty[2]; /* master is [0], slave is [1] */
int exp_pid;
char *exp_stty_init = 0; /* initial stty args */
int exp_ttycopy = TRUE; /* copy tty parms from /dev/tty */
int exp_ttyinit = TRUE; /* set tty parms to sane state */
int exp_console = FALSE; /* redirect console */
void (*exp_child_exec_prelude)() = 0;
void (*exp_close_in_child)() = 0;
#ifdef HAVE_SIGLONGJMP
sigjmp_buf exp_readenv; /* for interruptable read() */
#else
jmp_buf exp_readenv; /* for interruptable read() */
#endif /* HAVE_SIGLONGJMP */
int exp_reading = FALSE; /* whether we can longjmp or not */
int exp_is_debugging = FALSE;
FILE *exp_debugfile = 0;
FILE *exp_logfile = 0;
int exp_logfile_all = FALSE; /* if TRUE, write log of all interactions */
int exp_loguser = TRUE; /* if TRUE, user sees interactions on stdout */
char *exp_printify();
int exp_getptymaster();
int exp_getptyslave();
#define sysreturn(x) return(errno = x, -1)
void exp_init_pty();
/*
The following functions are linked from the Tcl library. They
don't cause anything else in the library to be dragged in, so it
shouldn't cause any problems (e.g., bloat).
The functions are relatively small but painful enough that I don't care
to recode them. You may, if you absolutely want to get rid of any
vestiges of Tcl.
*/
static unsigned int bufsiz = 2*EXP_MATCH_MAX;
static struct f {
int valid;
char *buffer; /* buffer of matchable chars */
char *buffer_end; /* one beyond end of matchable chars */
char *match_end; /* one beyond end of matched string */
int msize; /* size of allocate space */
/* actual size is one larger for null */
} *fs = 0;
static int fd_alloc_max = -1; /* max fd allocated */
/* translate fd or fp to fd */
static struct f *
fdfp2f(fd,fp)
int fd;
FILE *fp;
{
if (fd == -1) return(fs + fileno(fp));
else return(fs + fd);
}
static struct f *
fd_new(fd)
int fd;
{
int i, low;
struct f *fp;
struct f *newfs; /* temporary, so we don't lose old fs */
if (fd > fd_alloc_max) {
if (!fs) { /* no fd's yet allocated */
newfs = (struct f *)malloc(sizeof(struct f)*(fd+1));
low = 0;
} else { /* enlarge fd table */
newfs = (struct f *)realloc((char *)fs,sizeof(struct f)*(fd+1));
low = fd_alloc_max+1;
}
fs = newfs;
fd_alloc_max = fd;
for (i = low; i <= fd_alloc_max; i++) { /* init new entries */
fs[i].valid = FALSE;
}
}
fp = fs+fd;
if (!fp->valid) {
/* initialize */
fp->buffer = malloc((unsigned)(bufsiz+1));
if (!fp->buffer) return 0;
fp->msize = bufsiz;
fp->valid = TRUE;
}
fp->buffer_end = fp->buffer;
fp->match_end = fp->buffer;
return fp;
}
static
void
exp_setpgrp()
{
#ifdef MIPS_BSD
/* required on BSD side of MIPS OS <jmsellen@watdragon.waterloo.edu> */
# include <sysv/sys.s>
syscall(SYS_setpgrp);
#endif
#ifdef SETPGRP_VOID
(void) setpgrp();
#else
(void) setpgrp(0,0);
#endif
}
/* returns fd of master side of pty */
int
exp_spawnv(file,argv)
char *file;
char *argv[]; /* some compiler complains about **argv? */
{
int cc;
int errorfd; /* place to stash fileno(stderr) in child */
/* while we're setting up new stderr */
int ttyfd;
int sync_fds[2];
int sync2_fds[2];
int status_pipe[2];
int child_errno;
char sync_byte;
#ifdef PTYTRAP_DIES
int slave_write_ioctls = 1;
/* by default, slave will be write-ioctled this many times */
#endif
static int first_time = TRUE;
if (first_time) {
first_time = FALSE;
exp_init_pty();
exp_init_tty();
expDiagLogPtrSet(expDiagLogU);
/*
* TIP 27; It is unclear why this code produces a
* warning. The equivalent code in exp_main_sub.c
* (line 512) does not generate a warning !
*/
expErrnoMsgSet(Tcl_ErrnoMsg);
}
if (!file || !argv) sysreturn(EINVAL);
if (!argv[0] || strcmp(file,argv[0])) {
exp_debuglog("expect: warning: file (%s) != argv[0] (%s)\n",
file,
argv[0]?argv[0]:"");
}
#ifdef PTYTRAP_DIES
/* any extraneous ioctl's that occur in slave must be accounted for
when trapping, see below in child half of fork */
#if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(sun) && !defined(hp9000s300)
slave_write_ioctls++;
#endif
#endif /*PTYTRAP_DIES*/
if (exp_autoallocpty) {
if (0 > (exp_pty[0] = exp_getptymaster())) sysreturn(ENODEV);
}
fcntl(exp_pty[0],F_SETFD,1); /* close on exec */
#ifdef PTYTRAP_DIES
exp_slave_control(exp_pty[0],1);*/
#endif
if (!fd_new(exp_pty[0])) {
errno = ENOMEM;
return -1;
}
if (-1 == (pipe(sync_fds))) {
return -1;
}
if (-1 == (pipe(sync2_fds))) {
close(sync_fds[0]);
close(sync_fds[1]);
return -1;
}
if (-1 == pipe(status_pipe)) {
close(sync_fds[0]);
close(sync_fds[1]);
close(sync2_fds[0]);
close(sync2_fds[1]);
return -1;
}
if ((exp_pid = fork()) == -1) return(-1);
if (exp_pid) {
/* parent */
close(sync_fds[1]);
close(sync2_fds[0]);
close(status_pipe[1]);
if (!exp_autoallocpty) close(exp_pty[1]);
#ifdef PTYTRAP_DIES
#ifdef HAVE_PTYTRAP
if (exp_autoallocpty) {
/* trap initial ioctls in a feeble attempt to not */
/* block the initially. If the process itself */
/* ioctls /dev/tty, such blocks will be trapped */
/* later during normal event processing */
while (slave_write_ioctls) {
int cc;
cc = exp_wait_for_slave_open(exp_pty[0]);
#if defined(TIOCSCTTY) && !defined(CIBAUD) && !defined(sun) && !defined(hp9000s300)
if (cc == TIOCSCTTY) slave_write_ioctls = 0;
#endif
if (cc & IOC_IN) slave_write_ioctls--;
else if (cc == -1) {
printf("failed to trap slave pty");
return -1;
}
}
}
#endif
#endif /*PTYTRAP_DIES*/
/*
* wait for slave to initialize pty before allowing
* user to send to it
*/
exp_debuglog("parent: waiting for sync byte\r\n");
cc = read(sync_fds[0],&sync_byte,1);
if (cc == -1) {
exp_errorlog("parent sync byte read: %s\r\n",Tcl_ErrnoMsg(errno));
return -1;
}
/* turn on detection of eof */
exp_slave_control(exp_pty[0],1);
/*
* tell slave to go on now now that we have initialized pty
*/
exp_debuglog("parent: telling child to go ahead\r\n");
cc = write(sync2_fds[1]," ",1);
if (cc == -1) {
exp_errorlog("parent sync byte write: %s\r\n",Tcl_ErrnoMsg(errno));
return -1;
}
exp_debuglog("parent: now unsynchronized from child\r\n");
close(sync_fds[0]);
close(sync2_fds[1]);
/* see if child's exec worked */
retry:
switch (read(status_pipe[0],&child_errno,sizeof child_errno)) {
case -1:
if (errno == EINTR) goto retry;
/* well it's not really the child's errno */
/* but it can be treated that way */
child_errno = errno;
break;
case 0:
/* child's exec succeeded */
child_errno = 0;
break;
default:
/* child's exec failed; err contains exec's errno */
waitpid(exp_pid, NULL, 0);
errno = child_errno;
exp_pty[0] = -1;
}
close(status_pipe[0]);
return(exp_pty[0]);
}
/*
* child process - do not return from here! all errors must exit()
*/
close(sync_fds[0]);
close(sync2_fds[1]);
close(status_pipe[0]);
fcntl(status_pipe[1],F_SETFD,1); /* close on exec */
#ifdef CRAY
(void) close(exp_pty[0]);
#endif
/* ultrix (at least 4.1-2) fails to obtain controlling tty if setsid */
/* is called. setpgrp works though. */
#if defined(POSIX) && !defined(ultrix)
#define DO_SETSID
#endif
#ifdef __convex__
#define DO_SETSID
#endif
#ifdef DO_SETSID
setsid();
#else
#ifdef SYSV3
#ifndef CRAY
exp_setpgrp();
#endif /* CRAY */
#else /* !SYSV3 */
exp_setpgrp();
#ifdef TIOCNOTTY
ttyfd = open("/dev/tty", O_RDWR);
if (ttyfd >= 0) {
(void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
(void) close(ttyfd);
}
#endif /* TIOCNOTTY */
#endif /* SYSV3 */
#endif /* DO_SETSID */
/* save error fd while we're setting up new one */
errorfd = fcntl(2,F_DUPFD,3);
/* and here is the macro to restore it */
#define restore_error_fd {close(2);fcntl(errorfd,F_DUPFD,2);}
if (exp_autoallocpty) {
close(0);
close(1);
close(2);
/* since we closed fd 0, open of pty slave must return fd 0 */
if (0 > (exp_pty[1] = exp_getptyslave(exp_ttycopy,exp_ttyinit,
exp_stty_init))) {
restore_error_fd
fprintf(stderr,"open(slave pty): %s\n",Tcl_ErrnoMsg(errno));
exit(-1);
}
/* sanity check */
if (exp_pty[1] != 0) {
restore_error_fd
fprintf(stderr,"exp_getptyslave: slave = %d but expected 0\n",
exp_pty[1]);
exit(-1);
}
} else {
if (exp_pty[1] != 0) {
close(0); fcntl(exp_pty[1],F_DUPFD,0);
}
close(1); fcntl(0,F_DUPFD,1);
close(2); fcntl(0,F_DUPFD,1);
close(exp_pty[1]);
}
/* The test for hpux may have to be more specific. In particular, the */
/* code should be skipped on the hp9000s300 and hp9000s720 (but there */
/* is no documented define for the 720!) */
#if defined(TIOCSCTTY) && !defined(sun) && !defined(hpux)
/* 4.3+BSD way to acquire controlling terminal */
/* according to Stevens - Adv. Prog..., p 642 */
#ifdef __QNX__ /* posix in general */
if (tcsetct(0, getpid()) == -1) {
restore_error_fd
expErrorLog("failed to get controlling terminal using TIOCSCTTY");
exit(-1);
}
#else
(void) ioctl(0,TIOCSCTTY,(char *)0);
/* ignore return value - on some systems, it is defined but it
* fails and it doesn't seem to cause any problems. Or maybe
* it works but returns a bogus code. Noone seems to be able
* to explain this to me. The systems are an assortment of
* different linux systems (and FreeBSD 2.5), RedHat 5.2 and
* Debian 2.0
*/
#endif
#endif
#ifdef CRAY
(void) setsid();
(void) ioctl(0,TCSETCTTY,0);
(void) close(0);
if (open("/dev/tty", O_RDWR) < 0) {
restore_error_fd
fprintf(stderr,"open(/dev/tty): %s\r\n",Tcl_ErrnoMsg(errno));
exit(-1);
}
(void) close(1);
(void) close(2);
(void) dup(0);
(void) dup(0);
setptyutmp(); /* create a utmp entry */
/* _CRAY2 code from Hal Peterson <hrp@cray.com>, Cray Research, Inc. */
#ifdef _CRAY2
/*
* Interpose a process between expect and the spawned child to
* keep the slave side of the pty open to allow time for expect
* to read the last output. This is a workaround for an apparent
* bug in the Unicos pty driver on Cray-2's under Unicos 6.0 (at
* least).
*/
if ((pid = fork()) == -1) {
restore_error_fd
fprintf(stderr,"second fork: %s\r\n",Tcl_ErrnoMsg(errno));
exit(-1);
}
if (pid) {
/* Intermediate process. */
int status;
int timeout;
char *t;
/* How long should we wait? */
timeout = exp_pty_timeout;
/* Let the spawned process run to completion. */
while (wait(&status) < 0 && errno == EINTR)
/* empty body */;
/* Wait for the pty to clear. */
sleep(timeout);
/* Duplicate the spawned process's status. */
if (WIFSIGNALED(status))
kill(getpid(), WTERMSIG(status));
/* The kill may not have worked, but this will. */
exit(WEXITSTATUS(status));
}
#endif /* _CRAY2 */
#endif /* CRAY */
if (exp_console) {
#ifdef SRIOCSREDIR
int fd;
if ((fd = open("/dev/console", O_RDONLY)) == -1) {
restore_error_fd
fprintf(stderr, "spawn %s: cannot open console, check permissions of /dev/console\n",argv[0]);
exit(-1);
}
if (ioctl(fd, SRIOCSREDIR, 0) == -1) {
restore_error_fd
fprintf(stderr, "spawn %s: cannot redirect console, check permissions of /dev/console\n",argv[0]);
}
close(fd);
#endif
#ifdef TIOCCONS
int on = 1;
if (ioctl(0,TIOCCONS,(char *)&on) == -1) {
restore_error_fd
fprintf(stderr, "spawn %s: cannot open console, check permissions of /dev/console\n",argv[0]);
exit(-1);
}
#endif /* TIOCCONS */
}
/* tell parent that we are done setting up pty */
/* The actual char sent back is irrelevant. */
/* exp_debuglog("child: telling parent that pty is initialized\r\n");*/
cc = write(sync_fds[1]," ",1);
if (cc == -1) {
restore_error_fd
fprintf(stderr,"child: sync byte write: %s\r\n",Tcl_ErrnoMsg(errno));
exit(-1);
}
close(sync_fds[1]);
/* wait for master to let us go on */
cc = read(sync2_fds[0],&sync_byte,1);
if (cc == -1) {
restore_error_fd
exp_errorlog("child: sync byte read: %s\r\n",Tcl_ErrnoMsg(errno));
exit(-1);
}
close(sync2_fds[0]);
/* exp_debuglog("child: now unsynchronized from parent\r\n"); */
/* (possibly multiple) masters are closed automatically due to */
/* earlier fcntl(,,CLOSE_ON_EXEC); */
/* just in case, allow user to explicitly close other files */
if (exp_close_in_child) (*exp_close_in_child)();
/* allow user to do anything else to child */
if (exp_child_exec_prelude) (*exp_child_exec_prelude)();
(void) execvp(file,argv);
/* Unfortunately, by now we've closed fd's to stderr, logfile
* and debugfile. The only reasonable thing to do is to send
* *back the error as part of the program output. This will
* be *picked up in an expect or interact command.
*/
write(status_pipe[1], &errno, sizeof errno);
exit(-1);
/*NOTREACHED*/
}
/* returns fd of master side of pty */
/*VARARGS*/
int
exp_spawnl TCL_VARARGS_DEF(char *,arg1)
/*exp_spawnl(va_alist)*/
/*va_dcl*/
{
va_list args; /* problematic line here */
int i;
char *arg, **argv;
arg = TCL_VARARGS_START(char *,arg1,args);
/*va_start(args);*/
for (i=1;;i++) {
arg = va_arg(args,char *);
if (!arg) break;
}
va_end(args);
if (i == 0) sysreturn(EINVAL);
if (!(argv = (char **)malloc((i+1)*sizeof(char *)))) sysreturn(ENOMEM);
argv[0] = TCL_VARARGS_START(char *,arg1,args);
/*va_start(args);*/
for (i=1;;i++) {
argv[i] = va_arg(args,char *);
if (!argv[i]) break;
}
i = exp_spawnv(argv[0],argv+1);
free((char *)argv);
return(i);
}
/* allow user-provided fd to be passed to expect funcs */
int
exp_spawnfd(fd)
int fd;
{
if (!fd_new(fd)) {
errno = ENOMEM;
return -1;
}
return fd;
}
/* remove nulls from s. Initially, the number of chars in s is c, */
/* not strlen(s). This count does not include the trailing null. */
/* returns number of nulls removed. */
static int
rm_nulls(s,c)
char *s;
int c;
{
char *s2 = s; /* points to place in original string to put */
/* next non-null character */
int count = 0;
int i;
for (i=0;i<c;i++,s++) {
if (0 == *s) {
count++;
continue;
}
if (count) *s2 = *s;
s2++;
}
return(count);
}
static int i_read_errno;/* place to save errno, if i_read() == -1, so it
doesn't get overwritten before we get to read it */
/*ARGSUSED*/
static void
sigalarm_handler(n)
int n; /* signal number, unused by us */
{
#ifdef REARM_SIG
signal(SIGALRM,sigalarm_handler);
#endif
#ifdef HAVE_SIGLONGJMP
siglongjmp(exp_readenv,1);
#else
longjmp(exp_readenv,1);
#endif /* HAVE_SIGLONGJMP */
}
/* interruptable read */
static int
i_read(fd,fp,buffer,length,timeout)
int fd;
FILE *fp;
char *buffer;
int length;
int timeout;
{
int cc = -2;
/* since setjmp insists on returning 1 upon longjmp(,0), */
/* longjmp(,2 (EXP_RESTART)) instead. */
/* no need to set alarm if -1 (infinite) or 0 (poll with */
/* guaranteed data) */
if (timeout > 0) alarm(timeout);
/* restart read if setjmp returns 0 (first time) or 2 (EXP_RESTART). */
/* abort if setjmp returns 1 (EXP_ABORT). */
#ifdef HAVE_SIGLONGJMP
if (EXP_ABORT != sigsetjmp(exp_readenv,1)) {
#else
if (EXP_ABORT != setjmp(exp_readenv)) {
#endif /* HAVE_SIGLONGJMP */
exp_reading = TRUE;
if (fd == -1) {
int c;
c = getc(fp);
if (c == EOF) {
/*fprintf(stderr,"<<EOF>>",c);fflush(stderr);*/
if (feof(fp)) cc = 0;
else cc = -1;
} else {
/*fprintf(stderr,"<<%c>>",c);fflush(stderr);*/
buffer[0] = c;
cc = 1;
}
} else {
#ifndef HAVE_PTYTRAP
cc = read(fd,buffer,length);
#else
# include <sys/ptyio.h>
fd_set rdrs;
fd_set excep;
restart:
FD_ZERO(&rdrs);
FD_ZERO(&excep);
FD_SET(fd,&rdrs);
FD_SET(fd,&excep);
if (-1 == (cc = select(fd+1,
(SELECT_MASK_TYPE *)&rdrs,
(SELECT_MASK_TYPE *)0,
(SELECT_MASK_TYPE *)&excep,
(struct timeval *)0))) {
/* window refreshes trigger EINTR, ignore */
if (errno == EINTR) goto restart;
}
if (FD_ISSET(fd,&rdrs)) {
cc = read(fd,buffer,length);
} else if (FD_ISSET(fd,&excep)) {
struct request_info ioctl_info;
ioctl(fd,TIOCREQCHECK,&ioctl_info);
if (ioctl_info.request == TIOCCLOSE) {
cc = 0; /* indicate eof */
} else {
ioctl(fd, TIOCREQSET, &ioctl_info);
/* presumably, we trapped an open here */
goto restart;
}
}
#endif /* HAVE_PTYTRAP */
}
#if 0
/* can't get fread to return early! */
else {
if (!(cc = fread(buffer,1,length,fp))) {
if (ferror(fp)) cc = -1;
}
}
#endif
i_read_errno = errno; /* errno can be overwritten by the */
/* time we return */
}
exp_reading = FALSE;
if (timeout > 0) alarm(0);
return(cc);
}
/* I tried really hard to make the following two functions share the code */
/* that makes the ecase array, but I kept running into a brick wall when */
/* passing var args into the funcs and then again into a make_cases func */
/* I would very much appreciate it if someone showed me how to do it right */
/* takes triplets of args, with a final "exp_last" arg */
/* triplets are type, pattern, and then int to return */
/* returns negative value if error (or EOF/timeout) occurs */
/* some negative values can also have an associated errno */
/* the key internal variables that this function depends on are:
exp_buffer
exp_buffer_end
exp_match_end
*/
static int
expectv(fd,fp,ecases)
int fd;
FILE *fp;
struct exp_case *ecases;
{
int cc = 0; /* number of chars returned in a single read */
int buf_length; /* numbers of chars in exp_buffer */
int old_length; /* old buf_length */
int first_time = TRUE; /* force old buffer to be tested before */
/* additional reads */
int polled = 0; /* true if poll has caused read() to occur */
struct exp_case *ec; /* points to current ecase */
time_t current_time; /* current time (when we last looked)*/
time_t end_time; /* future time at which to give up */
int remtime; /* remaining time in timeout */
struct f *f;
int return_val;
int sys_error = 0;
#define return_normally(x) {return_val = x; goto cleanup;}
#define return_errno(x) {sys_error = x; goto cleanup;}
f = fdfp2f(fd,fp);
if (!f) return_errno(ENOMEM);
exp_buffer = f->buffer;
exp_buffer_end = f->buffer_end;
exp_match_end = f->match_end;
buf_length = exp_buffer_end - exp_match_end;
if (buf_length) {
/*
* take end of previous match to end of buffer
* and copy to beginning of buffer
*/
memmove(exp_buffer,exp_match_end,buf_length);
}
exp_buffer_end = exp_buffer + buf_length;
*exp_buffer_end = '\0';
if (!ecases) return_errno(EINVAL);
/* compile if necessary */
for (ec=ecases;ec->type != exp_end;ec++) {
if ((ec->type == exp_regexp) && !ec->re) {
TclRegError((char *)0);
if (!(ec->re = TclRegComp(ec->pattern))) {
fprintf(stderr,"regular expression %s is bad: %s",ec->pattern,TclGetRegError());
return_errno(EINVAL);
}
}
}
/* get the latest buffer size. Double the user input for two */
/* reasons. 1) Need twice the space in case the match */
/* straddles two bufferfuls, 2) easier to hack the division by */
/* two when shifting the buffers later on */
bufsiz = 2*exp_match_max;
if (f->msize != bufsiz) {
/* if truncated, forget about some data */
if (buf_length > bufsiz) {
/* copy end of buffer down */
/* copy one less than what buffer can hold to avoid */
/* triggering buffer-full handling code below */
/* which will immediately dump the first half */
/* of the buffer */
memmove(exp_buffer,exp_buffer+(buf_length - bufsiz)+1,
bufsiz-1);
buf_length = bufsiz-1;
}
exp_buffer = realloc(exp_buffer,bufsiz+1);
if (!exp_buffer) return_errno(ENOMEM);
exp_buffer[buf_length] = '\0';
exp_buffer_end = exp_buffer + buf_length;
f->msize = bufsiz;
}
/* some systems (i.e., Solaris) require fp be flushed when switching */
/* directions - do this again afterwards */
if (fd == -1) fflush(fp);
if (exp_timeout != -1) signal(SIGALRM,sigalarm_handler);
/* remtime and current_time updated at bottom of loop */
remtime = exp_timeout;
time(¤t_time);
end_time = current_time + remtime;
for (;;) {
/* when buffer fills, copy second half over first and */
/* continue, so we can do matches over multiple buffers */
if (buf_length == bufsiz) {
int first_half, second_half;
if (exp_full_buffer) {
exp_debuglog("expect: full buffer\r\n");
exp_match = exp_buffer;
exp_match_end = exp_buffer + buf_length;
exp_buffer_end = exp_match_end;
return_normally(EXP_FULLBUFFER);
}
first_half = bufsiz/2;
second_half = bufsiz - first_half;
memcpy(exp_buffer,exp_buffer+first_half,second_half);
buf_length = second_half;
exp_buffer_end = exp_buffer + second_half;
}
/*
* always check first if pattern is already in buffer
*/
if (first_time) {
first_time = FALSE;
goto after_read;
}
/*
* check for timeout
* we should timeout if either
* 1) exp_timeout > remtime <= 0 (normal)
* 2) exp_timeout == 0 and we have polled at least once
*
*/
if (((exp_timeout > remtime) && (remtime <= 0)) ||
((exp_timeout == 0) && polled)) {
exp_debuglog("expect: timeout\r\n");
exp_match_end = exp_buffer;
return_normally(EXP_TIMEOUT);
}
/* remember that we have actually checked at least once */
polled = 1;
cc = i_read(fd,fp,
exp_buffer_end,
bufsiz - buf_length,
remtime);
if (cc == 0) {
exp_debuglog("expect: eof\r\n");
return_normally(EXP_EOF); /* normal EOF */
} else if (cc == -1) { /* abnormal EOF */
/* ptys produce EIO upon EOF - sigh */
if (i_read_errno == EIO) {
/* convert to EOF indication */
exp_debuglog("expect: eof\r\n");
return_normally(EXP_EOF);
}
exp_debuglog("expect: error (errno = %d)\r\n",i_read_errno);
return_errno(i_read_errno);
} else if (cc == -2) {
exp_debuglog("expect: timeout\r\n");
exp_match_end = exp_buffer;
return_normally(EXP_TIMEOUT);
}
old_length = buf_length;
buf_length += cc;
exp_buffer_end += buf_length;
if (exp_logfile_all || (exp_loguser && exp_logfile)) {
fwrite(exp_buffer + old_length,1,cc,exp_logfile);
}
if (exp_loguser) fwrite(exp_buffer + old_length,1,cc,stdout);
if (exp_debugfile) fwrite(exp_buffer + old_length,1,cc,exp_debugfile);
/* if we wrote to any logs, flush them */
if (exp_debugfile) fflush(exp_debugfile);
if (exp_loguser) {
fflush(stdout);
if (exp_logfile) fflush(exp_logfile);
}
/* remove nulls from input, so we can use C-style strings */
/* doing it here lets them be sent to the screen, just */
/* in case they are involved in formatting operations */
if (exp_remove_nulls) {
buf_length -= rm_nulls(exp_buffer + old_length, cc);
}
/* cc should be decremented as well, but since it will not */
/* be used before being set again, there is no need */
exp_buffer_end = exp_buffer + buf_length;
*exp_buffer_end = '\0';
exp_match_end = exp_buffer;
after_read:
exp_debuglog("expect: does {%s} match ",exp_printify(exp_buffer));
/* pattern supplied */
for (ec=ecases;ec->type != exp_end;ec++) {
int matched = -1;
exp_debuglog("{%s}? ",exp_printify(ec->pattern));
if (ec->type == exp_glob) {
int offset;
matched = Exp_StringMatch(exp_buffer,ec->pattern,&offset);
if (matched >= 0) {
exp_match = exp_buffer + offset;
exp_match_end = exp_match + matched;
}
} else if (ec->type == exp_exact) {
char *p = strstr(exp_buffer,ec->pattern);
if (p) {
matched = 1;
exp_match = p;
exp_match_end = p + strlen(ec->pattern);
}
} else if (ec->type == exp_null) {
char *p;
for (p=exp_buffer;p<exp_buffer_end;p++) {
if (*p == 0) {
matched = 1;
exp_match = p;
exp_match_end = p+1;
}
}
} else {
TclRegError((char *)0);
if (TclRegExec(ec->re,exp_buffer,exp_buffer)) {
matched = 1;
exp_match = ec->re->startp[0];
exp_match_end = ec->re->endp[0];
} else if (TclGetRegError()) {
fprintf(stderr,"r.e. match (pattern %s) failed: %s",ec->pattern,TclGetRegError());
}
}
if (matched != -1) {
exp_debuglog("yes\nexp_buffer is {%s}\n",
exp_printify(exp_buffer));
return_normally(ec->value);
} else exp_debuglog("no\n");
}
/*
* Update current time and remaining time.
* Don't bother if we are waiting forever or polling.
*/
if (exp_timeout > 0) {
time(¤t_time);
remtime = end_time - current_time;
}
}
cleanup:
f->buffer = exp_buffer;
f->buffer_end = exp_buffer_end;
f->match_end = exp_match_end;
/* some systems (i.e., Solaris) require fp be flushed when switching */
/* directions - do this before as well */
if (fd == -1) fflush(fp);
if (sys_error) {
errno = sys_error;
return -1;
}
return return_val;
}
int
exp_fexpectv(fp,ecases)
FILE *fp;
struct exp_case *ecases;
{
return(expectv(-1,fp,ecases));
}
int
exp_expectv(fd,ecases)
int fd;
struct exp_case *ecases;
{
return(expectv(fd,(FILE *)0,ecases));
}
/*VARARGS*/
int
exp_expectl TCL_VARARGS_DEF(int,arg1)
/*exp_expectl(va_alist)*/
/*va_dcl*/
{
va_list args;
int fd;
struct exp_case *ec, *ecases;
int i;
enum exp_type type;
fd = TCL_VARARGS_START(int,arg1,args);
/* va_start(args);*/
/* fd = va_arg(args,int);*/
/* first just count the arg sets */
for (i=0;;i++) {
type = va_arg(args,enum exp_type);
if (type == exp_end) break;
/* Ultrix 4.2 compiler refuses enumerations comparison!? */
if ((int)type < 0 || (int)type >= (int)exp_bogus) {
fprintf(stderr,"bad type (set %d) in exp_expectl\n",i);
sysreturn(EINVAL);
}
va_arg(args,char *); /* COMPUTED BUT NOT USED */
if (type == exp_compiled) {
va_arg(args,regexp *); /* COMPUTED BUT NOT USED */
}
va_arg(args,int); /* COMPUTED BUT NOT USED*/
}
va_end(args);
if (!(ecases = (struct exp_case *)
malloc((1+i)*sizeof(struct exp_case))))
sysreturn(ENOMEM);
/* now set up the actual cases */
fd = TCL_VARARGS_START(int,arg1,args);
/*va_start(args);*/
/*va_arg(args,int);*/ /*COMPUTED BUT NOT USED*/
for (ec=ecases;;ec++) {
ec->type = va_arg(args,enum exp_type);
if (ec->type == exp_end) break;
ec->pattern = va_arg(args,char *);
if (ec->type == exp_compiled) {
ec->re = va_arg(args,regexp *);
} else {
ec->re = 0;
}
ec->value = va_arg(args,int);
}
va_end(args);
i = expectv(fd,(FILE *)0,ecases);
for (ec=ecases;ec->type != exp_end;ec++) {
/* free only if regexp and we compiled it for user */
if (ec->type == exp_regexp) {
free((char *)ec->re);
}
}
free((char *)ecases);
return(i);
}
int
exp_fexpectl TCL_VARARGS_DEF(FILE *,arg1)
/*exp_fexpectl(va_alist)*/
/*va_dcl*/
{
va_list args;
FILE *fp;
struct exp_case *ec, *ecases;
int i;
enum exp_type type;
fp = TCL_VARARGS_START(FILE *,arg1,args);
/*va_start(args);*/
/*fp = va_arg(args,FILE *);*/
/* first just count the arg-pairs */
for (i=0;;i++) {
type = va_arg(args,enum exp_type);
if (type == exp_end) break;
/* Ultrix 4.2 compiler refuses enumerations comparison!? */
if ((int)type < 0 || (int)type >= (int)exp_bogus) {
fprintf(stderr,"bad type (set %d) in exp_expectl\n",i);
sysreturn(EINVAL);
}
va_arg(args,char *); /* COMPUTED BUT NOT USED */
if (type == exp_compiled) {
va_arg(args,regexp *); /* COMPUTED BUT NOT USED */
}
va_arg(args,int); /* COMPUTED BUT NOT USED*/
}
va_end(args);
if (!(ecases = (struct exp_case *)
malloc((1+i)*sizeof(struct exp_case))))
sysreturn(ENOMEM);
#if 0
va_start(args);
va_arg(args,FILE *); /*COMPUTED, BUT NOT USED*/
#endif
(void) TCL_VARARGS_START(FILE *,arg1,args);
for (ec=ecases;;ec++) {
ec->type = va_arg(args,enum exp_type);
if (ec->type == exp_end) break;
ec->pattern = va_arg(args,char *);
if (ec->type == exp_compiled) {
ec->re = va_arg(args,regexp *);
} else {
ec->re = 0;
}
ec->value = va_arg(args,int);
}
va_end(args);
i = expectv(-1,fp,ecases);
for (ec=ecases;ec->type != exp_end;ec++) {
/* free only if regexp and we compiled it for user */
if (ec->type == exp_regexp) {
free((char *)ec->re);
}
}
free((char *)ecases);
return(i);
}
/* like popen(3) but works in both directions */
FILE *
exp_popen(program)
char *program;
{
FILE *fp;
int ec;
if (0 > (ec = exp_spawnl("sh","sh","-c",program,(char *)0))) return(0);
if (!(fp = fdopen(ec,"r+"))) return(0);
setbuf(fp,(char *)0);
return(fp);
}
int
exp_disconnect()
{
int ttyfd;
#ifndef EALREADY
#define EALREADY 37
#endif
/* presumably, no stderr, so don't bother with error message */
if (exp_disconnected) sysreturn(EALREADY);
exp_disconnected = TRUE;
freopen("/dev/null","r",stdin);
freopen("/dev/null","w",stdout);
freopen("/dev/null","w",stderr);
#ifdef POSIX
setsid();
#else
#ifdef SYSV3
/* put process in our own pgrp, and lose controlling terminal */
exp_setpgrp();
signal(SIGHUP,SIG_IGN);
if (fork()) exit(0); /* first child exits (as per Stevens, */
/* UNIX Network Programming, p. 79-80) */
/* second child process continues as daemon */
#else /* !SYSV3 */
exp_setpgrp();
/* Pyramid lacks this defn */
#ifdef TIOCNOTTY
ttyfd = open("/dev/tty", O_RDWR);
if (ttyfd >= 0) {
/* zap controlling terminal if we had one */
(void) ioctl(ttyfd, TIOCNOTTY, (char *)0);
(void) close(ttyfd);
}
#endif /* TIOCNOTTY */
#endif /* SYSV3 */
#endif /* POSIX */
return(0);
}
/* send to log if open and debugging enabled */
/* send to stderr if debugging enabled */
/* use this function for recording unusual things in the log */
/*VARARGS*/
void
exp_debuglog TCL_VARARGS_DEF(char *,arg1)
{
char *fmt;
va_list args;
fmt = TCL_VARARGS_START(char *,arg1,args);
if (exp_debugfile) vfprintf(exp_debugfile,fmt,args);
if (exp_is_debugging) {
vfprintf(stderr,fmt,args);
if (exp_logfile) vfprintf(exp_logfile,fmt,args);
}
va_end(args);
}
/* send to log if open */
/* send to stderr */
/* use this function for error conditions */
/*VARARGS*/
void
exp_errorlog TCL_VARARGS_DEF(char *,arg1)
{
char *fmt;
va_list args;
fmt = TCL_VARARGS_START(char *,arg1,args);
vfprintf(stderr,fmt,args);
if (exp_debugfile) vfprintf(exp_debugfile,fmt,args);
if (exp_logfile) vfprintf(exp_logfile,fmt,args);
va_end(args);
}
#include <ctype.h>
char *
exp_printify(s)
char *s;
{
static int destlen = 0;
static char *dest = 0;
char *d; /* ptr into dest */
unsigned int need;
if (s == 0) return("<null>");
/* worst case is every character takes 4 to printify */
need = strlen(s)*4 + 1;
if (need > destlen) {
if (dest) ckfree(dest);
dest = ckalloc(need);
destlen = need;
}
for (d = dest;*s;s++) {
if (*s == '\r') {
strcpy(d,"\\r"); d += 2;
} else if (*s == '\n') {
strcpy(d,"\\n"); d += 2;
} else if (*s == '\t') {
strcpy(d,"\\t"); d += 2;
} else if (isascii(*s) && isprint(*s)) {
*d = *s; d += 1;
} else {
sprintf(d,"\\x%02x",*s & 0xff); d += 4;
}
}
*d = '\0';
return(dest);
}
syntax highlighted by Code2HTML, v. 0.9.1