summaryrefslogtreecommitdiff
path: root/exp_chan.c
diff options
context:
space:
mode:
Diffstat (limited to 'exp_chan.c')
-rw-r--r--exp_chan.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/exp_chan.c b/exp_chan.c
new file mode 100644
index 0000000..963337d
--- /dev/null
+++ b/exp_chan.c
@@ -0,0 +1,708 @@
+/*
+ * exp_chan.c
+ *
+ * Channel driver for Expect channels.
+ * Based on UNIX File channel from TclUnixChan.c
+ *
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <ctype.h> /* for isspace */
+#include <time.h> /* for time(3) */
+
+#include "expect_cf.h"
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#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);*/
+ 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;
+}
+#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;
+
+ /* kick off any that already have input waiting */
+ for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) {
+ /* is bg_interp the best way to check if armed? */
+ if (esPtr->bg_interp && !expSizeZero(esPtr)) {
+ exp_background_channelhandler((ClientData)esPtr,0);
+ }
+ }
+}
+
+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;
+}