/*
* 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;
}
syntax highlighted by Code2HTML, v. 0.9.1