diff --git a/exp_chan.c b/exp_chan.c index 79f486c..c92e26b 100644 --- a/exp_chan.c +++ b/exp_chan.c @@ -51,6 +51,8 @@ static void ExpWatchProc _ANSI_ARGS_((ClientData instanceData, int mask)); static int ExpGetHandleProc _ANSI_ARGS_((ClientData instanceData, int direction, ClientData *handlePtr)); +void exp_background_channelhandler _ANSI_ARGS_((ClientData, + int)); /* * This structure describes the channel type structure for Expect-based IO: diff --git a/exp_chan.c.covscan-fixes b/exp_chan.c.covscan-fixes new file mode 100644 index 0000000..79f486c --- /dev/null +++ b/exp_chan.c.covscan-fixes @@ -0,0 +1,767 @@ +/* + * exp_chan.c + * + * Channel driver for Expect channels. + * Based on UNIX File channel from TclUnixChan.c + * + */ + +#include +#include +#include +#include +#include /* for isspace */ +#include /* for time(3) */ + +#include "expect_cf.h" + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include + +#include "tclInt.h" /* Internal definitions for Tcl. */ + +#include "tcl.h" + +#include "string.h" + +#include "exp_rename.h" +#include "exp_prog.h" +#include "exp_command.h" +#include "exp_log.h" +#include "tcldbg.h" /* Dbg_StdinMode */ + +extern int expSetBlockModeProc _ANSI_ARGS_((int fd, int mode)); +static int ExpBlockModeProc _ANSI_ARGS_((ClientData instanceData, + int mode)); +static int ExpCloseProc _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp)); +static int ExpInputProc _ANSI_ARGS_((ClientData instanceData, + char *buf, int toRead, int *errorCode)); +static int ExpOutputProc _ANSI_ARGS_(( + ClientData instanceData, char *buf, int toWrite, + int *errorCode)); +static void ExpWatchProc _ANSI_ARGS_((ClientData instanceData, + int mask)); +static int ExpGetHandleProc _ANSI_ARGS_((ClientData instanceData, + int direction, ClientData *handlePtr)); + +/* + * This structure describes the channel type structure for Expect-based IO: + */ + +Tcl_ChannelType expChannelType = { + "exp", /* Type name. */ + ExpBlockModeProc, /* Set blocking/nonblocking mode.*/ + ExpCloseProc, /* Close proc. */ + ExpInputProc, /* Input proc. */ + ExpOutputProc, /* Output proc. */ + NULL, /* Seek proc. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + ExpWatchProc, /* Initialize notifier. */ + ExpGetHandleProc, /* Get OS handles out of channel. */ + NULL, /* Close2 proc */ +}; + +typedef struct ThreadSpecificData { + /* + * List of all exp channels currently open. This is per thread and is + * used to match up fd's to channels, which rarely occurs. + */ + + ExpState *firstExpPtr; + int channelCount; /* this is process-wide as it is used to + give user some hint as to why a spawn has failed + by looking at process-wide resource usage */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + + /* + *---------------------------------------------------------------------- + * + * ExpBlockModeProc -- + * + * Helper procedure to set blocking and nonblocking modes on a + * file based channel. Invoked by generic IO level code. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ + + /* ARGSUSED */ +static int +ExpBlockModeProc(instanceData, mode) + ClientData instanceData; /* Exp state. */ + int mode; /* The mode to set. Can be one of + * TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + + if (esPtr->fdin == 0) { + /* Forward status to debugger. Required for FIONBIO systems, + * which are unable to query the fd for its current state. + */ + Dbg_StdinMode (mode); + } + + /* [Expect SF Bug 1108551] (July 7 2005) + * Exclude manipulation of the blocking status for stdin/stderr. + * + * This is handled by the Tcl core itself and we must absolutely + * not pull the rug out from under it. The standard setting to + * non-blocking will mess with the core which had them set to + * blocking, and makes all its decisions based on that assumption. + * Setting to non-blocking can cause hangs and crashes. + * + * Stdin is ok however, apparently. + * (Sep 9 2005) No, it is not. + */ + + if ((esPtr->fdin == 0) || + (esPtr->fdin == 1) || + (esPtr->fdin == 2)) { + return 0; + } + + return expSetBlockModeProc (esPtr->fdin, mode); +} + +int +expSetBlockModeProc(fd, mode) + int fd; + int mode; /* The mode to set. Can be one of + * TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + int curStatus; + /*printf("ExpBlockModeProc(%d)\n",mode); + printf("fdin = %d\n",fd);*/ + +#ifndef USE_FIONBIO + curStatus = fcntl(fd, F_GETFL); + /*printf("curStatus = %d\n",curStatus);*/ + if (mode == TCL_MODE_BLOCKING) { + curStatus &= (~(O_NONBLOCK)); + } else { + curStatus |= O_NONBLOCK; + } + /*printf("new curStatus %d\n",curStatus);*/ + if (fcntl(fd, F_SETFL, curStatus) < 0) { + return errno; + } + curStatus = fcntl(fd, F_GETFL); +#else /* USE_FIONBIO */ + if (mode == TCL_MODE_BLOCKING) { + curStatus = 0; + } else { + curStatus = 1; + } + if (ioctl(fd, (int) FIONBIO, &curStatus) < 0) { + return errno; + } +#endif /* !USE_FIONBIO */ + return 0; +} +/* + *---------------------------------------------------------------------- + * + * ExpInputProc -- + * + * This procedure is invoked from the generic IO level to read + * input from an exp-based channel. + * + * Results: + * The number of bytes read is returned or -1 on error. An output + * argument contains a POSIX error code if an error occurs, or zero. + * + * Side effects: + * Reads input from the input device of the channel. + * + *---------------------------------------------------------------------- + */ + +static int +ExpInputProc(instanceData, buf, toRead, errorCodePtr) + ClientData instanceData; /* Exp state. */ + char *buf; /* Where to store data read. */ + int toRead; /* How much space is available + * in the buffer? */ + int *errorCodePtr; /* Where to store error code. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + int bytesRead; /* How many bytes were actually + * read from the input device? */ + + *errorCodePtr = 0; + + /* + * Assume there is always enough input available. This will block + * appropriately, and read will unblock as soon as a short read is + * possible, if the channel is in blocking mode. If the channel is + * nonblocking, the read will never block. + */ + + bytesRead = read(esPtr->fdin, buf, (size_t) toRead); + /*printf("ExpInputProc: read(%d,,) = %d\r\n",esPtr->fdin,bytesRead);*/ + + /* Emulate EOF on tty for tcl */ + if ((bytesRead == -1) && (errno == EIO) && isatty(esPtr->fdin)) { + bytesRead = 0; + } + if (bytesRead > -1) { + /* strip parity if requested */ + if (esPtr->parity == 0) { + char *end = buf+bytesRead; + for (;buf < end;buf++) { + *buf &= 0x7f; + } + } + return bytesRead; + } + *errorCodePtr = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * ExpOutputProc-- + * + * This procedure is invoked from the generic IO level to write + * output to an exp channel. + * + * Results: + * The number of bytes written is returned or -1 on error. An + * output argument contains a POSIX error code if an error occurred, + * or zero. + * + * Side effects: + * Writes output on the output device of the channel. + * + *---------------------------------------------------------------------- + */ + +static int +ExpOutputProc(instanceData, buf, toWrite, errorCodePtr) + ClientData instanceData; /* Exp state. */ + char *buf; /* The data buffer. */ + int toWrite; /* How many bytes to write? */ + int *errorCodePtr; /* Where to store error code. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + int written = 0; + + *errorCodePtr = 0; + + if (toWrite < 0) Tcl_Panic("ExpOutputProc: called with negative char count"); + if (toWrite ==0) { + return 0; + } + + written = write(esPtr->fdout, buf, (size_t) toWrite); + if (written == 0) { + /* This shouldn't happen but I'm told that it does + * nonetheless (at least on SunOS 4.1.3). Since this is + * not a documented return value, the most reasonable + * thing is to complain here and retry in the hopes that + * it is some transient condition. */ + sleep(1); + expDiagLogU("write() failed to write anything - will sleep(1) and retry...\n"); + *errorCodePtr = EAGAIN; + return -1; + } else if (written < 0) { + *errorCodePtr = errno; + return -1; + } + return written; +} + +/* + *---------------------------------------------------------------------- + * + * ExpCloseProc -- + * + * This procedure is called from the generic IO level to perform + * channel-type-specific cleanup when an exp-based channel is closed. + * + * Results: + * 0 if successful, errno if failed. + * + * Side effects: + * Closes the device of the channel. + * + *---------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static int +ExpCloseProc(instanceData, interp) + ClientData instanceData; /* Exp state. */ + Tcl_Interp *interp; /* For error reporting - unused. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + ExpState **nextPtrPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + esPtr->registered = FALSE; + +#if 0 + /* + Really should check that we created one first. Since we're sharing fds + with Tcl, perhaps a filehandler was created with a plain tcl file - we + wouldn't want to delete that. Although if user really close Expect's + user_spawn_id, it probably doesn't matter anyway. + */ + + Tcl_DeleteFileHandler(esPtr->fdin); +#endif /*0*/ + + Tcl_Free((char*)esPtr->input.buffer); + Tcl_DecrRefCount (esPtr->input.newchars); + + /* Actually file descriptor should have been closed earlier. */ + /* So do nothing here */ + + /* + * Conceivably, the process may not yet have been waited for. If this + * becomes a requirement, we'll have to revisit this code. But for now, if + * it's just Tcl exiting, the processes will exit on their own soon + * anyway. + */ + + for (nextPtrPtr = &(tsdPtr->firstExpPtr); (*nextPtrPtr) != NULL; + nextPtrPtr = &((*nextPtrPtr)->nextPtr)) { + if ((*nextPtrPtr) == esPtr) { + (*nextPtrPtr) = esPtr->nextPtr; + break; + } + } + tsdPtr->channelCount--; + + if (esPtr->bg_status == blocked || + esPtr->bg_status == disarm_req_while_blocked) { + esPtr->freeWhenBgHandlerUnblocked = 1; + /* + * If we're in the middle of a bg event handler, then the event + * handler will have to take care of freeing esPtr. + */ + } else { + expStateFree(esPtr); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * ExpWatchProc -- + * + * Initialize the notifier to watch the fd from this channel. + * + * Results: + * None. + * + * Side effects: + * Sets up the notifier so that a future event on the channel will + * be seen by Tcl. + * + *---------------------------------------------------------------------- + */ + +static void +ExpWatchProc(instanceData, mask) + ClientData instanceData; /* The exp state. */ + int mask; /* Events of interest; an OR-ed + * combination of TCL_READABLE, + * TCL_WRITABLE and TCL_EXCEPTION. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + + /* + * Make sure we only register for events that are valid on this exp. + * Note that we are passing Tcl_NotifyChannel directly to + * Tcl_CreateExpHandler with the channel pointer as the client data. + */ + + mask &= esPtr->validMask; + if (mask) { + /*printf(" CreateFileHandler: %d (mask = %d)\r\n",esPtr->fdin,mask);*/ + Tcl_CreateFileHandler(esPtr->fdin, mask, + (Tcl_FileProc *) Tcl_NotifyChannel, + (ClientData) esPtr->channel); + } else { + /*printf(" DeleteFileHandler: %d (mask = %d)\r\n",esPtr->fdin,mask);*/ + Tcl_DeleteFileHandler(esPtr->fdin); + } +} + +/* + *---------------------------------------------------------------------- + * + * ExpGetHandleProc -- + * + * Called from Tcl_GetChannelHandle to retrieve OS handles from + * an exp-based channel. + * + * Results: + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if + * there is no handle for the specified direction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +ExpGetHandleProc(instanceData, direction, handlePtr) + ClientData instanceData; /* The exp state. */ + int direction; /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr; /* Where to store the handle. */ +{ + ExpState *esPtr = (ExpState *) instanceData; + + if (direction & TCL_WRITABLE) { + *handlePtr = (ClientData) esPtr->fdin; + } + if (direction & TCL_READABLE) { + *handlePtr = (ClientData) esPtr->fdin; + } else { + return TCL_ERROR; + } + return TCL_OK; +} + +int +expChannelCountGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->channelCount; +} + +int +expChannelStillAlive(esBackupPtr, backupName) + ExpState *esBackupPtr; + char *backupName; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ExpState *esPtr; + + /* + * This utility function is called from 'exp_background_channelhandler' + * and checks to make sure that backupName can still be found in the + * channels linked list at the same address as before. + * + * If it can't be (or if the memory address has changed) then it + * means that it was lost in the background (and possibly another + * channel was opened and reassigned the same name). + */ + + for (esPtr = tsdPtr->firstExpPtr; esPtr; esPtr = esPtr->nextPtr) { + if (0 == strcmp(esPtr->name, backupName)) + return (esPtr == esBackupPtr); + } + + /* not found; must have been lost in the background */ + return 0; +} + +#if 0 /* Converted to macros */ +int +expSizeGet(esPtr) + ExpState *esPtr; +{ + return esPtr->input.use; +} + +int +expSizeZero(esPtr) + ExpState *esPtr; +{ + return (esPtr->input.use == 0); +} +#endif +/* return 0 for success or negative for failure */ +int +expWriteChars(esPtr,buffer,lenBytes) + ExpState *esPtr; + char *buffer; + int lenBytes; +{ + int rc; + retry: + rc = Tcl_WriteChars(esPtr->channel,buffer,lenBytes); + if ((rc == -1) && (errno == EAGAIN)) goto retry; + + if (!exp_strict_write) { + /* + * 5.41 compatbility behaviour. Ignore any and all write errors + * the OS may have thrown. + */ + return 0; + } + + /* just return 0 rather than positive byte counts */ + return ((rc > 0) ? 0 : rc); +} + +int +expWriteCharsUni(esPtr,buffer,lenChars) + ExpState *esPtr; + Tcl_UniChar *buffer; + int lenChars; +{ + int rc; + Tcl_DString ds; + + Tcl_DStringInit (&ds); + Tcl_UniCharToUtfDString (buffer,lenChars,&ds); + + rc = expWriteChars(esPtr,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds)); + + Tcl_DStringFree (&ds); + + return rc; +} + +void +expStateFree(esPtr) + ExpState *esPtr; +{ + if (esPtr->fdBusy) { + close(esPtr->fdin); + } + + esPtr->valid = FALSE; + + if (!esPtr->keepForever) { + ckfree((char *)esPtr); + } +} + +/* close all connections + * + * The kernel would actually do this by default, however Tcl is going to come + * along later and try to reap its exec'd processes. If we have inherited any + * via spawn -open, Tcl can hang if we don't close the connections first. + */ +void +exp_close_all(interp) +Tcl_Interp *interp; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ExpState *esPtr; + ExpState *esNextPtr; + + /* Save the nextPtr in a local variable before calling 'exp_close' + as 'expStateFree' can be called from it under some + circumstances, possibly causing the memory allocator to smash + the value in 'esPtr'. - Andreas Kupries + */ + + /* no need to keep things in sync (i.e., tsdPtr, count) since we could only + be doing this if we're exiting. Just close everything down. */ + + for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esNextPtr) { + esNextPtr = esPtr->nextPtr; + exp_close(interp,esPtr); + } +} + +/* wait for any of our own spawned processes we call waitpid rather + * than wait to avoid running into someone else's processes. Yes, + * according to Ousterhout this is the best way to do it. + * returns the ExpState or 0 if nothing to wait on */ +ExpState * +expWaitOnAny() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int result; + ExpState *esPtr; + + for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) { + if (esPtr->pid == exp_getpid) continue; /* skip ourself */ + if (esPtr->user_waited) continue; /* one wait only! */ + if (esPtr->sys_waited) break; + restart: + result = waitpid(esPtr->pid,&esPtr->wait,WNOHANG); + if (result == esPtr->pid) break; + if (result == 0) continue; /* busy, try next */ + if (result == -1) { + if (errno == EINTR) goto restart; + else break; + } + } + return esPtr; +} + +ExpState * +expWaitOnOne() { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ExpState *esPtr; + int pid; + /* should really be recoded using the common wait code in command.c */ + WAIT_STATUS_TYPE status; + + pid = wait(&status); + for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) { + if (esPtr->pid == pid) { + esPtr->sys_waited = TRUE; + esPtr->wait = status; + return esPtr; + } + } + /* Should not reach this location. If it happens return a value + * causing an easy crash */ + return NULL; +} + +void +exp_background_channelhandlers_run_all() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ExpState *esPtr; + ExpState *esNextPtr; + ExpState *esPriorPtr = 0; + + /* kick off any that already have input waiting */ + for (esPtr = tsdPtr->firstExpPtr;esPtr; esPriorPtr = esPtr, esPtr = esPtr->nextPtr) { + /* is bg_interp the best way to check if armed? */ + if (esPtr->bg_interp && !expSizeZero(esPtr)) { + /* + * We save the nextPtr in a local variable before calling + * 'exp_background_channelhandler' since in some cases + * 'expStateFree' could end up getting called before it + * returns, leading to a likely segfault on the next + * interaction through the for loop. + */ + esNextPtr = esPtr->nextPtr; + exp_background_channelhandler((ClientData)esPtr,0); + if (esNextPtr != esPtr->nextPtr) { + /* + * 'expStateFree' must have been called from + * underneath us so we know that esPtr->nextPtr is + * invalid. However, it is possible that either the + * original nextPtr and/or the priorPtr have been + * freed too. If the esPriorPtr->nextPtr is now + * esNextPtr it seems safe to proceed. Otherwise we + * break and end early for safety. + */ + if (esPriorPtr && esPriorPtr->nextPtr == esNextPtr) { + esPtr = esPriorPtr; + } else { + break; /* maybe set esPtr = tsdPtr->firstExpPtr again? */ + } + } + } + } +} + +ExpState * +expCreateChannel(interp,fdin,fdout,pid) + Tcl_Interp *interp; + int fdin; + int fdout; + int pid; +{ + ExpState *esPtr; + int mask; + Tcl_ChannelType *channelTypePtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + channelTypePtr = &expChannelType; + + esPtr = (ExpState *) ckalloc((unsigned) sizeof(ExpState)); + + esPtr->nextPtr = tsdPtr->firstExpPtr; + tsdPtr->firstExpPtr = esPtr; + + sprintf(esPtr->name,"exp%d",fdin); + + /* + * For now, stupidly assume this. We we will likely have to revisit this + * later to prevent people from doing stupid things. + */ + mask = TCL_READABLE | TCL_WRITABLE; + + /* not sure about this - what about adopted channels */ + esPtr->validMask = mask | TCL_EXCEPTION; + esPtr->fdin = fdin; + esPtr->fdout = fdout; + + /* set close-on-exec for everything but std channels */ + /* (system and stty commands need access to std channels) */ + if (fdin != 0 && fdin != 2) { + expCloseOnExec(fdin); + if (fdin != fdout) expCloseOnExec(fdout); + } + + esPtr->fdBusy = FALSE; + esPtr->channel = Tcl_CreateChannel(channelTypePtr, esPtr->name, + (ClientData) esPtr, mask); + Tcl_RegisterChannel(interp,esPtr->channel); + esPtr->registered = TRUE; + Tcl_SetChannelOption(interp,esPtr->channel,"-buffering","none"); + Tcl_SetChannelOption(interp,esPtr->channel,"-blocking","0"); + Tcl_SetChannelOption(interp,esPtr->channel,"-translation","lf"); + + esPtr->pid = pid; + + esPtr->input.max = 1; + esPtr->input.use = 0; + esPtr->input.buffer = (Tcl_UniChar*) Tcl_Alloc (sizeof (Tcl_UniChar)); + esPtr->input.newchars = Tcl_NewObj(); + Tcl_IncrRefCount (esPtr->input.newchars); + + esPtr->umsize = exp_default_match_max; + /* this will reallocate object with an appropriate sized buffer */ + expAdjust(esPtr); + + esPtr->printed = 0; + esPtr->echoed = 0; + esPtr->rm_nulls = exp_default_rm_nulls; + esPtr->parity = exp_default_parity; + esPtr->close_on_eof = exp_default_close_on_eof; + esPtr->key = expect_key++; + esPtr->force_read = FALSE; + esPtr->fg_armed = FALSE; + esPtr->chan_orig = 0; + esPtr->fd_slave = EXP_NOFD; +#ifdef HAVE_PTYTRAP + esPtr->slave_name = 0; +#endif /* HAVE_PTYTRAP */ + esPtr->open = TRUE; + esPtr->notified = FALSE; + esPtr->user_waited = FALSE; + esPtr->sys_waited = FALSE; + esPtr->bg_interp = 0; + esPtr->bg_status = unarmed; + esPtr->bg_ecount = 0; + esPtr->freeWhenBgHandlerUnblocked = FALSE; + esPtr->keepForever = FALSE; + esPtr->valid = TRUE; + tsdPtr->channelCount++; + + return esPtr; +} + +void +expChannelInit() { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + tsdPtr->channelCount = 0; +} diff --git a/exp_clib.c b/exp_clib.c index 3127d61..6c6601c 100644 --- a/exp_clib.c +++ b/exp_clib.c @@ -37,6 +37,14 @@ would appreciate credit if this program or parts of it are used. # endif #endif +#ifdef HAVE_UNISTD_H +# include +#endif + +//#ifdef HAVE_SYS_WAIT_H +# include +//#endif + #ifdef HAVE_SYS_FCNTL_H # include #else @@ -2196,6 +2204,7 @@ int exp_getptyslave(); #define sysreturn(x) return(errno = x, -1) void exp_init_pty(); +void exp_init_tty(); /* The following functions are linked from the Tcl library. They @@ -2715,6 +2724,7 @@ exp_spawnl TCL_VARARGS_DEF(char *,arg1) argv[i] = va_arg(args,char *); if (!argv[i]) break; } + va_end(args); i = exp_spawnv(argv[0],argv+1); free((char *)argv); return(i); @@ -3188,6 +3198,7 @@ exp_expectl TCL_VARARGS_DEF(int,arg1) /* 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); + va_end(args); sysreturn(EINVAL); } @@ -3253,6 +3264,7 @@ exp_fexpectl TCL_VARARGS_DEF(FILE *,arg1) /* 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); + va_end(args); sysreturn(EINVAL); } diff --git a/exp_clib.c.covscan-fixes b/exp_clib.c.covscan-fixes new file mode 100644 index 0000000..3127d61 --- /dev/null +++ b/exp_clib.c.covscan-fixes @@ -0,0 +1,3432 @@ +/* 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 +#include +#ifdef HAVE_INTTYPES_H +# include +#endif +#include +#include + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#ifdef CRAY +# ifndef TCSETCTTY +# if defined(HAVE_TERMIOS) +# include +# else +# include +# endif +# endif +#endif + +#ifdef HAVE_SYS_FCNTL_H +# include +#else +# include +#endif + +#ifdef HAVE_STRREDIR_H +#include +# ifdef SRIOCSREDIR +# undef TIOCCONS +# endif +#endif + +#include +/*#include - deprecated - ANSI C moves them into string.h */ +#include "string.h" + +#include + +#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.38 2010/07/01 00:53:49 eee Exp $ + */ + +#ifndef _STDLIB +#define _STDLIB + +#include + +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 /* 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.38 2010/07/01 00:53:49 eee 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) { + ckfree ((char*) r); + 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 */ +# include + 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 , 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 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,"<>",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 + + 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;pre,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 + +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(""); + + /* 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); +} diff --git a/exp_log.c b/exp_log.c index 225ad48..4131720 100644 --- a/exp_log.c +++ b/exp_log.c @@ -174,7 +174,10 @@ expStdoutLog TCL_VARARGS_DEF(int,arg1) force_stdout = TCL_VARARGS_START(int,arg1,args); fmt = va_arg(args,char *); - if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return; + if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) { + va_end(args); + return; + } (void) vsnprintf(bigbuf,sizeof(bigbuf),fmt,args); expDiagWriteBytes(bigbuf,-1); diff --git a/exp_log.c.covscan-fixes b/exp_log.c.covscan-fixes new file mode 100644 index 0000000..225ad48 --- /dev/null +++ b/exp_log.c.covscan-fixes @@ -0,0 +1,768 @@ +/* exp_log.c - logging routines and other things common to both Expect + program and library. Note that this file must NOT have any + references to Tcl except for including tclInt.h +*/ + +#include "expect_cf.h" +#include +/*#include tclInt.h drags in varargs.h. Since Pyramid */ +/* objects to including varargs.h twice, just */ +/* omit this one. */ +#include "tclInt.h" +#ifdef NO_STDLIB_H +#include "../compat/stdlib.h" +#else +#include /* for malloc */ +#endif +#include + +#include "expect_comm.h" +#include "exp_int.h" +#include "exp_rename.h" +#include "exp_command.h" +#include "exp_log.h" + +typedef struct ThreadSpecificData { + Tcl_Channel diagChannel; + Tcl_DString diagFilename; + int diagToStderr; + + Tcl_Channel logChannel; + Tcl_DString logFilename; /* if no name, then it came from -open or -leaveopen */ + int logAppend; + int logLeaveOpen; + int logAll; /* if TRUE, write log of all interactions + * despite value of logUser - i.e., even if + * user is not seeing it (via stdout) + */ + int logUser; /* TRUE if user sees interactions on stdout */ +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; + +/* + * create a reasonably large buffer for the bulk of the output routines + * that are not too large + */ +static char bigbuf[2000]; + +static void expDiagWriteCharsUni _ANSI_ARGS_((Tcl_UniChar *str,int len)); + +/* + * Following this are several functions that log the conversation. Some + * general notes on all of them: + */ + +/* + * ignore sprintf return value ("character count") because it's not + * defined in terms of UTF so it would be misinterpreted if we passed + * it on. + */ + +/* + * if necessary, they could be made more efficient by skipping vsprintf based + * on booleans + */ + +/* Most of them have multiple calls to printf-style functions. */ +/* At first glance, it seems stupid to reformat the same arguments again */ +/* but we have no way of telling how long the formatted output will be */ +/* and hence cannot allocate a buffer to do so. */ +/* Fortunately, in production code, most of the duplicate reformatting */ +/* will be skipped, since it is due to handling errors and debugging. */ + +/* + * Name: expWriteBytesAndLogIfTtyU + * + * Output to channel (and log if channel is stdout or devtty) + * + * Returns: TCL_OK or TCL_ERROR; + */ + +int +expWriteBytesAndLogIfTtyU(esPtr,buf,lenChars) + ExpState *esPtr; + Tcl_UniChar *buf; + int lenChars; +{ + int wc; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (esPtr->valid) + wc = expWriteCharsUni(esPtr,buf,lenChars); + + if (tsdPtr->logChannel && ((esPtr->fdout == 1) || expDevttyIs(esPtr))) { + Tcl_DString ds; + Tcl_DStringInit (&ds); + Tcl_UniCharToUtfDString (buf,lenChars,&ds); + Tcl_WriteChars(tsdPtr->logChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds)); + Tcl_DStringFree (&ds); + } + return wc; +} + +/* + * Name: expLogDiagU + * + * Send to the Log (and Diag if open). This is for writing to the log. + * (In contrast, expDiagLog... is for writing diagnostics.) + */ + +void +expLogDiagU(buf) +char *buf; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + expDiagWriteChars(buf,-1); + if (tsdPtr->logChannel) { + Tcl_WriteChars(tsdPtr->logChannel, buf, -1); + } +} + +/* + * Name: expLogInteractionU + * + * Show chars to user if they've requested it, UNLESS they're seeing it + * already because they're typing it and tty driver is echoing it. + * Also send to Diag and Log if appropriate. + */ +void +expLogInteractionU(esPtr,buf,buflen) + ExpState *esPtr; + Tcl_UniChar *buf; + int buflen; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (tsdPtr->logAll || (tsdPtr->logUser && tsdPtr->logChannel)) { + Tcl_DString ds; + Tcl_DStringInit (&ds); + Tcl_UniCharToUtfDString (buf,buflen,&ds); + Tcl_WriteChars(tsdPtr->logChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds)); + Tcl_DStringFree (&ds); + } + + /* hmm.... if stdout is closed such as by disconnect, loguser + should be forced FALSE */ + + /* don't write to user if they're seeing it already, i.e., typing it! */ + if (tsdPtr->logUser && (!expStdinoutIs(esPtr)) && (!expDevttyIs(esPtr))) { + ExpState *stdinout = expStdinoutGet(); + if (stdinout->valid) { + (void) expWriteCharsUni(stdinout,buf,buflen); + } + } + expDiagWriteCharsUni(buf,buflen); +} + +/* send to log if open */ +/* send to stderr if debugging enabled */ +/* use this for logging everything but the parent/child conversation */ +/* (this turns out to be almost nothing) */ +/* uppercase L differentiates if from math function of same name */ +#define LOGUSER (tsdPtr->logUser || force_stdout) +/*VARARGS*/ +void +expStdoutLog TCL_VARARGS_DEF(int,arg1) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int force_stdout; + char *fmt; + va_list args; + + force_stdout = TCL_VARARGS_START(int,arg1,args); + fmt = va_arg(args,char *); + + if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return; + + (void) vsnprintf(bigbuf,sizeof(bigbuf),fmt,args); + expDiagWriteBytes(bigbuf,-1); + if (tsdPtr->logAll || (LOGUSER && tsdPtr->logChannel)) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1); + if (LOGUSER) fprintf(stdout,"%s",bigbuf); + va_end(args); +} + +/* just like log but does no formatting */ +/* send to log if open */ +/* use this function for logging the parent/child conversation */ +void +expStdoutLogU(buf,force_stdout) +char *buf; +int force_stdout; /* override value of logUser */ +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int length; + + if ((!tsdPtr->logUser) && (!force_stdout) && (!tsdPtr->logAll)) return; + + length = strlen(buf); + expDiagWriteBytes(buf,length); + if (tsdPtr->logAll || (LOGUSER && tsdPtr->logChannel)) Tcl_WriteChars(tsdPtr->logChannel,buf,-1); + if (LOGUSER) { +#if (TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1)) + Tcl_WriteChars (Tcl_GetStdChannel (TCL_STDOUT), buf, length); + Tcl_Flush (Tcl_GetStdChannel (TCL_STDOUT)); +#else + fwrite(buf,1,length,stdout); +#endif + } +} + +/* send to log if open */ +/* send to stderr */ +/* use this function for error conditions */ +/*VARARGS*/ +void +expErrorLog TCL_VARARGS_DEF(char *,arg1) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + char *fmt; + va_list args; + + fmt = TCL_VARARGS_START(char *,arg1,args); + (void) vsnprintf(bigbuf,sizeof(bigbuf),fmt,args); + + expDiagWriteChars(bigbuf,-1); + fprintf(stderr,"%s",bigbuf); + if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1); + + va_end(args); +} + +/* just like errorlog but does no formatting */ +/* send to log if open */ +/* use this function for logging the parent/child conversation */ +/*ARGSUSED*/ +void +expErrorLogU(buf) +char *buf; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + int length = strlen(buf); + fwrite(buf,1,length,stderr); + expDiagWriteChars(buf,-1); + if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,buf,-1); +} + + + +/* send diagnostics to Diag, Log, and stderr */ +/* use this function for recording unusual things in the log */ +/*VARARGS*/ +void +expDiagLog TCL_VARARGS_DEF(char *,arg1) +{ + char *fmt; + va_list args; + + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return; + + fmt = TCL_VARARGS_START(char *,arg1,args); + + (void) vsnprintf(bigbuf,sizeof(bigbuf),fmt,args); + + expDiagWriteBytes(bigbuf,-1); + if (tsdPtr->diagToStderr) { + fprintf(stderr,"%s",bigbuf); + if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,bigbuf,-1); + } + + va_end(args); +} + + +/* expDiagLog for unformatted strings + this also takes care of arbitrary large strings */ +void +expDiagLogU(str) +char *str; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if ((tsdPtr->diagToStderr == 0) && (tsdPtr->diagChannel == 0)) return; + + expDiagWriteBytes(str,-1); + + if (tsdPtr->diagToStderr) { + fprintf(stderr,"%s",str); + if (tsdPtr->logChannel) Tcl_WriteChars(tsdPtr->logChannel,str,-1); + } +} + +/* expPrintf prints to stderr. It's just a utility for making + debugging easier. */ + +/*VARARGS*/ +void +expPrintf TCL_VARARGS_DEF(char *,arg1) +{ + char *fmt; + va_list args; + char bigbuf[2000]; + int len, rc; + + fmt = TCL_VARARGS_START(char *,arg1,args); + len = vsnprintf(bigbuf,sizeof(bigbuf),arg1,args); + retry: + rc = write(2,bigbuf,len); + if ((rc == -1) && (errno == EAGAIN)) goto retry; + + va_end(args); +} + + +void +expDiagToStderrSet(val) + int val; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + tsdPtr->diagToStderr = val; +} + + +int +expDiagToStderrGet() { + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->diagToStderr; +} + +Tcl_Channel +expDiagChannelGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->diagChannel; +} + +void +expDiagChannelClose(interp) + Tcl_Interp *interp; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->diagChannel) return; + Tcl_UnregisterChannel(interp,tsdPtr->diagChannel); + Tcl_DStringFree(&tsdPtr->diagFilename); + tsdPtr->diagChannel = 0; +} + +/* currently this registers the channel, however the exp_internal + command doesn't currently give the channel name to the user so + this is kind of useless - but we might change this someday */ +int +expDiagChannelOpen(interp,filename) + Tcl_Interp *interp; + char *filename; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + char *newfilename; + + Tcl_ResetResult(interp); + newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->diagFilename); + if (!newfilename) return TCL_ERROR; + + /* Tcl_TildeSubst doesn't store into dstring */ + /* if no ~, so force string into dstring */ + /* this is only needed so that next time around */ + /* we can get dstring for -info if necessary */ + if (Tcl_DStringValue(&tsdPtr->diagFilename)[0] == '\0') { + Tcl_DStringAppend(&tsdPtr->diagFilename,filename,-1); + } + + tsdPtr->diagChannel = Tcl_OpenFileChannel(interp,newfilename,"a",0777); + if (!tsdPtr->diagChannel) { + Tcl_DStringFree(&tsdPtr->diagFilename); + return TCL_ERROR; + } + Tcl_RegisterChannel(interp,tsdPtr->diagChannel); + Tcl_SetChannelOption(interp,tsdPtr->diagChannel,"-buffering","none"); + return TCL_OK; +} + +void +expDiagWriteObj(obj) + Tcl_Obj *obj; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->diagChannel) return; + + Tcl_WriteObj(tsdPtr->diagChannel,obj); +} + +/* write 8-bit bytes */ +void +expDiagWriteBytes(str,len) +char *str; +int len; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->diagChannel) return; + + Tcl_Write(tsdPtr->diagChannel,str,len); +} + +/* write UTF chars */ +void +expDiagWriteChars(str,len) +char *str; +int len; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->diagChannel) return; + + Tcl_WriteChars(tsdPtr->diagChannel,str,len); +} + +/* write Unicode chars */ +static void +expDiagWriteCharsUni(str,len) +Tcl_UniChar *str; +int len; +{ + Tcl_DString ds; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->diagChannel) return; + + Tcl_DStringInit (&ds); + Tcl_UniCharToUtfDString (str,len,&ds); + Tcl_WriteChars(tsdPtr->diagChannel,Tcl_DStringValue (&ds), Tcl_DStringLength (&ds)); + Tcl_DStringFree (&ds); +} + +char * +expDiagFilename() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + return Tcl_DStringValue(&tsdPtr->diagFilename); +} + +void +expLogChannelClose(interp) + Tcl_Interp *interp; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (!tsdPtr->logChannel) return; + + if (Tcl_DStringLength(&tsdPtr->logFilename)) { + /* it's a channel that we created */ + Tcl_UnregisterChannel(interp,tsdPtr->logChannel); + Tcl_DStringFree(&tsdPtr->logFilename); + } else { + /* it's a channel that tcl::open created */ + if (!tsdPtr->logLeaveOpen) { + Tcl_UnregisterChannel(interp,tsdPtr->logChannel); + } + } + tsdPtr->logChannel = 0; + tsdPtr->logAll = 0; /* can't write to log if none open! */ +} + +/* currently this registers the channel, however the exp_log_file + command doesn't currently give the channel name to the user so + this is kind of useless - but we might change this someday */ +int +expLogChannelOpen(interp,filename,append) + Tcl_Interp *interp; + char *filename; + int append; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + char *newfilename; + char mode[2]; + + if (append) { + strcpy(mode,"a"); + } else { + strcpy(mode,"w"); + } + + Tcl_ResetResult(interp); + newfilename = Tcl_TranslateFileName(interp,filename,&tsdPtr->logFilename); + if (!newfilename) return TCL_ERROR; + + /* Tcl_TildeSubst doesn't store into dstring */ + /* if no ~, so force string into dstring */ + /* this is only needed so that next time around */ + /* we can get dstring for -info if necessary */ + if (Tcl_DStringValue(&tsdPtr->logFilename)[0] == '\0') { + Tcl_DStringAppend(&tsdPtr->logFilename,filename,-1); + } + + tsdPtr->logChannel = Tcl_OpenFileChannel(interp,newfilename,mode,0666); + if (!tsdPtr->logChannel) { + Tcl_DStringFree(&tsdPtr->logFilename); + return TCL_ERROR; + } + Tcl_RegisterChannel(interp,tsdPtr->logChannel); + Tcl_SetChannelOption(interp,tsdPtr->logChannel,"-buffering","none"); + expLogAppendSet(append); + return TCL_OK; +} + +int +expLogAppendGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->logAppend; +} + +void +expLogAppendSet(app) + int app; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->logAppend = app; +} + +int +expLogAllGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->logAll; +} + +void +expLogAllSet(app) + int app; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->logAll = app; + /* should probably confirm logChannel != 0 */ +} + +int +expLogToStdoutGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->logUser; +} + +void +expLogToStdoutSet(app) + int app; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->logUser = app; +} + +int +expLogLeaveOpenGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->logLeaveOpen; +} + +void +expLogLeaveOpenSet(app) + int app; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->logLeaveOpen = app; +} + +Tcl_Channel +expLogChannelGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + return tsdPtr->logChannel; +} + +/* to set to a pre-opened channel (presumably by tcl::open) */ +int +expLogChannelSet(interp,name) + Tcl_Interp *interp; + char *name; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + int mode; + + if (0 == (tsdPtr->logChannel = Tcl_GetChannel(interp,name,&mode))) { + return TCL_ERROR; + } + if (!(mode & TCL_WRITABLE)) { + tsdPtr->logChannel = 0; + Tcl_SetResult(interp,"channel is not writable",TCL_VOLATILE); + return TCL_ERROR; + } + return TCL_OK; +} + +char * +expLogFilenameGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + return Tcl_DStringValue(&tsdPtr->logFilename); +} + +int +expLogUserGet() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + return tsdPtr->logUser; +} + +void +expLogUserSet(logUser) + int logUser; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + tsdPtr->logUser = logUser; +} + + + +/* generate printable versions of random ASCII strings. Primarily used */ +/* in diagnostic mode, "expect -d" */ +static char * +expPrintifyReal(s) +char *s; +{ + static int destlen = 0; + static char *dest = 0; + char *d; /* ptr into dest */ + unsigned int need; + Tcl_UniChar ch; + + if (s == 0) return(""); + + /* worst case is every character takes 4 to printify */ + need = strlen(s)*6 + 1; + if (need > destlen) { + if (dest) ckfree(dest); + dest = ckalloc(need); + destlen = need; + } + + for (d = dest;*s;) { + s += Tcl_UtfToUniChar(s, &ch); + if (ch == '\r') { + strcpy(d,"\\r"); d += 2; + } else if (ch == '\n') { + strcpy(d,"\\n"); d += 2; + } else if (ch == '\t') { + strcpy(d,"\\t"); d += 2; + } else if ((ch < 0x80) && isprint(UCHAR(ch))) { + *d = (char)ch; d += 1; + } else { + sprintf(d,"\\u%04x",ch); d += 6; + } + } + *d = '\0'; + return(dest); +} + +/* generate printable versions of random ASCII strings. Primarily used */ +/* in diagnostic mode, "expect -d" */ +static char * +expPrintifyRealUni(s,numchars) +Tcl_UniChar *s; +int numchars; +{ + static int destlen = 0; + static char *dest = 0; + char *d; /* ptr into dest */ + unsigned int need; + Tcl_UniChar ch; + + if (s == 0) return(""); + if (numchars == 0) return(""); + + /* worst case is every character takes 6 to printify */ + need = numchars*6 + 1; + if (need > destlen) { + if (dest) ckfree(dest); + dest = ckalloc(need); + destlen = need; + } + + for (d = dest;numchars > 0;numchars--) { + ch = *s; s++; + + if (ch == '\r') { + strcpy(d,"\\r"); d += 2; + } else if (ch == '\n') { + strcpy(d,"\\n"); d += 2; + } else if (ch == '\t') { + strcpy(d,"\\t"); d += 2; + } else if ((ch < 0x80) && isprint(UCHAR(ch))) { + *d = (char)ch; d += 1; + } else { + sprintf(d,"\\u%04x",ch); d += 6; + } + } + *d = '\0'; + return(dest); +} + +char * +expPrintifyObj(obj) + Tcl_Obj *obj; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* don't bother writing into bigbuf if we're not going to ever use it */ + if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0); + + return expPrintifyReal(Tcl_GetString(obj)); +} + +char * +expPrintify(s) /* INTL */ +char *s; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* don't bother writing into bigbuf if we're not going to ever use it */ + if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0); + + return expPrintifyReal(s); +} + +char * +expPrintifyUni(s,numchars) /* INTL */ +Tcl_UniChar *s; +int numchars; +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + /* don't bother writing into bigbuf if we're not going to ever use it */ + if ((!tsdPtr->diagToStderr) && (!tsdPtr->diagChannel)) return((char *)0); + + return expPrintifyRealUni(s,numchars); +} + +void +expDiagInit() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + Tcl_DStringInit(&tsdPtr->diagFilename); + tsdPtr->diagChannel = 0; + tsdPtr->diagToStderr = 0; +} + +void +expLogInit() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + Tcl_DStringInit(&tsdPtr->logFilename); + tsdPtr->logChannel = 0; + tsdPtr->logAll = FALSE; + tsdPtr->logUser = TRUE; +} diff --git a/exp_main_sub.c b/exp_main_sub.c index 66fa48a..0732736 100644 --- a/exp_main_sub.c +++ b/exp_main_sub.c @@ -57,6 +57,7 @@ int exp_cmdlinecmds = FALSE; int exp_interactive = FALSE; int exp_buffer_command_input = FALSE;/* read in entire cmdfile at once */ int exp_fgets(); +int exp_tty_cooked_echo(Tcl_Interp *interp, exp_tty *tty_old, int *was_raw, int *was_echo); Tcl_Interp *exp_interp; /* for use by signal handlers who can't figure out */ /* the interpreter directly */ diff --git a/exp_main_sub.c.covscan-fixes b/exp_main_sub.c.covscan-fixes new file mode 100644 index 0000000..66fa48a --- /dev/null +++ b/exp_main_sub.c.covscan-fixes @@ -0,0 +1,1040 @@ +/* exp_main_sub.c - miscellaneous subroutines for Expect or Tk main() */ + +#include "expect_cf.h" +#include +#include +#ifdef HAVE_INTTYPES_H +# include +#endif +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#include "tcl.h" +#include "tclInt.h" +#include "exp_rename.h" +#include "exp_prog.h" +#include "exp_command.h" +#include "exp_tty_in.h" +#include "exp_log.h" +#include "exp_event.h" +#ifdef TCL_DEBUGGER +#include "tcldbg.h" +#endif + +#ifndef EXP_VERSION +#define EXP_VERSION PACKAGE_VERSION +#endif +#ifdef __CENTERLINE__ +#undef EXP_VERSION +#define EXP_VERSION "5.45.4" /* I give up! */ + /* It is not necessary that number */ + /* be accurate. It is just here to */ + /* pacify Centerline which doesn't */ + /* seem to be able to get it from */ + /* the Makefile. */ +#undef SCRIPTDIR +#define SCRIPTDIR "example/" +#undef EXECSCRIPTDIR +#define EXECSCRIPTDIR "example/" +#endif +char exp_version[] = PACKAGE_VERSION; +#define NEED_TCL_MAJOR 7 +#define NEED_TCL_MINOR 5 + +char *exp_argv0 = "this program"; /* default program name */ +void (*exp_app_exit)() = 0; +void (*exp_event_exit)() = 0; +FILE *exp_cmdfile = 0; +char *exp_cmdfilename = 0; +int exp_cmdlinecmds = FALSE; +int exp_interactive = FALSE; +int exp_buffer_command_input = FALSE;/* read in entire cmdfile at once */ +int exp_fgets(); + +Tcl_Interp *exp_interp; /* for use by signal handlers who can't figure out */ + /* the interpreter directly */ +int exp_tcl_debugger_available = FALSE; + +int exp_getpid; + +int exp_strict_write = 0; + + +static void +usage(interp) +Tcl_Interp *interp; +{ + char buffer [] = "exit 1"; + expErrorLog("usage: expect [-dDhinNv] [-c cmds] [[-[f|b]] cmdfile] [args]\r\n" + " Flags are as follows:\r\n" + " -b prefaces a file from which to read commands from\r\n" + " (expect reads one line at a timei from the file)\r\n" + " -c prefaces a command to be executed before any in the script,\r\n" + " may be used multiple times\r\n" + " -d enables diagnostic output\r\n" + " -D enables interactive debugger\r\n" + " -f prefaces a file from which to read commands from\r\n" + " (expect reads the whole file at once)\r\n" + " -h prints this usage message and exits\r\n" + " -i interactively prompts for commands\r\n" + " -n expect doesn't read personal rc file\r\n" + " -N expect doesn't read system-wide rc file\r\n" + " -v prints version and exits\r\n"); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + Tcl_Eval(interp, buffer); +} + +/* this clumsiness because pty routines don't know Tcl definitions */ +/*ARGSUSED*/ +static +void +exp_pty_exit_for_tcl(clientData) +ClientData clientData; +{ + exp_pty_exit(); +} + +static +void +exp_init_pty_exit() +{ + Tcl_CreateExitHandler(exp_pty_exit_for_tcl,(ClientData)0); +} + +/* This can be called twice or even recursively - it's safe. */ +void +exp_exit_handlers(clientData) +ClientData clientData; +{ + extern int exp_forked; + + Tcl_Interp *interp = (Tcl_Interp *)clientData; + + /* use following checks to prevent recursion in exit handlers */ + /* if this code ever supports multiple interps, these should */ + /* become interp-specific */ + + static int did_app_exit = FALSE; + static int did_expect_exit = FALSE; + + if (!did_expect_exit) { + did_expect_exit = TRUE; + /* called user-defined exit routine if one exists */ + if (exp_onexit_action) { + int result = Tcl_GlobalEval(interp,exp_onexit_action); + if (result != TCL_OK) Tcl_BackgroundError(interp); + } + } else { + expDiagLogU("onexit handler called recursively - forcing exit\r\n"); + } + + if (exp_app_exit) { + if (!did_app_exit) { + did_app_exit = TRUE; + (*exp_app_exit)(interp); + } else { + expDiagLogU("application exit handler called recursively - forcing exit\r\n"); + } + } + + if (!exp_disconnected + && !exp_forked + && (exp_dev_tty != -1) + && isatty(exp_dev_tty)) { + if (exp_ioctled_devtty) { + exp_tty_set(interp,&exp_tty_original,exp_dev_tty,0); + } + } + /* all other files either don't need to be flushed or will be + implicitly closed at exit. Spawned processes are free to continue + running, however most will shutdown after seeing EOF on stdin. + Some systems also deliver SIGHUP and other sigs to idle processes + which will blow them away if not prepared. + */ + + exp_close_all(interp); +} + +static int +history_nextid(interp) +Tcl_Interp *interp; +{ + /* unncessarily tricky coding - if nextid isn't defined, + maintain our own static version */ + + static int nextid = 0; + CONST char *nextidstr = Tcl_GetVar2(interp,"tcl::history","nextid",0); + if (nextidstr) { + /* intentionally ignore failure */ + (void) sscanf(nextidstr,"%d",&nextid); + } + return ++nextid; +} + +/* this stupidity because Tcl needs commands in writable space */ +static char prompt1[] = "prompt1"; +static char prompt2[] = "prompt2"; + +static char *prompt2_default = "+> "; +static char prompt1_default[] = "expect%d.%d> "; + +/*ARGSUSED*/ +int +Exp_Prompt1ObjCmd(clientData, interp, objc, objv) +ClientData clientData; +Tcl_Interp *interp; +int objc; +Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + static char buffer[200]; + + Interp *iPtr = (Interp *)interp; + + sprintf(buffer,prompt1_default,iPtr->numLevels,history_nextid(interp)); + Tcl_SetResult(interp,buffer,TCL_STATIC); + return(TCL_OK); +} + +/*ARGSUSED*/ +int +Exp_Prompt2ObjCmd(clientData, interp, objc, objv) +ClientData clientData; +Tcl_Interp *interp; +int objc; +Tcl_Obj *CONST objv[]; +{ + Tcl_SetResult(interp,prompt2_default,TCL_STATIC); + return(TCL_OK); +} + +/*ARGSUSED*/ +static int +ignore_procs(interp,s) +Tcl_Interp *interp; +char *s; /* function name */ +{ + return ((s[0] == 'p') && + (s[1] == 'r') && + (s[2] == 'o') && + (s[3] == 'm') && + (s[4] == 'p') && + (s[5] == 't') && + ((s[6] == '1') || + (s[6] == '2')) && + (s[7] == '\0') + ); +} + +/* handle an error from Tcl_Eval or Tcl_EvalFile */ +static void +handle_eval_error(interp,check_for_nostack) +Tcl_Interp *interp; +int check_for_nostack; +{ + char *msg; + + /* if errorInfo has something, print it */ + /* else use what's in the interp result */ + + msg = Tcl_GetVar(interp,"errorInfo",TCL_GLOBAL_ONLY); + if (!msg) msg = Tcl_GetStringResult (interp); + else if (check_for_nostack) { + /* suppress errorInfo if generated via */ + /* error ... -nostack */ + if (0 == strncmp("-nostack",msg,8)) return; + + /* + * This shouldn't be necessary, but previous test fails + * because of recent change John made - see eval_trap_action() + * in exp_trap.c for more info + */ + if (exp_nostack_dump) { + exp_nostack_dump = FALSE; + return; + } + } + + /* no \n at end, since ccmd will already have one. */ + /* Actually, this is not true if command is last in */ + /* file and has no newline after it, oh well */ + expErrorLogU(exp_cook(msg,(int *)0)); + expErrorLogU("\r\n"); +} + +/* user has pressed escape char from interact or somehow requested expect. +If a user-supplied command returns: + +TCL_ERROR, assume user is experimenting and reprompt +TCL_OK, ditto +TCL_RETURN, return TCL_OK (assume user just wants to escape() to return) +EXP_TCL_RETURN, return TCL_RETURN +anything else return it +*/ +int +exp_interpreter(interp,eofObj) +Tcl_Interp *interp; +Tcl_Obj *eofObj; +{ + Tcl_Obj *commandPtr = NULL; + int code; + int gotPartial; + Interp *iPtr = (Interp *)interp; + int tty_changed = FALSE; + exp_tty tty_old; + int was_raw, was_echo; + + Tcl_Channel inChannel, outChannel; + ExpState *esPtr = expStdinoutGet(); + /* int fd = fileno(stdin);*/ + + expect_key++; + commandPtr = Tcl_NewObj(); + Tcl_IncrRefCount(commandPtr); + + gotPartial = 0; + while (TRUE) { + if (Tcl_IsShared(commandPtr)) { + Tcl_DecrRefCount(commandPtr); + commandPtr = Tcl_DuplicateObj(commandPtr); + Tcl_IncrRefCount(commandPtr); + } + outChannel = expStdinoutGet()->channel; + if (outChannel) { + Tcl_Flush(outChannel); + } + if (!esPtr->open) { + code = EXP_EOF; + goto eof; + } + + /* force terminal state */ + tty_changed = exp_tty_cooked_echo(interp,&tty_old,&was_raw,&was_echo); + + if (!gotPartial) { + code = Tcl_Eval(interp,prompt1); + if (code == TCL_OK) { + expStdoutLogU(Tcl_GetStringResult(interp),1); + } + else expStdoutLog(1,prompt1_default,iPtr->numLevels,history_nextid(interp)); + } else { + code = Tcl_Eval(interp,prompt2); + if (code == TCL_OK) { + expStdoutLogU(Tcl_GetStringResult(interp),1); + } + else expStdoutLogU(prompt2_default,1); + } + + esPtr->force_read = 1; + code = exp_get_next_event(interp,&esPtr,1,&esPtr,EXP_TIME_INFINITY, + esPtr->key); + /* check for code == EXP_TCLERROR? */ + + if (code != EXP_EOF) { + inChannel = expStdinoutGet()->channel; + code = Tcl_GetsObj(inChannel, commandPtr); +#ifdef SIMPLE_EVENT + if (code == -1 && errno == EINTR) { + if (Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(interp,TCL_OK); + } + continue; + } +#endif + if (code < 0) code = EXP_EOF; + if ((code == 0) && Tcl_Eof(inChannel) && !gotPartial) code = EXP_EOF; + } + + eof: + if (code == EXP_EOF) { + if (eofObj) { + code = Tcl_EvalObjEx(interp,eofObj,0); + } else { + code = TCL_OK; + } + goto done; + } + + expDiagWriteObj(commandPtr); + /* intentionally always write to logfile */ + if (expLogChannelGet()) { + Tcl_WriteObj(expLogChannelGet(),commandPtr); + } + /* no need to write to stdout, since they will see */ + /* it just from it having been echoed as they are */ + /* typing it */ + + /* + * Add the newline removed by Tcl_GetsObj back to the string. + */ + + if (Tcl_IsShared(commandPtr)) { + Tcl_DecrRefCount(commandPtr); + commandPtr = Tcl_DuplicateObj(commandPtr); + Tcl_IncrRefCount(commandPtr); + } + Tcl_AppendToObj(commandPtr, "\n", 1); + if (!TclObjCommandComplete(commandPtr)) { + gotPartial = 1; + continue; + } + + Tcl_AppendToObj(commandPtr, "\n", 1); + if (!TclObjCommandComplete(commandPtr)) { + gotPartial = 1; + continue; + } + + gotPartial = 0; + + if (tty_changed) exp_tty_set(interp,&tty_old,was_raw,was_echo); + + code = Tcl_RecordAndEvalObj(interp, commandPtr, 0); + Tcl_DecrRefCount(commandPtr); + commandPtr = Tcl_NewObj(); + Tcl_IncrRefCount(commandPtr); + switch (code) { + char *str; + + case TCL_OK: + str = Tcl_GetStringResult(interp); + if (*str != 0) { + expStdoutLogU(exp_cook(str,(int *)0),1); + expStdoutLogU("\r\n",1); + } + continue; + case TCL_ERROR: + handle_eval_error(interp,1); + /* since user is typing by hand, we expect lots */ + /* of errors, and want to give another chance */ + continue; +#define finish(x) {code = x; goto done;} + case TCL_BREAK: + case TCL_CONTINUE: + finish(code); + case EXP_TCL_RETURN: + finish(TCL_RETURN); + case TCL_RETURN: + finish(TCL_OK); + default: + /* note that ccmd has trailing newline */ + expErrorLog("error %d: ",code); + expErrorLogU(Tcl_GetString(Tcl_GetObjResult(interp))); + expErrorLogU("\r\n"); + continue; + } + } + /* cannot fall thru here, must jump to label */ + done: + if (tty_changed) exp_tty_set(interp,&tty_old,was_raw,was_echo); + + Tcl_DecrRefCount(commandPtr); + return(code); +} + +/*ARGSUSED*/ +int +Exp_ExpVersionObjCmd(clientData, interp, objc, objv) +ClientData clientData; +Tcl_Interp *interp; + int objc; + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + int emajor, umajor; + char *user_version; /* user-supplied version string */ + + if (objc == 1) { + Tcl_SetResult(interp,exp_version,TCL_STATIC); + return(TCL_OK); + } + if (objc > 3) { + exp_error(interp,"usage: expect_version [[-exit] version]"); + return(TCL_ERROR); + } + + user_version = Tcl_GetString (objv[objc==2?1:2]); + emajor = atoi(exp_version); + umajor = atoi(user_version); + + /* first check major numbers */ + if (emajor == umajor) { + int u, e; + + /* now check minor numbers */ + char *dot = strchr(user_version,'.'); + if (!dot) { + exp_error(interp,"version number must include a minor version number"); + return TCL_ERROR; + } + + u = atoi(dot+1); + dot = strchr(exp_version,'.'); + e = atoi(dot+1); + if (e >= u) return(TCL_OK); + } + + if (objc == 2) { + exp_error(interp,"%s requires Expect version %s (but using %s)", + exp_argv0,user_version,exp_version); + return(TCL_ERROR); + } + expErrorLog("%s requires Expect version %s (but is using %s)\r\n", + exp_argv0,user_version,exp_version); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + /*NOTREACHED, but keep compiler from complaining*/ + return TCL_ERROR; +} + +static char init_auto_path[] = "\ +if {$exp_library != \"\"} {\n\ + lappend auto_path $exp_library\n\ +}\n\ +if {$exp_exec_library != \"\"} {\n\ + lappend auto_path $exp_exec_library\n\ +}"; + +static void +DeleteCmdInfo (clientData, interp) + ClientData clientData; + Tcl_Interp *interp; +{ + ckfree (clientData); +} + + +int +Expect_Init(interp) +Tcl_Interp *interp; +{ + static int first_time = TRUE; + + Tcl_CmdInfo* close_info = NULL; + Tcl_CmdInfo* return_info = NULL; + + if (first_time) { +#ifndef USE_TCL_STUBS + int tcl_major = atoi(TCL_VERSION); + char *dot = strchr(TCL_VERSION,'.'); + int tcl_minor = atoi(dot+1); + + if (tcl_major < NEED_TCL_MAJOR || + (tcl_major == NEED_TCL_MAJOR && tcl_minor < NEED_TCL_MINOR)) { + + char bufa [20]; + char bufb [20]; + Tcl_Obj* s = Tcl_NewStringObj (exp_argv0,-1); + + sprintf(bufa,"%d.%d",tcl_major,tcl_minor); + sprintf(bufb,"%d.%d",NEED_TCL_MAJOR,NEED_TCL_MINOR); + + Tcl_AppendStringsToObj (s, + " compiled with Tcl ", bufa, + " but needs at least Tcl ", bufb, + "\n", NULL); + Tcl_SetObjResult (interp, s); + return TCL_ERROR; + } +#endif + } + +#ifndef USE_TCL_STUBS + if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) { + return TCL_ERROR; + } +#else + if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { + return TCL_ERROR; + } +#endif + + /* + * Save initial close and return for later use + */ + + close_info = (Tcl_CmdInfo*) ckalloc (sizeof (Tcl_CmdInfo)); + if (Tcl_GetCommandInfo(interp, "close", close_info) == 0) { + ckfree ((char*) close_info); + return TCL_ERROR; + } + return_info = (Tcl_CmdInfo*) ckalloc (sizeof (Tcl_CmdInfo)); + if (Tcl_GetCommandInfo(interp, "return", return_info) == 0){ + ckfree ((char*) close_info); + ckfree ((char*) return_info); + return TCL_ERROR; + } + Tcl_SetAssocData (interp, EXP_CMDINFO_CLOSE, DeleteCmdInfo, (ClientData) close_info); + Tcl_SetAssocData (interp, EXP_CMDINFO_RETURN, DeleteCmdInfo, (ClientData) return_info); + + /* + * Expect redefines close so we need to save the original (pre-expect) + * definition so it can be restored before exiting. + * + * Needed when expect is dynamically loaded after close has + * been redefined e.g. the virtual file system in tclkit + */ + if (TclRenameCommand(interp, "close", "_close.pre_expect") != TCL_OK) { + return TCL_ERROR; + } + + if (Tcl_PkgProvide(interp, "Expect", PACKAGE_VERSION) != TCL_OK) { + return TCL_ERROR; + } + + Tcl_Preserve(interp); + Tcl_CreateExitHandler(Tcl_Release,(ClientData)interp); + + if (first_time) { + exp_getpid = getpid(); + exp_init_pty(); + exp_init_pty_exit(); + exp_init_tty(); /* do this only now that we have looked at */ + /* original tty state */ + exp_init_stdio(); + exp_init_sig(); + exp_init_event(); + exp_init_trap(); + exp_init_unit_random(); + exp_init_spawn_ids(interp); + expChannelInit(); + expDiagInit(); + expLogInit(); + expDiagLogPtrSet(expDiagLogU); + expErrnoMsgSet(Tcl_ErrnoMsg); + + Tcl_CreateExitHandler(exp_exit_handlers,(ClientData)interp); + + first_time = FALSE; + } + + /* save last known interp for emergencies */ + exp_interp = interp; + + /* initialize commands */ + exp_init_most_cmds(interp); /* add misc cmds to interpreter */ + exp_init_expect_cmds(interp); /* add expect cmds to interpreter */ + exp_init_main_cmds(interp); /* add main cmds to interpreter */ + exp_init_trap_cmds(interp); /* add trap cmds to interpreter */ + exp_init_tty_cmds(interp); /* add tty cmds to interpreter */ + exp_init_interact_cmds(interp); /* add interact cmds to interpreter */ + + /* initialize variables */ + exp_init_spawn_id_vars(interp); + expExpectVarsInit(); + + /* + * For each of the the Tcl variables, "expect_library", + *"exp_library", and "exp_exec_library", set the variable + * if it does not already exist. This mechanism allows the + * application calling "Expect_Init()" to set these varaibles + * to alternate locations from where Expect was built. + */ + + if (Tcl_GetVar(interp, "expect_library", TCL_GLOBAL_ONLY) == NULL) { + Tcl_SetVar(interp,"expect_library",SCRIPTDIR,0);/* deprecated */ + } + if (Tcl_GetVar(interp, "exp_library", TCL_GLOBAL_ONLY) == NULL) { + Tcl_SetVar(interp,"exp_library",SCRIPTDIR,0); + } + if (Tcl_GetVar(interp, "exp_exec_library", TCL_GLOBAL_ONLY) == NULL) { + Tcl_SetVar(interp,"exp_exec_library",EXECSCRIPTDIR,0); + } + + Tcl_Eval(interp,init_auto_path); + Tcl_ResetResult(interp); + +#ifdef TCL_DEBUGGER + Dbg_IgnoreFuncs(interp,ignore_procs); +#endif + + return TCL_OK; +} + +static char sigint_init_default[80]; +static char sigterm_init_default[80]; +static char debug_init_default[] = "trap {exp_debug 1} SIGINT"; + +void +exp_parse_argv(interp,argc,argv) +Tcl_Interp *interp; +int argc; +char **argv; +{ + char argc_rep[10]; /* enough space for storing literal rep of argc */ + + int sys_rc = TRUE; /* read system rc file */ + int my_rc = TRUE; /* read personal rc file */ + + int c; + int rc; + + extern int optind; + extern char *optarg; + char *args; /* ptr to string-rep of all args */ + char *debug_init; + + exp_argv0 = argv[0]; + +#ifdef TCL_DEBUGGER + Dbg_ArgcArgv(argc,argv,1); +#endif + + /* initially, we must assume we are not interactive */ + /* this prevents interactive weirdness courtesy of unknown via -c */ + /* after handling args, we can change our mind */ + Tcl_SetVar(interp, "tcl_interactive", "0", TCL_GLOBAL_ONLY); + + /* there's surely a system macro to do this but I don't know what it is */ +#define EXP_SIG_EXIT(signalnumber) (0x80|signalnumber) + + sprintf(sigint_init_default, "trap {exit %d} SIGINT", EXP_SIG_EXIT(SIGINT)); + Tcl_Eval(interp,sigint_init_default); + sprintf(sigterm_init_default,"trap {exit %d} SIGTERM",EXP_SIG_EXIT(SIGTERM)); + Tcl_Eval(interp,sigterm_init_default); + + /* + * [#418892]. The '+' character in front of every other option + * declaration causes 'GNU getopt' to deactivate its + * non-standard behaviour and switch to POSIX. Other + * implementations of 'getopt' might recognize the option '-+' + * because of this, but the following switch will catch this + * and generate a usage message. + */ + + while ((c = getopt(argc, argv, "+b:c:dD:f:inN-vh")) != EOF) { + switch(c) { + case '-': + /* getopt already handles -- internally, however */ + /* this allows us to abort getopt when dash is at */ + /* the end of another option which is required */ + /* in order to allow things like -n- on #! line */ + goto abort_getopt; + case 'c': /* command */ + exp_cmdlinecmds = TRUE; + rc = Tcl_Eval(interp,optarg); + if (rc != TCL_OK) { + expErrorLogU(exp_cook(Tcl_GetVar(interp,"errorInfo",TCL_GLOBAL_ONLY),(int *)0)); + expErrorLogU("\r\n"); + } + break; + case 'd': expDiagToStderrSet(TRUE); + expDiagLog("expect version %s\r\n",exp_version); + break; +#ifdef TCL_DEBUGGER + case 'D': + exp_tcl_debugger_available = TRUE; + if (Tcl_GetInt(interp,optarg,&rc) != TCL_OK) { + expErrorLog("%s: -D argument must be 0 or 1\r\n",exp_argv0); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 1"; + Tcl_Eval(interp, buffer); + } + } + + /* set up trap handler before Dbg_On so user does */ + /* not have to see it at first debugger prompt */ + if (0 == (debug_init = getenv("EXPECT_DEBUG_INIT"))) { + debug_init = debug_init_default; + } + Tcl_Eval(interp,debug_init); + if (rc == 1) Dbg_On(interp,0); + break; +#endif + case 'f': /* name of cmd file */ + exp_cmdfilename = optarg; + break; + case 'b': /* read cmdfile one part at a time */ + exp_cmdfilename = optarg; + exp_buffer_command_input = TRUE; + break; + case 'i': /* interactive */ + exp_interactive = TRUE; + break; + case 'n': /* don't read personal rc file */ + my_rc = FALSE; + break; + case 'N': /* don't read system-wide rc file */ + sys_rc = FALSE; + break; + case 'v': + printf("expect version %s\n", exp_version); + + /* SF #439042 -- Allow overide of "exit" by user / script + */ + { + char buffer [] = "exit 0"; + Tcl_Eval(interp, buffer); + } + break; + case 'h': + usage(interp); + break; + default: usage(interp); + } + } + + abort_getopt: + + for (c = 0;c /*extern char *sys_errlist[];*/ @@ -189,6 +190,7 @@ static char slave_name[MAXPTYNAMELEN]; #endif /* HAVE_SCO_CLIST_PTYS */ #ifdef HAVE_OPENPTY +#include static char master_name[64]; static char slave_name[64]; #endif diff --git a/pty_termios.c.covscan-fixes b/pty_termios.c.covscan-fixes new file mode 100644 index 0000000..c605b23 --- /dev/null +++ b/pty_termios.c.covscan-fixes @@ -0,0 +1,781 @@ +/* pty_termios.c - routines to allocate ptys - termios version + +Written by: Don Libes, NIST, 2/6/90 + +This file is in the public domain. However, the author and NIST +would appreciate credit if you use this file or parts of it. + +*/ + +#include +#include + +#if defined(SIGCLD) && !defined(SIGCHLD) +#define SIGCHLD SIGCLD +#endif + +#include "expect_cf.h" + +/* + 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. +*/ +extern char *TclGetRegError(); + +#if defined(HAVE_PTMX_BSD) && defined(HAVE_PTMX) +/* + * Some systems have both PTMX and PTMX_BSD. + * In fact, alphaev56-dec-osf4.0e has /dev/pts, /dev/pty, /dev/ptym, + * /dev/ptm, /dev/ptmx, and /dev/ptmx_bsd + * Suggestion from Martin Buchholz is that BSD + * is usually deprecated and so should be here. + */ +#undef HAVE_PTMX_BSD +#endif + +/* Linux and Digital systems can be configured to have both. +According to Ashley Pittman , Digital works better +with openpty which supports 4000 while ptmx supports 60. */ +#if defined(HAVE_OPENPTY) && defined(HAVE_PTMX) +#undef HAVE_PTMX +#endif + +#if defined(HAVE_PTYM) && defined(HAVE_PTMX) +/* + * HP-UX 10.0 with streams (optional) have both PTMX and PTYM. I don't + * know which is preferred but seeing as how the HP trap stuff is so + * unusual, it is probably safer to stick with the native HP pty support, + * too. + */ +#undef HAVE_PTMX +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#include +#include + +#ifdef NO_STDLIB_H +#include "../compat/stdlib.h" +#else +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#ifdef HAVE_SYSMACROS_H +#include +#endif + +#ifdef HAVE_PTYTRAP +#include +#endif + +#include + +#ifdef HAVE_SYS_FCNTL_H +# include +#else +# include +#endif + +#if defined(_SEQUENT_) +# include +#endif + +#if defined(HAVE_PTMX) && defined(HAVE_STROPTS_H) +# include +#endif + +#include "exp_win.h" + +#include "exp_tty_in.h" +#include "exp_rename.h" +#include "exp_pty.h" + +void expDiagLog(); +void expDiagLogPtr(); + +#include +/*extern char *sys_errlist[];*/ + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/* Convex getpty is different than older-style getpty */ +/* Convex getpty is really just a cover function that does the traversal */ +/* across the domain of pty names. It makes no attempt to verify that */ +/* they can actually be used. Indded, the logic in the man page is */ +/* wrong because it will allow you to allocate ptys that your own account */ +/* already has in use. */ +#if defined(HAVE_GETPTY) && defined(CONVEX) +#undef HAVE_GETPTY +#define HAVE_CONVEX_GETPTY +extern char *getpty(); +static char *master_name; +static char slave_name[] = "/dev/ptyXX"; +static char *tty_bank; /* ptr to char [p-z] denoting + which bank it is */ +static char *tty_num; /* ptr to char [0-f] denoting + which number it is */ +#endif + +#if defined(_SEQUENT_) && !defined(HAVE_PTMX) +/* old-style SEQUENT, new-style uses ptmx */ +static char *master_name, *slave_name; +#endif /* _SEQUENT */ + +/* very old SGIs prefer _getpty over ptc */ +#if defined(HAVE__GETPTY) && defined(HAVE_PTC) && !defined(HAVE_GETPTY) +#undef HAVE_PTC +#endif + +#if defined(HAVE_PTC) +static char slave_name[] = "/dev/ttyqXXX"; +/* some machines (e.g., SVR4.0 StarServer) have all of these and */ +/* HAVE_PTC works best */ +#undef HAVE_GETPTY +#undef HAVE__GETPTY +#endif + +#if defined(HAVE__GETPTY) || defined(HAVE_PTC_PTS) || defined(HAVE_PTMX) +static char *slave_name; +#endif + +#if defined(HAVE_GETPTY) +#include +static char master_name[MAXPTYNAMELEN]; +static char slave_name[MAXPTYNAMELEN]; +#endif + +#if !defined(HAVE_GETPTY) && !defined(HAVE__GETPTY) && !defined(HAVE_PTC) && !defined(HAVE_PTC_PTS) && !defined(HAVE_PTMX) && !defined(HAVE_CONVEX_GETPTY) && !defined(_SEQUENT_) && !defined(HAVE_SCO_CLIST_PTYS) && !defined(HAVE_OPENPTY) +#ifdef HAVE_PTYM + /* strange order and missing d is intentional */ +static char banks[] = "pqrstuvwxyzabcefghijklo"; +static char master_name[] = "/dev/ptym/ptyXXXX"; +static char slave_name[] = "/dev/pty/ttyXXXX"; +static char *slave_bank; +static char *slave_num; +#else +static char banks[] = "pqrstuvwxyzPQRSTUVWXYZ"; +static char master_name[] = "/dev/ptyXX"; +static char slave_name [] = "/dev/ttyXX"; +#endif /* HAVE_PTYM */ + +static char *tty_type; /* ptr to char [pt] denoting + whether it is a pty or tty */ +static char *tty_bank; /* ptr to char [p-z] denoting + which bank it is */ +static char *tty_num; /* ptr to char [0-f] denoting + which number it is */ +#endif + +#if defined(HAVE_SCO_CLIST_PTYS) +# define MAXPTYNAMELEN 64 +static char master_name[MAXPTYNAMELEN]; +static char slave_name[MAXPTYNAMELEN]; +#endif /* HAVE_SCO_CLIST_PTYS */ + +#ifdef HAVE_OPENPTY +static char master_name[64]; +static char slave_name[64]; +#endif + +char *exp_pty_slave_name; +char *exp_pty_error; + +#if 0 +static void +pty_stty(s,name) +char *s; /* args to stty */ +char *name; /* name of pty */ +{ +#define MAX_ARGLIST 10240 + char buf[MAX_ARGLIST]; /* overkill is easier */ + RETSIGTYPE (*old)(); /* save old sigalarm handler */ + int pid; + + old = signal(SIGCHLD, SIG_DFL); + switch (pid = fork()) { + case 0: /* child */ + exec_stty(STTY_BIN,STTY_BIN,s); + break; + case -1: /* fail */ + default: /* parent */ + waitpid(pid); + break; + } + + signal(SIGCHLD, old); /* restore signal handler */ +} + +exec_stty(s) +char *s; +{ + char *args[50]; + char *cp; + int argi = 0; + int quoting = FALSE; + int in_token = FALSE; /* TRUE if we are reading a token */ + + args[0] = cp = s; + while (*s) { + if (quoting) { + if (*s == '\\' && *(s+1) == '"') { /* quoted quote */ + s++; /* get past " */ + *cp++ = *s++; + } else if (*s == '\"') { /* close quote */ + end_token + quoting = FALSE; + } else *cp++ = *s++; /* suck up anything */ + } else if (*s == '\"') { /* open quote */ + in_token = TRUE; + quoting = TRUE; + s++; + } else if (isspace(*s)) { + end_token + } else { + *cp++ = *s++; + in_token = TRUE; + } + } + end_token + args[argi] = (char *) 0; /* terminate argv */ + execvp(args[0],args); +} +#endif /*0*/ + +static void +pty_stty(s,name) +char *s; /* args to stty */ +char *name; /* name of pty */ +{ +#define MAX_ARGLIST 10240 + char buf[MAX_ARGLIST]; /* overkill is easier */ + RETSIGTYPE (*old)(); /* save old sigalarm handler */ + +#ifdef STTY_READS_STDOUT + sprintf(buf,"%s %s > %s",STTY_BIN,s,name); +#else + sprintf(buf,"%s %s < %s",STTY_BIN,s,name); +#endif + old = signal(SIGCHLD, SIG_DFL); + system(buf); + signal(SIGCHLD, old); /* restore signal handler */ +} + +int exp_dev_tty; /* file descriptor to /dev/tty or -1 if none */ +static int knew_dev_tty;/* true if we had our hands on /dev/tty at any time */ + +exp_tty exp_tty_original; + +#define GET_TTYTYPE 0 +#define SET_TTYTYPE 1 +static void +ttytype(request,fd,ttycopy,ttyinit,s) +int request; +int fd; + /* following are used only if request == SET_TTYTYPE */ +int ttycopy; /* true/false, copy from /dev/tty */ +int ttyinit; /* if true, initialize to sane state */ +char *s; /* stty args */ +{ + if (request == GET_TTYTYPE) { +#ifdef HAVE_TCSETATTR + if (-1 == tcgetattr(fd, &exp_tty_original)) { +#else + if (-1 == ioctl(fd, TCGETS, (char *)&exp_tty_original)) { +#endif + knew_dev_tty = FALSE; + exp_dev_tty = -1; + } + exp_window_size_get(fd); + } else { /* type == SET_TTYTYPE */ + if (ttycopy && knew_dev_tty) { +#ifdef HAVE_TCSETATTR + (void) tcsetattr(fd, TCSADRAIN, &exp_tty_current); +#else + (void) ioctl(fd, TCSETS, (char *)&exp_tty_current); +#endif + + exp_window_size_set(fd); + } + +#ifdef __CENTERLINE__ +#undef DFLT_STTY +#define DFLT_STTY "sane" +#endif + +/* Apollo Domain doesn't need this */ +#ifdef DFLT_STTY + if (ttyinit) { + /* overlay parms originally supplied by Makefile */ +/* As long as BSD stty insists on stdout == stderr, we can no longer write */ +/* diagnostics to parent stderr, since stderr has is now child's */ +/* Maybe someday they will fix stty? */ +/* expDiagLogPtrStr("exp_getptyslave: (default) stty %s\n",DFLT_STTY);*/ + pty_stty(DFLT_STTY,slave_name); + } +#endif + + /* lastly, give user chance to override any terminal parms */ + if (s) { + /* give user a chance to override any terminal parms */ +/* expDiagLogPtrStr("exp_getptyslave: (user-requested) stty %s\n",s);*/ + pty_stty(s,slave_name); + } + } +} + +void +exp_init_pty() +{ +#if !defined(HAVE_GETPTY) && !defined(HAVE__GETPTY) && !defined(HAVE_PTC) && !defined(HAVE_PTC_PTS) && !defined(HAVE_PTMX) && !defined(HAVE_CONVEX_GETPTY) && !defined(_SEQUENT_) && !defined(HAVE_SCO_CLIST_PTYS) && !defined(HAVE_OPENPTY) +#ifdef HAVE_PTYM + static char dummy; + tty_bank = &master_name[strlen("/dev/ptym/pty")]; + tty_num = &master_name[strlen("/dev/ptym/ptyX")]; + slave_bank = &slave_name[strlen("/dev/pty/tty")]; + slave_num = &slave_name[strlen("/dev/pty/ttyX")]; +#else + tty_bank = &master_name[strlen("/dev/pty")]; + tty_num = &master_name[strlen("/dev/ptyp")]; + tty_type = &slave_name[strlen("/dev/")]; +#endif + +#endif /* HAVE_PTYM */ + + + exp_dev_tty = open("/dev/tty",O_RDWR); + knew_dev_tty = (exp_dev_tty != -1); + if (knew_dev_tty) ttytype(GET_TTYTYPE,exp_dev_tty,0,0,(char *)0); +} + +#ifndef R_OK +/* 3b2 doesn't define these according to jthomas@nmsu.edu. */ +#define R_OK 04 +#define W_OK 02 +#endif + +int +exp_getptymaster() +{ + char *hex, *bank; + struct stat stat_buf; + int master = -1; + int slave = -1; + int num; + + exp_pty_error = 0; + +#define TEST_PTY 1 + +#if defined(HAVE_PTMX) || defined(HAVE_PTMX_BSD) +#undef TEST_PTY +#if defined(HAVE_PTMX_BSD) + if ((master = open("/dev/ptmx_bsd", O_RDWR)) == -1) return(-1); +#else + if ((master = open("/dev/ptmx", O_RDWR)) == -1) return(-1); +#endif + if ((slave_name = (char *)ptsname(master)) == NULL) { + close(master); + return(-1); + } + if (grantpt(master)) { + static char buf[500]; + exp_pty_error = buf; + sprintf(exp_pty_error,"grantpt(%s) failed - likely reason is that your system administrator (in a rage of blind passion to rid the system of security holes) removed setuid from the utility used internally by grantpt to change pty permissions. Tell your system admin to reestablish setuid on the utility. Get the utility name by running Expect under truss or trace.", expErrnoMsg(errno)); + close(master); + return(-1); + } + if (-1 == (int)unlockpt(master)) { + static char buf[500]; + exp_pty_error = buf; + sprintf(exp_pty_error,"unlockpt(%s) failed.", expErrnoMsg(errno)); + close(master); + return(-1); + } +#ifdef TIOCFLUSH + (void) ioctl(master,TIOCFLUSH,(char *)0); +#endif /* TIOCFLUSH */ + + exp_pty_slave_name = slave_name; + return(master); +#endif + +#if defined(HAVE__GETPTY) /* SGI needs it this way */ +#undef TEST_PTY + slave_name = _getpty(&master, O_RDWR, 0600, 0); + if (slave_name == NULL) + return (-1); + exp_pty_slave_name = slave_name; + return(master); +#endif + +#if defined(HAVE_PTC) && !defined(HAVE__GETPTY) /* old SGI, version 3 */ +#undef TEST_PTY + master = open("/dev/ptc", O_RDWR); + if (master >= 0) { + int ptynum; + + if (fstat(master, &stat_buf) < 0) { + close(master); + return(-1); + } + ptynum = minor(stat_buf.st_rdev); + sprintf(slave_name,"/dev/ttyq%d",ptynum); + } + exp_pty_slave_name = slave_name; + return(master); +#endif + +#if defined(HAVE_GETPTY) && !defined(HAVE__GETPTY) +#undef TEST_PTY + master = getpty(master_name, slave_name, O_RDWR); + /* is it really necessary to verify slave side is usable? */ + exp_pty_slave_name = slave_name; + return master; +#endif + +#if defined(HAVE_PTC_PTS) +#undef TEST_PTY + master = open("/dev/ptc",O_RDWR); + if (master >= 0) { + /* never fails */ + slave_name = ttyname(master); + } + exp_pty_slave_name = slave_name; + return(master); +#endif + +#if defined(_SEQUENT_) && !defined(HAVE_PTMX) +#undef TEST_PTY + /* old-style SEQUENT, new-style uses ptmx */ + master = getpseudotty(&slave_name, &master_name); + exp_pty_slave_name = slave_name; + return(master); +#endif /* _SEQUENT_ */ + +#if defined(HAVE_OPENPTY) +#undef TEST_PTY + if (openpty(&master, &slave, master_name, 0, 0) != 0) { + close(master); + close(slave); + return -1; + } + strcpy(slave_name, ttyname(slave)); + exp_pty_slave_name = slave_name; + close(slave); + return master; +#endif /* HAVE_OPENPTY */ + +#if defined(TEST_PTY) + /* + * all pty allocation mechanisms after this require testing + */ + if (exp_pty_test_start() == -1) return -1; + +#if !defined(HAVE_CONVEX_GETPTY) && !defined(HAVE_PTYM) && !defined(HAVE_SCO_CLIST_PTYS) + for (bank = banks;*bank;bank++) { + *tty_bank = *bank; + *tty_num = '0'; + if (stat(master_name, &stat_buf) < 0) break; + for (hex = "0123456789abcdef";*hex;hex++) { + *tty_num = *hex; + strcpy(slave_name,master_name); + *tty_type = 't'; + master = exp_pty_test(master_name,slave_name,*tty_bank,tty_num); + if (master >= 0) goto done; + } + } +#endif + +#ifdef HAVE_SCO_CLIST_PTYS + for (num = 0; ; num++) { + char num_str [16]; + + sprintf (num_str, "%d", num); + sprintf (master_name, "%s%s", "/dev/ptyp", num_str); + if (stat (master_name, &stat_buf) < 0) + break; + sprintf (slave_name, "%s%s", "/dev/ttyp", num_str); + + master = exp_pty_test(master_name,slave_name,'0',num_str); + if (master >= 0) + goto done; + } +#endif + +#ifdef HAVE_PTYM + /* systems with PTYM follow this idea: + + /dev/ptym/pty[a-ce-z][0-9a-f] master pseudo terminals + /dev/pty/tty[a-ce-z][0-9a-f] slave pseudo terminals + /dev/ptym/pty[a-ce-z][0-9][0-9] master pseudo terminals + /dev/pty/tty[a-ce-z][0-9][0-9] slave pseudo terminals + + SPPUX (Convex's HPUX compatible) follows the PTYM convention but + extends it: + + /dev/ptym/pty[a-ce-z][0-9][0-9][0-9] master pseudo terminals + /dev/pty/tty[a-ce-z][0-9][0-9][0-9] slave pseudo terminals + + The code does not distinguish between HPUX and SPPUX because there + is no reason to. HPUX will merely fail the extended SPPUX tests. + In fact, most SPPUX systems will fail simply because few systems + will actually have the extended ptys. However, the tests are + fast so it is no big deal. + */ + + /* + * pty[a-ce-z][0-9a-f] + */ + + for (bank = banks;*bank;bank++) { + *tty_bank = *bank; + sprintf(tty_num,"0"); + if (stat(master_name, &stat_buf) < 0) break; + *(slave_num+1) = '\0'; + for (hex = "0123456789abcdef";*hex;hex++) { + *tty_num = *hex; + *slave_bank = *tty_bank; + *slave_num = *tty_num; + master = exp_pty_test(master_name,slave_name,*tty_bank,tty_num); + if (master >= 0) goto done; + } + } + + /* + * tty[p-za-ce-o][0-9][0-9] + */ + + for (bank = banks;*bank;bank++) { + *tty_bank = *bank; + sprintf(tty_num,"00"); + if (stat(master_name, &stat_buf) < 0) break; + for (num = 0; num<100; num++) { + *slave_bank = *tty_bank; + sprintf(tty_num,"%02d",num); + strcpy(slave_num,tty_num); + master = exp_pty_test(master_name,slave_name,*tty_bank,tty_num); + if (master >= 0) goto done; + } + } + + /* + * tty[p-za-ce-o][0-9][0-9][0-9] + */ + for (bank = banks;*bank;bank++) { + *tty_bank = *bank; + sprintf(tty_num,"000"); + if (stat(master_name, &stat_buf) < 0) break; + for (num = 0; num<1000; num++) { + *slave_bank = *tty_bank; + sprintf(tty_num,"%03d",num); + strcpy(slave_num,tty_num); + master = exp_pty_test(master_name,slave_name,*tty_bank,tty_num); + if (master >= 0) goto done; + } + } + +#endif /* HAVE_PTYM */ + +#if defined(HAVE_CONVEX_GETPTY) + for (;;) { + if ((master_name = getpty()) == NULL) return -1; + + strcpy(slave_name,master_name); + slave_name[5] = 't';/* /dev/ptyXY ==> /dev/ttyXY */ + + tty_bank = &slave_name[8]; + tty_num = &slave_name[9]; + master = exp_pty_test(master_name,slave_name,*tty_bank,tty_num); + if (master >= 0) goto done; + } +#endif + + done: + exp_pty_test_end(); + exp_pty_slave_name = slave_name; + return(master); + +#endif /* defined(TEST_PTY) */ +} + +/* if slave is opened in a child, slave_control(1) must be executed after */ +/* master is opened (when child is opened is irrelevent) */ +/* if slave is opened in same proc as master, slave_control(1) must executed */ +/* after slave is opened */ +/*ARGSUSED*/ +void +exp_slave_control(master,control) +int master; +int control; /* if 1, enable pty trapping of close/open/ioctl */ +{ +#ifdef HAVE_PTYTRAP + ioctl(master, TIOCTRAP, &control); +#endif /* HAVE_PTYTRAP */ +} + +int +exp_getptyslave( + int ttycopy, + int ttyinit, + CONST char *stty_args) +{ + int slave, slave2; + char buf[10240]; + + if (0 > (slave = open(slave_name, O_RDWR))) { + static char buf[500]; + exp_pty_error = buf; + sprintf(exp_pty_error,"open(%s,rw) = %d (%s)",slave_name,slave,expErrnoMsg(errno)); + return(-1); + } + +#if defined(HAVE_PTMX_BSD) + if (ioctl (slave, I_LOOK, buf) != 0) + if (ioctl (slave, I_PUSH, "ldterm")) { + expDiagLogPtrStrStr("ioctl(%d,I_PUSH,\"ldterm\") = %s\n",slave,expErrnoMsg(errno)); + } +#else +#if defined(HAVE_PTMX) + if (ioctl(slave, I_PUSH, "ptem")) { + expDiagLogPtrStrStr("ioctl(%d,I_PUSH,\"ptem\") = %s\n",slave,expErrnoMsg(errno)); + } + if (ioctl(slave, I_PUSH, "ldterm")) { + expDiagLogPtrStrStr("ioctl(%d,I_PUSH,\"ldterm\") = %s\n",slave,expErrnoMsg(errno)); + } + if (ioctl(slave, I_PUSH, "ttcompat")) { + expDiagLogPtrStrStr("ioctl(%d,I_PUSH,\"ttcompat\") = %s\n",slave,expErrnoMsg(errno)); + } +#endif +#endif + + if (0 == slave) { + /* if opened in a new process, slave will be 0 (and */ + /* ultimately, 1 and 2 as well) */ + + /* duplicate 0 onto 1 and 2 to prepare for stty */ + fcntl(0,F_DUPFD,1); + fcntl(0,F_DUPFD,2); + } + + ttytype(SET_TTYTYPE,slave,ttycopy,ttyinit,stty_args); + +#if 0 +#ifdef HAVE_PTYTRAP + /* do another open, to tell master that slave is done fiddling */ + /* with pty and master does not have to wait to do further acks */ + if (0 > (slave2 = open(slave_name, O_RDWR))) return(-1); + close(slave2); +#endif /* HAVE_PTYTRAP */ +#endif + + (void) exp_pty_unlock(); + return(slave); +} + +#ifdef HAVE_PTYTRAP +#include +#include + +/* This function attempts to deal with HP's pty interface. This +function simply returns an indication of what was trapped (or -1 for +failure), the parent deals with the details. + +Originally, I tried to just trap open's but that is not enough. When +the pty is initialized, ioctl's are generated and if not trapped will +hang the child if no further trapping is done. (This could occur if +parent spawns a process and then immediatley does a close.) So +instead, the parent must trap the ioctl's. It probably suffices to +trap the write ioctl's (and tiocsctty which some hp's need) - +conceivably, stty could be smart enough not to do write's if the tty +settings are already correct. In that case, we'll have to rethink +this. + +Suggestions from HP engineers encouraged. I cannot imagine how this +interface was intended to be used! + +*/ + +int +exp_wait_for_slave_open(fd) +int fd; +{ + fd_set excep; + struct timeval t; + struct request_info ioctl_info; + int rc; + int found = 0; + + int maxfds = sysconf(_SC_OPEN_MAX); + + t.tv_sec = 30; /* 30 seconds */ + t.tv_usec = 0; + + FD_ZERO(&excep); + FD_SET(fd,&excep); + + rc = select(maxfds, + (SELECT_MASK_TYPE *)0, + (SELECT_MASK_TYPE *)0, + (SELECT_MASK_TYPE *)&excep, + &t); + if (rc != 1) { + expDiagLogPtrStr("spawned process never started: %s\r\n",expErrnoMsg(errno)); + return(-1); + } + if (ioctl(fd,TIOCREQCHECK,&ioctl_info) < 0) { + expDiagLogPtrStr("ioctl(TIOCREQCHECK) failed: %s\r\n",expErrnoMsg(errno)); + return(-1); + } + + found = ioctl_info.request; + + expDiagLogPtrX("trapped pty op = %x",found); + if (found == TIOCOPEN) { + expDiagLogPtr(" TIOCOPEN"); + } else if (found == TIOCCLOSE) { + expDiagLogPtr(" TIOCCLOSE"); + } + +#ifdef TIOCSCTTY + if (found == TIOCSCTTY) { + expDiagLogPtr(" TIOCSCTTY"); + } +#endif + + if (found & IOC_IN) { + expDiagLogPtr(" IOC_IN (set)"); + } else if (found & IOC_OUT) { + expDiagLogPtr(" IOC_OUT (get)"); + } + + expDiagLogPtr("\n"); + + if (ioctl(fd, TIOCREQSET, &ioctl_info) < 0) { + expDiagLogPtrStr("ioctl(TIOCREQSET) failed: %s\r\n",expErrnoMsg(errno)); + return(-1); + } + return(found); +} +#endif + +void +exp_pty_exit() +{ + /* a stub so we can do weird things on the cray */ +}