#include "expect_cf.h"
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_PTYTRAP
# include <sys/ptyio.h>
#endif
#include "tcl.h"
#include "exp_prog.h"
#include "exp_command.h"
#include "exp_event.h"
static int ready_fd = EXP_SPAWN_ID_BAD;
static int ready_mask;
static int default_mask = TCL_READABLE | TCL_EXCEPTION;
void
exp_event_disarm(fd)
int fd;
{
#if TCL_MAJOR_VERSION < 8
Tcl_DeleteFileHandler(exp_fs[fd].Master);
#else
Tcl_DeleteFileHandler(fd);
#endif
exp_fs[fd].fg_armed = FALSE;
}
void
exp_event_disarm_fast(fd,filehandler)
int fd;
Tcl_FileProc *filehandler;
{
#if TCL_MAJOR_VERSION < 8
Tcl_CreateFileHandler(exp_fs[fd].Master,0,filehandler,(ClientData)0);
#else
Tcl_CreateFileHandler(fd,0,filehandler,(ClientData)0);
#endif
exp_fs[fd].fg_armed = FALSE;
}
static void
exp_arm_background_filehandler_force(m)
int m;
{
#if TCL_MAJOR_VERSION < 8
Tcl_CreateFileHandler(exp_fs[m].Master,
#else
Tcl_CreateFileHandler(m,
#endif
TCL_READABLE|TCL_EXCEPTION,
exp_background_filehandler,
(ClientData)(exp_fs[m].fd_ptr));
exp_fs[m].bg_status = armed;
}
void
exp_arm_background_filehandler(m)
int m;
{
switch (exp_fs[m].bg_status) {
case unarmed:
exp_arm_background_filehandler_force(m);
break;
case disarm_req_while_blocked:
exp_fs[m].bg_status = blocked;
break;
case armed:
case blocked:
break;
}
}
void
exp_disarm_background_filehandler(m)
int m;
{
switch (exp_fs[m].bg_status) {
case blocked:
exp_fs[m].bg_status = disarm_req_while_blocked;
break;
case armed:
exp_fs[m].bg_status = unarmed;
exp_event_disarm(m);
break;
case disarm_req_while_blocked:
case unarmed:
break;
}
}
void
exp_disarm_background_filehandler_force(m)
int m;
{
switch (exp_fs[m].bg_status) {
case blocked:
case disarm_req_while_blocked:
case armed:
exp_fs[m].bg_status = unarmed;
exp_event_disarm(m);
break;
case unarmed:
break;
}
}
void
exp_unblock_background_filehandler(m)
int m;
{
switch (exp_fs[m].bg_status) {
case blocked:
exp_arm_background_filehandler_force(m);
break;
case disarm_req_while_blocked:
exp_disarm_background_filehandler_force(m);
break;
case armed:
case unarmed:
break;
}
}
void
exp_block_background_filehandler(m)
int m;
{
exp_fs[m].bg_status = blocked;
exp_event_disarm_fast(m,exp_background_filehandler);
}
static void
exp_timehandler(clientData)
ClientData clientData;
{
*(int *)clientData = TRUE;
}
static void exp_filehandler(clientData,mask)
ClientData clientData;
int mask;
{
ready_fd = *(int *)clientData;
ready_mask = mask;
exp_event_disarm_fast(ready_fd,exp_filehandler);
#if 0
if (ready_fd == *(int *)clientData) {
exp_event_disarm_fast(ready_fd,exp_filehandler);
} else {
ready_fd = *(int *)clientData;
ready_mask = mask;
}
#endif
}
int exp_get_next_event(interp,masters, n,master_out,timeout,key)
Tcl_Interp *interp;
int *masters;
int n;
int *master_out;
int timeout;
int key;
{
static rr = 0;
int i;
#ifdef HAVE_PTYTRAP
struct request_info ioctl_info;
#endif
int old_configure_count = exp_configure_count;
int timer_created = FALSE;
int timer_fired = FALSE;
Tcl_TimerToken timetoken;
for (;;) {
int m;
struct exp_f *f;
for (i=0;i<n;i++) {
rr++;
if (rr >= n) rr = 0;
m = masters[rr];
f = exp_fs + m;
if (f->key != key) {
f->key = key;
f->force_read = FALSE;
*master_out = m;
return(EXP_DATA_OLD);
} else if ((!f->force_read) && (f->size != 0)) {
*master_out = m;
return(EXP_DATA_OLD);
}
}
if (!timer_created) {
if (timeout >= 0) {
timetoken = Tcl_CreateTimerHandler(1000*timeout,
exp_timehandler,
(ClientData)&timer_fired);
timer_created = TRUE;
}
}
for (;;) {
int j;
for (j=0;j<n;j++) {
int k = masters[j];
if (!exp_fs[k].fg_armed) {
Tcl_CreateFileHandler(
#if TCL_MAJOR_VERSION < 8
exp_fs[k].Master,
#else
k,
#endif
default_mask,
exp_filehandler,
(ClientData)exp_fs[k].fd_ptr);
exp_fs[k].fg_armed = TRUE;
}
}
Tcl_DoOneEvent(0);
if (timer_fired) return(EXP_TIMEOUT);
if (old_configure_count != exp_configure_count) {
if (timer_created) Tcl_DeleteTimerHandler(timetoken);
return EXP_RECONFIGURE;
}
if (ready_fd == EXP_SPAWN_ID_BAD) continue;
for (j=0;j<n;j++) {
if (ready_fd == masters[j]) goto found;
}
exp_event_disarm_fast(ready_fd,exp_filehandler);
ready_fd = EXP_SPAWN_ID_BAD;
continue;
found:
*master_out = ready_fd;
ready_fd = EXP_SPAWN_ID_BAD;
if (ready_mask & TCL_READABLE) {
if (timer_created) Tcl_DeleteTimerHandler(timetoken);
return EXP_DATA_NEW;
}
#ifndef HAVE_PTYTRAP
if (timer_created) Tcl_DeleteTimerHandler(timetoken);
return(EXP_EOF);
#else
if (ioctl(*master_out,TIOCREQCHECK,&ioctl_info) < 0) {
if (timer_created)
Tcl_DeleteTimerHandler(timetoken);
exp_debuglog("ioctl error on TIOCREQCHECK: %s", Tcl_PosixError(interp));
return(EXP_TCLERROR);
}
if (ioctl_info.request == TIOCCLOSE) {
if (timer_created)
Tcl_DeleteTimerHandler(timetoken);
return(EXP_EOF);
}
if (ioctl(*master_out, TIOCREQSET, &ioctl_info) < 0) {
exp_debuglog("ioctl error on TIOCREQSET after ioctl or open on slave: %s", Tcl_ErrnoMsg(errno));
}
continue;
#endif
}
}
}
int
exp_get_next_event_info(interp,fd,ready_mask)
Tcl_Interp *interp;
int fd;
int ready_mask;
{
#ifdef HAVE_PTYTRAP
struct request_info ioctl_info;
#endif
if (ready_mask & TCL_READABLE) return EXP_DATA_NEW;
#ifndef HAVE_PTYTRAP
return(EXP_EOF);
#else
if (ioctl(fd,TIOCREQCHECK,&ioctl_info) < 0) {
exp_debuglog("ioctl error on TIOCREQCHECK: %s",
Tcl_PosixError(interp));
return(EXP_TCLERROR);
}
if (ioctl_info.request == TIOCCLOSE) {
return(EXP_EOF);
}
if (ioctl(fd, TIOCREQSET, &ioctl_info) < 0) {
exp_debuglog("ioctl error on TIOCREQSET after ioctl or open on slave: %s", Tcl_ErrnoMsg(errno));
}
return EXP_TCLERROR;
#endif
}
int
exp_dsleep(interp,sec)
Tcl_Interp *interp;
double sec;
{
int timer_fired = FALSE;
Tcl_CreateTimerHandler((int)(sec*1000),exp_timehandler,(ClientData)&timer_fired);
while (1) {
Tcl_DoOneEvent(0);
if (timer_fired) return TCL_OK;
if (ready_fd == EXP_SPAWN_ID_BAD) continue;
exp_event_disarm_fast(ready_fd,exp_filehandler);
ready_fd = EXP_SPAWN_ID_BAD;
}
}
#if 0
int
exp_usleep(interp,usec)
Tcl_Interp *interp;
long usec;
{
int timer_fired = FALSE;
Tcl_CreateTimerHandler(usec/1000,exp_timehandler,(ClientData)&timer_fired);
while (1) {
Tcl_DoOneEvent(0);
if (timer_fired) return TCL_OK;
if (ready_fd == EXP_SPAWN_ID_BAD) continue;
exp_event_disarm_fast(ready_fd,exp_filehandler);
ready_fd = EXP_SPAWN_ID_BAD;
}
}
#endif
static char destroy_cmd[] = "destroy .";
static void
exp_event_exit_real(interp)
Tcl_Interp *interp;
{
Tcl_Eval(interp,destroy_cmd);
}
void
exp_init_event()
{
exp_event_exit = exp_event_exit_real;
}
#ifdef __CYGWIN32__
#ifndef CYGWIN_ALTTCL
#include <windows.h>
#include <sys/socket.h>
#include <tclInt.h>
#if TCL_MAJOR_VERSION < 7
# error not implemented
#endif
static void pipe_setup _ANSI_ARGS_((ClientData, int));
static void pipe_check _ANSI_ARGS_((ClientData, int));
static void pipe_exit _ANSI_ARGS_((ClientData));
static int pipe_close _ANSI_ARGS_((ClientData, Tcl_Interp *));
static int pipe_input _ANSI_ARGS_((ClientData, char *, int, int *));
static int pipe_output _ANSI_ARGS_((ClientData, char *, int, int *));
static void pipe_watch _ANSI_ARGS_((ClientData, int));
static int pipe_get_handle _ANSI_ARGS_((ClientData, int, ClientData *));
static int pipe_event _ANSI_ARGS_((Tcl_Event *, int));
static Tcl_ChannelType pipe_channel = {
"expect_pipe",
NULL,
pipe_close,
pipe_input,
pipe_output,
NULL,
NULL,
NULL,
pipe_watch,
pipe_get_handle
};
struct pipe_info {
struct pipe_info *next;
Tcl_Channel channel;
int fd;
int watch_mask;
int flags;
HANDLE flags_mutex;
HANDLE mutex;
HANDLE try_read;
HANDLE pthread;
};
#define PIPE_PENDING (1 << 0)
#define PIPE_READABLE (1 << 1)
#define PIPE_CLOSED (1 << 2)
#define PIPE_HAS_THREAD (1 << 3)
struct pipe_event {
Tcl_Event header;
struct pipe_info *pipe;
};
static int pipe_initialized;
static struct pipe_info *pipes;
static HWND pipe_window;
#define PIPE_MESSAGE (WM_USER + 1)
static int
pipe_get_flags (pipe)
struct pipe_info *pipe;
{
int flags;
WaitForSingleObject (pipe->flags_mutex, INFINITE);
flags = pipe->flags;
ReleaseMutex (pipe->flags_mutex);
return flags;
}
static void
pipe_set_flag (pipe, flag)
struct pipe_info *pipe;
int flag;
{
WaitForSingleObject (pipe->flags_mutex, INFINITE);
pipe->flags |= flag;
ReleaseMutex (pipe->flags_mutex);
}
static void
pipe_reset_flag (pipe, flag)
struct pipe_info *pipe;
int flag;
{
WaitForSingleObject (pipe->flags_mutex, INFINITE);
pipe->flags &= ~ flag;
ReleaseMutex (pipe->flags_mutex);
}
static DWORD
pipe_thread (arg)
LPVOID arg;
{
struct pipe_info *pipe = (struct pipe_info *) arg;
HANDLE handle = get_osfhandle(pipe->fd);
struct timeval timeout;
while (1) {
int n, tba;
fd_set r, x;
if (WaitForSingleObject (pipe->try_read, 10000) == WAIT_TIMEOUT)
{
n = PeekNamedPipe(handle, NULL, 0, NULL, &tba, NULL);
if (n == 0)
break;
}
if (pipe_get_flags (pipe) & PIPE_CLOSED) {
break;
}
timeout.tv_sec = 10;
timeout.tv_usec = 0;
FD_ZERO (&r);
FD_SET (pipe->fd, &r);
FD_ZERO (&x);
FD_SET (pipe->fd, &x);
if ((n = select (pipe->fd + 1, &r, NULL, &x, &timeout)) <= 0 ||
FD_ISSET(pipe->fd, &x))
;
pipe_set_flag (pipe, PIPE_READABLE);
if (pipe_get_flags (pipe) & PIPE_CLOSED) {
break;
}
PostMessage (pipe_window, PIPE_MESSAGE, 0, (LPARAM) pipe);
if (n < 0 || FD_ISSET (pipe->fd, &x))
break;
}
CloseHandle (pipe->flags_mutex); pipe->flags_mutex = NULL;
CloseHandle (pipe->try_read); pipe->try_read = NULL;
pipe_reset_flag (pipe, PIPE_HAS_THREAD);
return 0;
}
static LRESULT CALLBACK
pipe_proc (hwnd, message, wParam, lParam)
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
{
if (message != PIPE_MESSAGE) {
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
static void
pipe_init ()
{
WNDCLASS class;
pipe_initialized = 1;
Tcl_CreateEventSource (pipe_setup, pipe_check, NULL);
Tcl_CreateExitHandler (pipe_exit, NULL);
memset (&class, 0, sizeof class);
class.hInstance = GetModuleHandle (NULL);
class.lpszClassName = "expect_pipe";
class.lpfnWndProc = pipe_proc;
if (! RegisterClass (&class)) {
exp_errorlog ("RegisterClass failed: %d\n", GetLastError ());
exit (-1);
}
pipe_window = CreateWindow ("expect_pipe", "expect_pipe",
WS_TILED, 0, 0, 0, 0, NULL, NULL,
class.hInstance, NULL);
if (pipe_window == NULL) {
exp_errorlog ("CreateWindow failed: %d\n", GetLastError ());
exit (-1);
}
}
static void
pipe_exit (cd)
ClientData cd;
{
Tcl_DeleteEventSource (pipe_setup, pipe_check, NULL);
UnregisterClass ("expect_pipe", GetModuleHandle (NULL));
DestroyWindow (pipe_window);
pipe_initialized = 0;
}
static void
pipe_setup (cd, flags)
ClientData cd;
int flags;
{
struct pipe_info *p;
Tcl_Time zero_time = { 0, 0 };
if (! (flags & TCL_FILE_EVENTS)) {
return;
}
for (p = pipes; p != NULL; p = p->next) {
if ((p->watch_mask &~ TCL_READABLE)
|| ((p->watch_mask & TCL_READABLE)
&& ((pipe_get_flags (p) & PIPE_HAS_THREAD) == 0
|| (pipe_get_flags (p) & PIPE_READABLE)))) {
Tcl_SetMaxBlockTime (&zero_time);
break;
} else if (p->watch_mask & TCL_READABLE) {
SetEvent (p->try_read);
}
}
}
static void
pipe_check (cd, flags)
ClientData cd;
int flags;
{
struct pipe_info *p;
if (! (flags & TCL_FILE_EVENTS)) {
return;
}
for (p = pipes; p != NULL; p = p->next) {
if (((p->watch_mask &~ TCL_READABLE)
|| ((p->watch_mask & TCL_READABLE)
&& ((pipe_get_flags (p) & PIPE_HAS_THREAD) == 0
|| (pipe_get_flags (p) & PIPE_READABLE))))
&& ! (pipe_get_flags (p) & PIPE_PENDING)) {
struct pipe_event *ev;
pipe_set_flag (p, PIPE_PENDING);
ev = (struct pipe_event *) Tcl_Alloc (sizeof *ev);
ev->header.proc = pipe_event;
ev->pipe = p;
Tcl_QueueEvent ((Tcl_Event *) ev, TCL_QUEUE_TAIL);
}
}
}
static int
pipe_close (cd, interp)
ClientData cd;
Tcl_Interp *interp;
{
struct pipe_info *p = (struct pipe_info *) cd;
struct pipe_info **pp;
for (pp = &pipes; *pp != NULL; pp = &(*pp)->next) {
if (*pp == p) {
*pp = p->next;
break;
}
}
if (pipe_get_flags (p) & PIPE_HAS_THREAD) {
close (p->fd);
pipe_set_flag (p, PIPE_CLOSED);
(void) SetEvent (p->try_read);
WaitForSingleObject (p->pthread, 10000);
CloseHandle (p->pthread);
} else {
CloseHandle (p->flags_mutex);
}
Tcl_Free ((char *) p);
return 0;
}
static int
pipe_input (cd, buf, size, error)
ClientData cd;
char *buf;
int size;
int *error;
{
exp_errorlog ("pipe_input called");
exit (-1);
}
static int
pipe_output (cd, buf, size, error)
ClientData cd;
char *buf;
int size;
int *error;
{
exp_errorlog ("pipe_output called");
exit (-1);
}
static int
pipe_event (ev, flags)
Tcl_Event *ev;
int flags;
{
struct pipe_event *pev = (struct pipe_event *) ev;
struct pipe_info *p;
int mask;
if (! (flags & TCL_FILE_EVENTS)) {
return 0;
}
for (p = pipes; p != NULL; p = p->next) {
if (p == pev->pipe) {
pipe_reset_flag (p, PIPE_PENDING);
break;
}
}
if (p == NULL) {
return 1;
}
if (pipe_get_flags (p) & PIPE_HAS_THREAD) {
mask = TCL_WRITABLE;
if (pipe_get_flags (p) & PIPE_READABLE) {
mask |= TCL_READABLE;
}
} else {
mask = TCL_WRITABLE | TCL_READABLE;
}
Tcl_NotifyChannel (p->channel, p->watch_mask & mask);
return 1;
}
static void
pipe_watch (cd, mask)
ClientData cd;
int mask;
{
struct pipe_info *p = (struct pipe_info *) cd;
int old_mask;
old_mask = p->watch_mask;
p->watch_mask = mask & (TCL_READABLE | TCL_WRITABLE);
if (p->watch_mask != 0) {
Tcl_Time zero_time = { 0, 0 };
if ((p->watch_mask & TCL_READABLE) != 0
&& (pipe_get_flags (p) & PIPE_HAS_THREAD) == 0) {
HANDLE thread;
DWORD tid;
p->try_read = CreateEvent (NULL, FALSE, FALSE,
NULL);
pipe_set_flag (p, PIPE_HAS_THREAD);
p->pthread = CreateThread (NULL, 0, pipe_thread,
p, 0, &tid);
if (!p->pthread)
{
fprintf(stderr, "Error: cannot create pipe thread, error=%d\n", GetLastError());
exit(1);
}
CloseHandle(p->pthread);
}
if (old_mask == 0) {
p->next = pipes;
pipes = p;
}
Tcl_SetMaxBlockTime (&zero_time);
} else {
if (old_mask != 0) {
struct pipe_info **pp;
for (pp = &pipes; *pp != NULL; pp = &(*pp)->next) {
if (*pp == p) {
*pp = p->next;
break;
}
}
}
}
}
static int
pipe_get_handle (cd, dir, handle_ptr)
ClientData cd;
int dir;
ClientData *handle_ptr;
{
struct pipe_info *p = (struct pipe_info *) cd;
*handle_ptr = (ClientData *)p->fd;
return TCL_OK;
}
static Tcl_Channel
make_pipe_channel (fd, handle)
int fd;
HANDLE handle;
{
Tcl_Channel chan;
struct pipe_info *p;
char namebuf[30];
if (! pipe_initialized) {
pipe_init ();
}
p = (struct pipe_info *) Tcl_Alloc (sizeof *p);
p->next = NULL;
p->fd = fd;
p->watch_mask = 0;
p->flags = 0;
p->flags_mutex = CreateMutex (NULL, FALSE, NULL);
p->try_read = NULL;
sprintf (namebuf, "expect_pipe%d", handle);
p->channel = Tcl_CreateChannel (&pipe_channel, namebuf,
(ClientData) p,
TCL_READABLE | TCL_WRITABLE);
Tcl_SetChannelOption ((Tcl_Interp *) NULL, p->channel,
"-translation", "binary");
return p->channel;
}
void
exp_reading_from_descriptor (fd)
int fd;
{
struct pipe_info *p;
for (p = pipes; p != NULL; p = p->next) {
if (p->fd == fd) {
pipe_reset_flag (p, PIPE_READABLE);
break;
}
}
}
void
Tcl_CreateFileHandler (fd, mask, proc, cd)
int fd;
int mask;
Tcl_FileProc *proc;
ClientData cd;
{
if (exp_fs[fd].channel == NULL) {
HANDLE handle;
Tcl_Channel chan;
struct sockaddr sa;
int salen;
handle = (HANDLE) get_osfhandle (fd);
if (handle == (HANDLE) -1)
abort ();
chan = NULL;
salen = sizeof sa;
if (getsockname (fd, &sa, &salen) == 0)
chan = Tcl_MakeTcpClientChannel ((ClientData) handle);
else if (GetFileType (handle) != FILE_TYPE_PIPE)
chan = Tcl_MakeFileChannel ((ClientData) fd,
(TCL_READABLE
| TCL_WRITABLE));
else {
if ((mask & TCL_READABLE) == 0)
chan = Tcl_MakeFileChannel ((ClientData) fd,
mask);
else
chan = make_pipe_channel (fd, handle);
}
if (chan == NULL)
abort ();
exp_fs[fd].channel = chan;
}
if (exp_fs[fd].fileproc != NULL)
Tcl_DeleteChannelHandler (exp_fs[fd].channel,
exp_fs[fd].fileproc,
exp_fs[fd].procdata);
Tcl_CreateChannelHandler (exp_fs[fd].channel, mask, proc, cd);
exp_fs[fd].fileproc = proc;
exp_fs[fd].procdata = cd;
}
void
Tcl_DeleteFileHandler (fd)
int fd;
{
if (exp_fs[fd].channel != NULL && exp_fs[fd].fileproc != NULL) {
Tcl_DeleteChannelHandler (exp_fs[fd].channel,
exp_fs[fd].fileproc,
exp_fs[fd].procdata);
exp_fs[fd].fileproc = NULL;
}
}
#endif
#endif