/* Written by Matt Newman and Jean-Claude Wippler, as part of Tclkit.
* March 2003 - placed in the public domain by the authors.
*
* Reflecting channel interface
*/
#include <tcl.h>
#ifndef TCL_DECLARE_MUTEX
#define TCL_DECLARE_MUTEX(v)
#define Tcl_MutexLock(v)
#define Tcl_MutexUnlock(v)
#endif
static int mkChanSeq = 0;
TCL_DECLARE_MUTEX(rechanMutex)
/* Uncomment for Linux or other non-Solaris OS's for memcpy declaration */
#include <memory.h>
/* Uncomment for Solaris (and comment above) for memcpy declaration */
/* #include <string.h> */
#ifndef EINVAL
#define EINVAL 9
#endif
typedef struct
{
Tcl_Channel _chan;
int _validMask;
int _watchMask;
Tcl_Interp* _interp;
Tcl_Obj* _context;
Tcl_Obj* _seek;
Tcl_Obj* _read;
Tcl_Obj* _write;
Tcl_Obj* _name;
Tcl_TimerToken _timer;
} ReflectingChannel;
static ReflectingChannel*
rcCreate (Tcl_Interp* ip_, Tcl_Obj* context_, int mode_, const char* name_)
{
ReflectingChannel* cp = (ReflectingChannel*) Tcl_Alloc (sizeof *cp);
cp->_validMask = mode_;
cp->_watchMask = 0;
cp->_chan = 0;
cp->_context = context_;
cp->_interp = ip_;
cp->_name = Tcl_NewStringObj(name_, -1);
cp->_timer = NULL;
/* support Tcl_GetIndexFromObj by keeping these objectified */
cp->_seek = Tcl_NewStringObj("seek", -1);
cp->_read = Tcl_NewStringObj("read", -1);
cp->_write = Tcl_NewStringObj("write", -1);
Tcl_IncrRefCount(cp->_context);
Tcl_IncrRefCount(cp->_seek);
Tcl_IncrRefCount(cp->_read);
Tcl_IncrRefCount(cp->_write);
Tcl_IncrRefCount(cp->_name);
return cp;
}
static Tcl_Obj*
rcBuildCmdList(ReflectingChannel* chan_, Tcl_Obj* cmd_)
{
Tcl_Obj* vec = Tcl_DuplicateObj(chan_->_context);
Tcl_IncrRefCount(vec);
Tcl_ListObjAppendElement(chan_->_interp, vec, cmd_);
Tcl_ListObjAppendElement(chan_->_interp, vec, chan_->_name);
return vec; /* with refcount 1 */
}
static int
rcClose (ClientData cd_, Tcl_Interp* interp)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
int n = -1;
Tcl_SavedResult sr;
Tcl_Obj* cmd = rcBuildCmdList(chan, Tcl_NewStringObj("close", -1));
Tcl_Interp* ip = chan->_interp;
Tcl_SaveResult(ip, &sr);
if (Tcl_EvalObjEx(ip, cmd, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT) == TCL_OK)
Tcl_GetIntFromObj(NULL, Tcl_GetObjResult(ip), &n);
Tcl_RestoreResult(ip, &sr);
Tcl_DecrRefCount(cmd);
if (chan->_timer != NULL) {
Tcl_DeleteTimerHandler(chan->_timer);
chan->_timer = NULL;
}
Tcl_DecrRefCount(chan->_context);
Tcl_DecrRefCount(chan->_seek);
Tcl_DecrRefCount(chan->_read);
Tcl_DecrRefCount(chan->_write);
Tcl_DecrRefCount(chan->_name);
Tcl_Free((char*) chan);
return TCL_OK;
}
static int
rcInput (ClientData cd_, char* buf, int toRead, int* errorCodePtr)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
int n = -1;
if (chan->_validMask & TCL_READABLE) {
Tcl_SavedResult sr;
Tcl_Obj* cmd = rcBuildCmdList(chan, chan->_read);
Tcl_Interp* ip = chan->_interp;
Tcl_ListObjAppendElement(NULL, cmd, Tcl_NewIntObj(toRead));
Tcl_SaveResult(ip, &sr);
if (Tcl_EvalObjEx(ip, cmd, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT) == TCL_OK) {
void* s = Tcl_GetByteArrayFromObj(Tcl_GetObjResult(ip), &n);
if (0 <= n && n <= toRead)
if (n > 0)
memcpy(buf, s, n);
else
chan->_watchMask &= ~TCL_READABLE;
else
n = -1;
}
Tcl_RestoreResult(ip, &sr);
Tcl_DecrRefCount(cmd);
}
if (n < 0)
*errorCodePtr = EINVAL;
return n;
}
static int
rcOutput (ClientData cd_, const char* buf, int toWrite, int* errorCodePtr)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
int n = -1;
if (chan->_validMask & TCL_WRITABLE) {
Tcl_SavedResult sr;
Tcl_Obj* cmd = rcBuildCmdList(chan, chan->_write);
Tcl_Interp* ip = chan->_interp;
Tcl_ListObjAppendElement(NULL, cmd,
Tcl_NewByteArrayObj((unsigned char*) buf, toWrite));
Tcl_SaveResult(ip, &sr);
if (Tcl_EvalObjEx(ip, cmd, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT) == TCL_OK &&
Tcl_GetIntFromObj(NULL, Tcl_GetObjResult(ip), &n) == TCL_OK)
if (0 <= n && n <= toWrite)
chan->_watchMask = chan->_validMask;
else
n = -1;
Tcl_RestoreResult(ip, &sr);
Tcl_DecrRefCount(cmd);
}
if (n < 0)
*errorCodePtr = EINVAL;
return n;
}
static int
rcSeek (ClientData cd_, long offset, int seekMode, int* errorCodePtr)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
int n = -1;
Tcl_SavedResult sr;
Tcl_Obj* cmd = rcBuildCmdList(chan, chan->_seek);
Tcl_Interp* ip = chan->_interp;
Tcl_ListObjAppendElement(NULL, cmd, Tcl_NewLongObj(offset));
Tcl_ListObjAppendElement(NULL, cmd, Tcl_NewIntObj(seekMode));
Tcl_SaveResult(ip, &sr);
if (Tcl_EvalObjEx(ip, cmd, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT) == TCL_OK &&
Tcl_GetIntFromObj(NULL, Tcl_GetObjResult(ip), &n) == TCL_OK)
chan->_watchMask = chan->_validMask;
Tcl_RestoreResult(ip, &sr);
Tcl_DecrRefCount(cmd);
if (n < 0)
*errorCodePtr = EINVAL;
return n;
}
static void
rcTimerProc (ClientData cd_)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
if (chan->_timer != NULL)
Tcl_DeleteTimerHandler(chan->_timer);
chan->_timer = NULL;
Tcl_NotifyChannel(chan->_chan, chan->_watchMask);
}
static void
rcWatchChannel (ClientData cd_, int mask)
{
ReflectingChannel* chan = (ReflectingChannel*) cd_;
/* Dec 2001: adopting logic used in Andreas Kupries' memchan, i.e. timers */
if (mask) {
chan->_watchMask = mask & chan->_validMask;
if (chan->_watchMask && chan->_timer == NULL)
chan->_timer = Tcl_CreateTimerHandler(5, rcTimerProc, cd_);
} else if (chan->_timer != NULL) {
Tcl_DeleteTimerHandler(chan->_timer);
chan->_timer = NULL;
}
}
static int
rcGetFile (ClientData cd_, int direction, ClientData* handlePtr)
{
return TCL_ERROR;
}
static int
rcBlock (ClientData cd_, int mode)
{
return 0;
}
static Tcl_ChannelType reChannelType = {
"rechan", /* Type name. */
(Tcl_ChannelTypeVersion) rcBlock, /* Set blocking/nonblocking behaviour */
rcClose, /* Close channel, clean instance data */
rcInput, /* Handle read request */
rcOutput, /* Handle write request */
rcSeek, /* Move location of access point. NULL'able */
0, /* Set options. NULL'able */
0, /* Get options. NULL'able */
rcWatchChannel, /* Initialize notifier */
rcGetFile /* Get OS handle from the channel. */
};
static int
cmd_rechan(ClientData cd_, Tcl_Interp* ip_, int objc_, Tcl_Obj*const* objv_)
{
ReflectingChannel *rc;
int mode;
char buffer [20];
if (objc_ != 3) {
Tcl_WrongNumArgs(ip_, 1, objv_, "command mode");
return TCL_ERROR;
}
if (Tcl_ListObjLength(ip_, objv_[1], &mode) == TCL_ERROR ||
Tcl_GetIntFromObj(ip_, objv_[2], &mode) == TCL_ERROR)
return TCL_ERROR;
Tcl_MutexLock(&rechanMutex);
sprintf(buffer, "rechan%d", ++mkChanSeq);
Tcl_MutexUnlock(&rechanMutex);
rc = rcCreate (ip_, objv_[1], mode, buffer);
rc->_chan = Tcl_CreateChannel(&reChannelType, buffer, (ClientData) rc, mode);
Tcl_RegisterChannel(ip_, rc->_chan);
Tcl_SetChannelOption(ip_, rc->_chan, "-buffering", "none");
Tcl_SetChannelOption(ip_, rc->_chan, "-blocking", "0");
Tcl_SetResult(ip_, buffer, TCL_VOLATILE);
return TCL_OK;
}
DLLEXPORT int Rechan_Init(Tcl_Interp* interp)
{
if (!Tcl_InitStubs(interp, "8.4", 0))
return TCL_ERROR;
Tcl_CreateObjCommand(interp, "rechan", cmd_rechan, 0, 0);
return Tcl_PkgProvide(interp, "rechan", "1.0");
}