/* 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"); }