sl@0: /* sl@0: * tclIOGT.c -- sl@0: * sl@0: * Implements a generic transformation exposing the underlying API sl@0: * at the script level. Contributed by Andreas Kupries. sl@0: * sl@0: * Copyright (c) 2000 Ajuba Solutions sl@0: * Copyright (c) 1999-2000 Andreas Kupries (a.kupries@westend.com) sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * CVS: $Id: tclIOGT.c,v 1.7.2.2 2006/08/30 17:24:07 hobbs Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #include "tclIO.h" sl@0: sl@0: sl@0: /* sl@0: * Forward declarations of internal procedures. sl@0: * First the driver procedures of the transformation. sl@0: */ sl@0: sl@0: static int TransformBlockModeProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int mode)); sl@0: static int TransformCloseProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, Tcl_Interp* interp)); sl@0: static int TransformInputProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, sl@0: char* buf, int toRead, int* errorCodePtr)); sl@0: static int TransformOutputProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, CONST char *buf, sl@0: int toWrite, int* errorCodePtr)); sl@0: static int TransformSeekProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, long offset, sl@0: int mode, int* errorCodePtr)); sl@0: static int TransformSetOptionProc _ANSI_ARGS_(( sl@0: ClientData instanceData, Tcl_Interp *interp, sl@0: CONST char *optionName, CONST char *value)); sl@0: static int TransformGetOptionProc _ANSI_ARGS_(( sl@0: ClientData instanceData, Tcl_Interp *interp, sl@0: CONST char *optionName, Tcl_DString *dsPtr)); sl@0: static void TransformWatchProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int mask)); sl@0: static int TransformGetFileHandleProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int direction, sl@0: ClientData* handlePtr)); sl@0: static int TransformNotifyProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int mask)); sl@0: static Tcl_WideInt TransformWideSeekProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, Tcl_WideInt offset, sl@0: int mode, int* errorCodePtr)); sl@0: sl@0: /* sl@0: * Forward declarations of internal procedures. sl@0: * Secondly the procedures for handling and generating fileeevents. sl@0: */ sl@0: sl@0: static void TransformChannelHandlerTimer _ANSI_ARGS_ (( sl@0: ClientData clientData)); sl@0: sl@0: /* sl@0: * Forward declarations of internal procedures. sl@0: * Third, helper procedures encapsulating essential tasks. sl@0: */ sl@0: sl@0: typedef struct TransformChannelData TransformChannelData; sl@0: sl@0: static int ExecuteCallback _ANSI_ARGS_ (( sl@0: TransformChannelData* ctrl, Tcl_Interp* interp, sl@0: unsigned char* op, unsigned char* buf, sl@0: int bufLen, int transmit, int preserve)); sl@0: sl@0: /* sl@0: * Action codes to give to 'ExecuteCallback' (argument 'transmit') sl@0: * confering to the procedure what to do with the result of the script sl@0: * it calls. sl@0: */ sl@0: sl@0: #define TRANSMIT_DONT (0) /* No transfer to do */ sl@0: #define TRANSMIT_DOWN (1) /* Transfer to the underlying channel */ sl@0: #define TRANSMIT_SELF (2) /* Transfer into our channel. */ sl@0: #define TRANSMIT_IBUF (3) /* Transfer to internal input buffer */ sl@0: #define TRANSMIT_NUM (4) /* Transfer number to 'maxRead' */ sl@0: sl@0: /* sl@0: * Codes for 'preserve' of 'ExecuteCallback' sl@0: */ sl@0: sl@0: #define P_PRESERVE (1) sl@0: #define P_NO_PRESERVE (0) sl@0: sl@0: /* sl@0: * Strings for the action codes delivered to the script implementing sl@0: * a transformation. Argument 'op' of 'ExecuteCallback'. sl@0: */ sl@0: sl@0: #define A_CREATE_WRITE (UCHARP ("create/write")) sl@0: #define A_DELETE_WRITE (UCHARP ("delete/write")) sl@0: #define A_FLUSH_WRITE (UCHARP ("flush/write")) sl@0: #define A_WRITE (UCHARP ("write")) sl@0: sl@0: #define A_CREATE_READ (UCHARP ("create/read")) sl@0: #define A_DELETE_READ (UCHARP ("delete/read")) sl@0: #define A_FLUSH_READ (UCHARP ("flush/read")) sl@0: #define A_READ (UCHARP ("read")) sl@0: sl@0: #define A_QUERY_MAXREAD (UCHARP ("query/maxRead")) sl@0: #define A_CLEAR_READ (UCHARP ("clear/read")) sl@0: sl@0: /* sl@0: * Management of a simple buffer. sl@0: */ sl@0: sl@0: typedef struct ResultBuffer ResultBuffer; sl@0: sl@0: static void ResultClear _ANSI_ARGS_ ((ResultBuffer* r)); sl@0: static void ResultInit _ANSI_ARGS_ ((ResultBuffer* r)); sl@0: static int ResultLength _ANSI_ARGS_ ((ResultBuffer* r)); sl@0: static int ResultCopy _ANSI_ARGS_ ((ResultBuffer* r, sl@0: unsigned char* buf, int toRead)); sl@0: static void ResultAdd _ANSI_ARGS_ ((ResultBuffer* r, sl@0: unsigned char* buf, int toWrite)); sl@0: sl@0: /* sl@0: * This structure describes the channel type structure for tcl based sl@0: * transformations. sl@0: */ sl@0: sl@0: static Tcl_ChannelType transformChannelType = { sl@0: "transform", /* Type name. */ sl@0: TCL_CHANNEL_VERSION_3, sl@0: TransformCloseProc, /* Close proc. */ sl@0: TransformInputProc, /* Input proc. */ sl@0: TransformOutputProc, /* Output proc. */ sl@0: TransformSeekProc, /* Seek proc. */ sl@0: TransformSetOptionProc, /* Set option proc. */ sl@0: TransformGetOptionProc, /* Get option proc. */ sl@0: TransformWatchProc, /* Initialize notifier. */ sl@0: TransformGetFileHandleProc, /* Get OS handles out of channel. */ sl@0: NULL, /* close2proc */ sl@0: TransformBlockModeProc, /* Set blocking/nonblocking mode.*/ sl@0: NULL, /* Flush proc. */ sl@0: TransformNotifyProc, /* Handling of events bubbling up */ sl@0: TransformWideSeekProc, /* Wide seek proc */ sl@0: }; sl@0: sl@0: /* sl@0: * Possible values for 'flags' field in control structure, see below. sl@0: */ sl@0: sl@0: #define CHANNEL_ASYNC (1<<0) /* non-blocking mode */ sl@0: sl@0: /* sl@0: * Definition of the structure containing the information about the sl@0: * internal input buffer. sl@0: */ sl@0: sl@0: struct ResultBuffer { sl@0: unsigned char* buf; /* Reference to the buffer area */ sl@0: int allocated; /* Allocated size of the buffer area */ sl@0: int used; /* Number of bytes in the buffer, <= allocated */ sl@0: }; sl@0: sl@0: /* sl@0: * Additional bytes to allocate during buffer expansion sl@0: */ sl@0: sl@0: #define INCREMENT (512) sl@0: sl@0: /* sl@0: * Number of milliseconds to wait before firing an event to flush sl@0: * out information waiting in buffers (fileevent support). sl@0: */ sl@0: sl@0: #define FLUSH_DELAY (5) sl@0: sl@0: /* sl@0: * Convenience macro to make some casts easier to use. sl@0: */ sl@0: sl@0: #define UCHARP(x) ((unsigned char*) (x)) sl@0: #define NO_INTERP ((Tcl_Interp*) NULL) sl@0: sl@0: /* sl@0: * Definition of a structure used by all transformations generated here to sl@0: * maintain their local state. sl@0: */ sl@0: sl@0: struct TransformChannelData { sl@0: sl@0: /* sl@0: * General section. Data to integrate the transformation into the channel sl@0: * system. sl@0: */ sl@0: sl@0: Tcl_Channel self; /* Our own Channel handle */ sl@0: int readIsFlushed; /* Flag to note wether in.flushProc was called or not sl@0: */ sl@0: int flags; /* Currently CHANNEL_ASYNC or zero */ sl@0: int watchMask; /* Current watch/event/interest mask */ sl@0: int mode; /* mode of parent channel, OR'ed combination of sl@0: * TCL_READABLE, TCL_WRITABLE */ sl@0: Tcl_TimerToken timer; /* Timer for automatic flushing of information sl@0: * sitting in an internal buffer. Required for full sl@0: * fileevent support */ sl@0: /* sl@0: * Transformation specific data. sl@0: */ sl@0: sl@0: int maxRead; /* Maximum allowed number of bytes to read, as sl@0: * given to us by the tcl script implementing the sl@0: * transformation. */ sl@0: Tcl_Interp* interp; /* Reference to the interpreter which created the sl@0: * transformation. Used to execute the code sl@0: * below. */ sl@0: Tcl_Obj* command; /* Tcl code to execute for a buffer */ sl@0: ResultBuffer result; /* Internal buffer used to store the result of a sl@0: * transformation of incoming data. Additionally sl@0: * serves as buffer of all data not yet consumed by sl@0: * the reader. */ sl@0: }; sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclChannelTransform -- sl@0: * sl@0: * Implements the Tcl "testchannel transform" debugging command. sl@0: * This is part of the testing environment. This sets up a tcl sl@0: * script (cmdObjPtr) to be used as a transform on the channel. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: /* ARGSUSED */ sl@0: int sl@0: TclChannelTransform(interp, chan, cmdObjPtr) sl@0: Tcl_Interp *interp; /* Interpreter for result. */ sl@0: Tcl_Channel chan; /* Channel to transform. */ sl@0: Tcl_Obj *cmdObjPtr; /* Script to use for transform. */ sl@0: { sl@0: Channel *chanPtr; /* The actual channel. */ sl@0: ChannelState *statePtr; /* state info for channel */ sl@0: int mode; /* rw mode of the channel */ sl@0: TransformChannelData *dataPtr; sl@0: int res; sl@0: Tcl_DString ds; sl@0: sl@0: if (chan == (Tcl_Channel) NULL) { sl@0: return TCL_ERROR; sl@0: } sl@0: chanPtr = (Channel *) chan; sl@0: statePtr = chanPtr->state; sl@0: chanPtr = statePtr->topChanPtr; sl@0: chan = (Tcl_Channel) chanPtr; sl@0: mode = (statePtr->flags & (TCL_READABLE|TCL_WRITABLE)); sl@0: sl@0: /* sl@0: * Now initialize the transformation state and stack it upon the sl@0: * specified channel. One of the necessary things to do is to sl@0: * retrieve the blocking regime of the underlying channel and to sl@0: * use the same for us too. sl@0: */ sl@0: sl@0: dataPtr = (TransformChannelData*) ckalloc(sizeof(TransformChannelData)); sl@0: sl@0: Tcl_DStringInit (&ds); sl@0: Tcl_GetChannelOption(interp, chan, "-blocking", &ds); sl@0: sl@0: dataPtr->readIsFlushed = 0; sl@0: dataPtr->flags = 0; sl@0: sl@0: if (ds.string[0] == '0') { sl@0: dataPtr->flags |= CHANNEL_ASYNC; sl@0: } sl@0: sl@0: Tcl_DStringFree (&ds); sl@0: sl@0: dataPtr->self = chan; sl@0: dataPtr->watchMask = 0; sl@0: dataPtr->mode = mode; sl@0: dataPtr->timer = (Tcl_TimerToken) NULL; sl@0: dataPtr->maxRead = 4096; /* Initial value not relevant */ sl@0: dataPtr->interp = interp; sl@0: dataPtr->command = cmdObjPtr; sl@0: sl@0: Tcl_IncrRefCount(dataPtr->command); sl@0: sl@0: ResultInit(&dataPtr->result); sl@0: sl@0: dataPtr->self = Tcl_StackChannel(interp, &transformChannelType, sl@0: (ClientData) dataPtr, mode, chan); sl@0: if (dataPtr->self == (Tcl_Channel) NULL) { sl@0: Tcl_AppendResult(interp, "\nfailed to stack channel \"", sl@0: Tcl_GetChannelName(chan), "\"", (char *) NULL); sl@0: sl@0: Tcl_DecrRefCount(dataPtr->command); sl@0: ResultClear(&dataPtr->result); sl@0: ckfree((VOID *) dataPtr); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: * At last initialize the transformation at the script level. sl@0: */ sl@0: sl@0: if (dataPtr->mode & TCL_WRITABLE) { sl@0: res = ExecuteCallback (dataPtr, NO_INTERP, A_CREATE_WRITE, sl@0: NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); sl@0: sl@0: if (res != TCL_OK) { sl@0: Tcl_UnstackChannel(interp, chan); sl@0: return TCL_ERROR; sl@0: } sl@0: } sl@0: sl@0: if (dataPtr->mode & TCL_READABLE) { sl@0: res = ExecuteCallback (dataPtr, NO_INTERP, A_CREATE_READ, sl@0: NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); sl@0: sl@0: if (res != TCL_OK) { sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_DELETE_WRITE, sl@0: NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); sl@0: sl@0: Tcl_UnstackChannel(interp, chan); sl@0: return TCL_ERROR; sl@0: } sl@0: } sl@0: sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ExecuteCallback -- sl@0: * sl@0: * Executes the defined callback for buffer and sl@0: * operation. sl@0: * sl@0: * Sideeffects: sl@0: * As of the executed tcl script. sl@0: * sl@0: * Result: sl@0: * A standard TCL error code. In case of an sl@0: * error a message is left in the result area sl@0: * of the specified interpreter. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: ExecuteCallback (dataPtr, interp, op, buf, bufLen, transmit, preserve) sl@0: TransformChannelData* dataPtr; /* Transformation with the callback */ sl@0: Tcl_Interp* interp; /* Current interpreter, possibly NULL */ sl@0: unsigned char* op; /* Operation invoking the callback */ sl@0: unsigned char* buf; /* Buffer to give to the script. */ sl@0: int bufLen; /* Ands its length */ sl@0: int transmit; /* Flag, determines whether the result sl@0: * of the callback is sent to the sl@0: * underlying channel or not. */ sl@0: int preserve; /* Flag. If true the procedure will sl@0: * preserver the result state of all sl@0: * accessed interpreters. */ sl@0: { sl@0: /* sl@0: * Step 1, create the complete command to execute. Do this by appending sl@0: * operation and buffer to operate upon to a copy of the callback sl@0: * definition. We *cannot* create a list containing 3 objects and then use sl@0: * 'Tcl_EvalObjv', because the command may contain additional prefixed sl@0: * arguments. Feather's curried commands would come in handy here. sl@0: */ sl@0: sl@0: Tcl_Obj* resObj; /* See below, switch (transmit) */ sl@0: int resLen; sl@0: unsigned char* resBuf; sl@0: Tcl_SavedResult ciSave; sl@0: int res = TCL_OK; sl@0: Tcl_Obj* command = Tcl_DuplicateObj (dataPtr->command); sl@0: Tcl_Obj* temp; sl@0: sl@0: if (preserve) { sl@0: Tcl_SaveResult (dataPtr->interp, &ciSave); sl@0: } sl@0: sl@0: if (command == (Tcl_Obj*) NULL) { sl@0: /* Memory allocation problem */ sl@0: res = TCL_ERROR; sl@0: goto cleanup; sl@0: } sl@0: sl@0: Tcl_IncrRefCount(command); sl@0: sl@0: temp = Tcl_NewStringObj((char*) op, -1); sl@0: sl@0: if (temp == (Tcl_Obj*) NULL) { sl@0: /* Memory allocation problem */ sl@0: res = TCL_ERROR; sl@0: goto cleanup; sl@0: } sl@0: sl@0: res = Tcl_ListObjAppendElement(dataPtr->interp, command, temp); sl@0: sl@0: if (res != TCL_OK) sl@0: goto cleanup; sl@0: sl@0: /* sl@0: * Use a byte-array to prevent the misinterpretation of binary data sl@0: * coming through as UTF while at the tcl level. sl@0: */ sl@0: sl@0: temp = Tcl_NewByteArrayObj(buf, bufLen); sl@0: sl@0: if (temp == (Tcl_Obj*) NULL) { sl@0: /* Memory allocation problem */ sl@0: res = TCL_ERROR; sl@0: goto cleanup; sl@0: } sl@0: sl@0: res = Tcl_ListObjAppendElement (dataPtr->interp, command, temp); sl@0: sl@0: if (res != TCL_OK) sl@0: goto cleanup; sl@0: sl@0: /* sl@0: * Step 2, execute the command at the global level of the interpreter sl@0: * used to create the transformation. Destroy the command afterward. sl@0: * If an error occured and the current interpreter is defined and not sl@0: * equal to the interpreter for the callback, then copy the error sl@0: * message into current interpreter. Don't copy if in preservation mode. sl@0: */ sl@0: sl@0: res = Tcl_EvalObjEx(dataPtr->interp, command, TCL_EVAL_GLOBAL); sl@0: Tcl_DecrRefCount (command); sl@0: command = (Tcl_Obj*) NULL; sl@0: sl@0: if ((res != TCL_OK) && (interp != NO_INTERP) && sl@0: (dataPtr->interp != interp) && !preserve) { sl@0: Tcl_SetObjResult(interp, Tcl_GetObjResult(dataPtr->interp)); sl@0: return res; sl@0: } sl@0: sl@0: /* sl@0: * Step 3, transmit a possible conversion result to the underlying sl@0: * channel, or ourselves. sl@0: */ sl@0: sl@0: switch (transmit) { sl@0: case TRANSMIT_DONT: sl@0: /* nothing to do */ sl@0: break; sl@0: sl@0: case TRANSMIT_DOWN: sl@0: resObj = Tcl_GetObjResult(dataPtr->interp); sl@0: resBuf = (unsigned char*) Tcl_GetByteArrayFromObj(resObj, &resLen); sl@0: Tcl_WriteRaw(Tcl_GetStackedChannel(dataPtr->self), sl@0: (char*) resBuf, resLen); sl@0: break; sl@0: sl@0: case TRANSMIT_SELF: sl@0: resObj = Tcl_GetObjResult (dataPtr->interp); sl@0: resBuf = (unsigned char*) Tcl_GetByteArrayFromObj(resObj, &resLen); sl@0: Tcl_WriteRaw(dataPtr->self, (char*) resBuf, resLen); sl@0: break; sl@0: sl@0: case TRANSMIT_IBUF: sl@0: resObj = Tcl_GetObjResult (dataPtr->interp); sl@0: resBuf = (unsigned char*) Tcl_GetByteArrayFromObj(resObj, &resLen); sl@0: ResultAdd(&dataPtr->result, resBuf, resLen); sl@0: break; sl@0: sl@0: case TRANSMIT_NUM: sl@0: /* Interpret result as integer number */ sl@0: resObj = Tcl_GetObjResult (dataPtr->interp); sl@0: Tcl_GetIntFromObj(dataPtr->interp, resObj, &dataPtr->maxRead); sl@0: break; sl@0: } sl@0: sl@0: Tcl_ResetResult(dataPtr->interp); sl@0: sl@0: if (preserve) { sl@0: Tcl_RestoreResult(dataPtr->interp, &ciSave); sl@0: } sl@0: sl@0: return res; sl@0: sl@0: cleanup: sl@0: if (preserve) { sl@0: Tcl_RestoreResult(dataPtr->interp, &ciSave); sl@0: } sl@0: sl@0: if (command != (Tcl_Obj*) NULL) { sl@0: Tcl_DecrRefCount(command); sl@0: } sl@0: sl@0: return res; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformBlockModeProc -- sl@0: * sl@0: * Trap handler. Called by the generic IO system sl@0: * during option processing to change the blocking sl@0: * mode of the channel. sl@0: * sl@0: * Sideeffects: sl@0: * Forwards the request to the underlying sl@0: * channel. sl@0: * sl@0: * Result: sl@0: * 0 if successful, errno when failed. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformBlockModeProc (instanceData, mode) sl@0: ClientData instanceData; /* State of transformation */ sl@0: int mode; /* New blocking mode */ sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: sl@0: if (mode == TCL_MODE_NONBLOCKING) { sl@0: dataPtr->flags |= CHANNEL_ASYNC; sl@0: } else { sl@0: dataPtr->flags &= ~(CHANNEL_ASYNC); sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformCloseProc -- sl@0: * sl@0: * Trap handler. Called by the generic IO system sl@0: * during destruction of the transformation channel. sl@0: * sl@0: * Sideeffects: sl@0: * Releases the memory allocated in sl@0: * 'Tcl_TransformObjCmd'. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformCloseProc (instanceData, interp) sl@0: ClientData instanceData; sl@0: Tcl_Interp* interp; sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: sl@0: /* sl@0: * Important: In this procedure 'dataPtr->self' already points to sl@0: * the underlying channel. sl@0: */ sl@0: sl@0: /* sl@0: * There is no need to cancel an existing channel handler, this is already sl@0: * done. Either by 'Tcl_UnstackChannel' or by the general cleanup in sl@0: * 'Tcl_Close'. sl@0: * sl@0: * But we have to cancel an active timer to prevent it from firing on the sl@0: * removed channel. sl@0: */ sl@0: sl@0: if (dataPtr->timer != (Tcl_TimerToken) NULL) { sl@0: Tcl_DeleteTimerHandler (dataPtr->timer); sl@0: dataPtr->timer = (Tcl_TimerToken) NULL; sl@0: } sl@0: sl@0: /* sl@0: * Now flush data waiting in internal buffers to output and input. The sl@0: * input must be done despite the fact that there is no real receiver sl@0: * for it anymore. But the scripts might have sideeffects other parts sl@0: * of the system rely on (f.e. signaling the close to interested parties). sl@0: */ sl@0: sl@0: if (dataPtr->mode & TCL_WRITABLE) { sl@0: ExecuteCallback (dataPtr, interp, A_FLUSH_WRITE, sl@0: NULL, 0, TRANSMIT_DOWN, 1); sl@0: } sl@0: sl@0: if ((dataPtr->mode & TCL_READABLE) && !dataPtr->readIsFlushed) { sl@0: dataPtr->readIsFlushed = 1; sl@0: ExecuteCallback (dataPtr, interp, A_FLUSH_READ, sl@0: NULL, 0, TRANSMIT_IBUF, 1); sl@0: } sl@0: sl@0: if (dataPtr->mode & TCL_WRITABLE) { sl@0: ExecuteCallback (dataPtr, interp, A_DELETE_WRITE, sl@0: NULL, 0, TRANSMIT_DONT, 1); sl@0: } sl@0: sl@0: if (dataPtr->mode & TCL_READABLE) { sl@0: ExecuteCallback (dataPtr, interp, A_DELETE_READ, sl@0: NULL, 0, TRANSMIT_DONT, 1); sl@0: } sl@0: sl@0: /* sl@0: * General cleanup sl@0: */ sl@0: sl@0: ResultClear(&dataPtr->result); sl@0: Tcl_DecrRefCount(dataPtr->command); sl@0: ckfree((VOID*) dataPtr); sl@0: sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformInputProc -- sl@0: * sl@0: * Called by the generic IO system to convert read data. sl@0: * sl@0: * Sideeffects: sl@0: * As defined by the conversion. sl@0: * sl@0: * Result: sl@0: * A transformed buffer. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformInputProc (instanceData, buf, toRead, errorCodePtr) sl@0: ClientData instanceData; sl@0: char* buf; sl@0: int toRead; sl@0: int* errorCodePtr; sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: int gotBytes, read, res, copied; sl@0: Tcl_Channel downChan; sl@0: sl@0: /* should assert (dataPtr->mode & TCL_READABLE) */ sl@0: sl@0: if (toRead == 0) { sl@0: /* Catch a no-op. sl@0: */ sl@0: return 0; sl@0: } sl@0: sl@0: gotBytes = 0; sl@0: downChan = Tcl_GetStackedChannel(dataPtr->self); sl@0: sl@0: while (toRead > 0) { sl@0: /* sl@0: * Loop until the request is satisfied (or no data is available from sl@0: * below, possibly EOF). sl@0: */ sl@0: sl@0: copied = ResultCopy (&dataPtr->result, UCHARP (buf), toRead); sl@0: sl@0: toRead -= copied; sl@0: buf += copied; sl@0: gotBytes += copied; sl@0: sl@0: if (toRead == 0) { sl@0: /* The request was completely satisfied from our buffers. sl@0: * We can break out of the loop and return to the caller. sl@0: */ sl@0: return gotBytes; sl@0: } sl@0: sl@0: /* sl@0: * Length (dataPtr->result) == 0, toRead > 0 here . Use the incoming sl@0: * 'buf'! as target to store the intermediary information read sl@0: * from the underlying channel. sl@0: * sl@0: * Ask the tcl level how much data it allows us to read from sl@0: * the underlying channel. This feature allows the transform to sl@0: * signal EOF upstream although there is none downstream. Useful sl@0: * to control an unbounded 'fcopy', either through counting bytes, sl@0: * or by pattern matching. sl@0: */ sl@0: sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_QUERY_MAXREAD, sl@0: NULL, 0, TRANSMIT_NUM /* -> maxRead */, 1); sl@0: sl@0: if (dataPtr->maxRead >= 0) { sl@0: if (dataPtr->maxRead < toRead) { sl@0: toRead = dataPtr->maxRead; sl@0: } sl@0: } /* else: 'maxRead < 0' == Accept the current value of toRead */ sl@0: sl@0: if (toRead <= 0) { sl@0: return gotBytes; sl@0: } sl@0: sl@0: read = Tcl_ReadRaw(downChan, buf, toRead); sl@0: sl@0: if (read < 0) { sl@0: /* Report errors to caller. EAGAIN is a special situation. sl@0: * If we had some data before we report that instead of the sl@0: * request to re-try. sl@0: */ sl@0: sl@0: if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { sl@0: return gotBytes; sl@0: } sl@0: sl@0: *errorCodePtr = Tcl_GetErrno(); sl@0: return -1; sl@0: } sl@0: sl@0: if (read == 0) { sl@0: /* sl@0: * Check wether we hit on EOF in the underlying channel or sl@0: * not. If not differentiate between blocking and sl@0: * non-blocking modes. In non-blocking mode we ran sl@0: * temporarily out of data. Signal this to the caller via sl@0: * EWOULDBLOCK and error return (-1). In the other cases sl@0: * we simply return what we got and let the caller wait sl@0: * for more. On the other hand, if we got an EOF we have sl@0: * to convert and flush all waiting partial data. sl@0: */ sl@0: sl@0: if (! Tcl_Eof (downChan)) { sl@0: if ((gotBytes == 0) && (dataPtr->flags & CHANNEL_ASYNC)) { sl@0: *errorCodePtr = EWOULDBLOCK; sl@0: return -1; sl@0: } else { sl@0: return gotBytes; sl@0: } sl@0: } else { sl@0: if (dataPtr->readIsFlushed) { sl@0: /* Already flushed, nothing to do anymore sl@0: */ sl@0: return gotBytes; sl@0: } sl@0: sl@0: dataPtr->readIsFlushed = 1; sl@0: sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_FLUSH_READ, sl@0: NULL, 0, TRANSMIT_IBUF, P_PRESERVE); sl@0: sl@0: if (ResultLength (&dataPtr->result) == 0) { sl@0: /* we had nothing to flush */ sl@0: return gotBytes; sl@0: } sl@0: sl@0: continue; /* at: while (toRead > 0) */ sl@0: } sl@0: } /* read == 0 */ sl@0: sl@0: /* Transform the read chunk and add the result to our sl@0: * read buffer (dataPtr->result) sl@0: */ sl@0: sl@0: res = ExecuteCallback (dataPtr, NO_INTERP, A_READ, sl@0: UCHARP (buf), read, TRANSMIT_IBUF, P_PRESERVE); sl@0: sl@0: if (res != TCL_OK) { sl@0: *errorCodePtr = EINVAL; sl@0: return -1; sl@0: } sl@0: } /* while toRead > 0 */ sl@0: sl@0: return gotBytes; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformOutputProc -- sl@0: * sl@0: * Called by the generic IO system to convert data sl@0: * waiting to be written. sl@0: * sl@0: * Sideeffects: sl@0: * As defined by the transformation. sl@0: * sl@0: * Result: sl@0: * A transformed buffer. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformOutputProc (instanceData, buf, toWrite, errorCodePtr) sl@0: ClientData instanceData; sl@0: CONST char* buf; sl@0: int toWrite; sl@0: int* errorCodePtr; sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: int res; sl@0: sl@0: /* should assert (dataPtr->mode & TCL_WRITABLE) */ sl@0: sl@0: if (toWrite == 0) { sl@0: /* Catch a no-op. sl@0: */ sl@0: return 0; sl@0: } sl@0: sl@0: res = ExecuteCallback (dataPtr, NO_INTERP, A_WRITE, sl@0: UCHARP (buf), toWrite, sl@0: TRANSMIT_DOWN, P_NO_PRESERVE); sl@0: sl@0: if (res != TCL_OK) { sl@0: *errorCodePtr = EINVAL; sl@0: return -1; sl@0: } sl@0: sl@0: return toWrite; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformSeekProc -- sl@0: * sl@0: * This procedure is called by the generic IO level sl@0: * to move the access point in a channel. sl@0: * sl@0: * Sideeffects: sl@0: * Moves the location at which the channel sl@0: * will be accessed in future operations. sl@0: * Flushes all transformation buffers, then sl@0: * forwards it to the underlying channel. sl@0: * sl@0: * Result: sl@0: * -1 if failed, the new position if sl@0: * successful. An output argument contains sl@0: * the POSIX error code if an error sl@0: * occurred, or zero. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformSeekProc (instanceData, offset, mode, errorCodePtr) sl@0: ClientData instanceData; /* The channel to manipulate */ sl@0: long offset; /* Size of movement. */ sl@0: int mode; /* How to move */ sl@0: int* errorCodePtr; /* Location of error flag. */ sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: Tcl_Channel parent = Tcl_GetStackedChannel(dataPtr->self); sl@0: Tcl_ChannelType* parentType = Tcl_GetChannelType(parent); sl@0: Tcl_DriverSeekProc* parentSeekProc = Tcl_ChannelSeekProc(parentType); sl@0: sl@0: if ((offset == 0) && (mode == SEEK_CUR)) { sl@0: /* This is no seek but a request to tell the caller the current sl@0: * location. Simply pass the request down. sl@0: */ sl@0: sl@0: return (*parentSeekProc) (Tcl_GetChannelInstanceData(parent), sl@0: offset, mode, errorCodePtr); sl@0: } sl@0: sl@0: /* sl@0: * It is a real request to change the position. Flush all data waiting sl@0: * for output and discard everything in the input buffers. Then pass sl@0: * the request down, unchanged. sl@0: */ sl@0: sl@0: if (dataPtr->mode & TCL_WRITABLE) { sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_FLUSH_WRITE, sl@0: NULL, 0, TRANSMIT_DOWN, P_NO_PRESERVE); sl@0: } sl@0: sl@0: if (dataPtr->mode & TCL_READABLE) { sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_CLEAR_READ, sl@0: NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); sl@0: ResultClear(&dataPtr->result); sl@0: dataPtr->readIsFlushed = 0; sl@0: } sl@0: sl@0: return (*parentSeekProc) (Tcl_GetChannelInstanceData(parent), sl@0: offset, mode, errorCodePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TransformWideSeekProc -- sl@0: * sl@0: * This procedure is called by the generic IO level to move the sl@0: * access point in a channel, with a (potentially) 64-bit offset. sl@0: * sl@0: * Side effects: sl@0: * Moves the location at which the channel will be accessed in sl@0: * future operations. Flushes all transformation buffers, then sl@0: * forwards it to the underlying channel. sl@0: * sl@0: * Result: sl@0: * -1 if failed, the new position if successful. An output sl@0: * argument contains the POSIX error code if an error occurred, sl@0: * or zero. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static Tcl_WideInt sl@0: TransformWideSeekProc (instanceData, offset, mode, errorCodePtr) sl@0: ClientData instanceData; /* The channel to manipulate */ sl@0: Tcl_WideInt offset; /* Size of movement. */ sl@0: int mode; /* How to move */ sl@0: int* errorCodePtr; /* Location of error flag. */ sl@0: { sl@0: TransformChannelData* dataPtr = sl@0: (TransformChannelData*) instanceData; sl@0: Tcl_Channel parent = sl@0: Tcl_GetStackedChannel(dataPtr->self); sl@0: Tcl_ChannelType* parentType = sl@0: Tcl_GetChannelType(parent); sl@0: Tcl_DriverSeekProc* parentSeekProc = sl@0: Tcl_ChannelSeekProc(parentType); sl@0: Tcl_DriverWideSeekProc* parentWideSeekProc = sl@0: Tcl_ChannelWideSeekProc(parentType); sl@0: ClientData parentData = sl@0: Tcl_GetChannelInstanceData(parent); sl@0: sl@0: if ((offset == Tcl_LongAsWide(0)) && (mode == SEEK_CUR)) { sl@0: /* sl@0: * This is no seek but a request to tell the caller the current sl@0: * location. Simply pass the request down. sl@0: */ sl@0: sl@0: if (parentWideSeekProc != NULL) { sl@0: return (*parentWideSeekProc) (parentData, offset, mode, sl@0: errorCodePtr); sl@0: } sl@0: sl@0: return Tcl_LongAsWide((*parentSeekProc) (parentData, 0, mode, sl@0: errorCodePtr)); sl@0: } sl@0: sl@0: /* sl@0: * It is a real request to change the position. Flush all data waiting sl@0: * for output and discard everything in the input buffers. Then pass sl@0: * the request down, unchanged. sl@0: */ sl@0: sl@0: if (dataPtr->mode & TCL_WRITABLE) { sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_FLUSH_WRITE, sl@0: NULL, 0, TRANSMIT_DOWN, P_NO_PRESERVE); sl@0: } sl@0: sl@0: if (dataPtr->mode & TCL_READABLE) { sl@0: ExecuteCallback (dataPtr, NO_INTERP, A_CLEAR_READ, sl@0: NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE); sl@0: ResultClear(&dataPtr->result); sl@0: dataPtr->readIsFlushed = 0; sl@0: } sl@0: sl@0: /* sl@0: * If we have a wide seek capability, we should stick with that. sl@0: */ sl@0: if (parentWideSeekProc != NULL) { sl@0: return (*parentWideSeekProc) (parentData, offset, mode, errorCodePtr); sl@0: } sl@0: sl@0: /* sl@0: * We're transferring to narrow seeks at this point; this is a bit sl@0: * complex because we have to check whether the seek is possible sl@0: * first (i.e. whether we are losing information in truncating the sl@0: * bits of the offset.) Luckily, there's a defined error for what sl@0: * happens when trying to go out of the representable range. sl@0: */ sl@0: if (offsetTcl_LongAsWide(LONG_MAX)) { sl@0: *errorCodePtr = EOVERFLOW; sl@0: return Tcl_LongAsWide(-1); sl@0: } sl@0: return Tcl_LongAsWide((*parentSeekProc) (parentData, sl@0: Tcl_WideAsLong(offset), mode, errorCodePtr)); sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformSetOptionProc -- sl@0: * sl@0: * Called by generic layer to handle the reconfi- sl@0: * guration of channel specific options. As this sl@0: * channel type does not have such, it simply passes sl@0: * all requests downstream. sl@0: * sl@0: * Sideeffects: sl@0: * As defined by the channel downstream. sl@0: * sl@0: * Result: sl@0: * A standard TCL error code. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformSetOptionProc (instanceData, interp, optionName, value) sl@0: ClientData instanceData; sl@0: Tcl_Interp *interp; sl@0: CONST char *optionName; sl@0: CONST char *value; sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); sl@0: Tcl_DriverSetOptionProc *setOptionProc; sl@0: sl@0: setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(downChan)); sl@0: if (setOptionProc != NULL) { sl@0: return (*setOptionProc)(Tcl_GetChannelInstanceData(downChan), sl@0: interp, optionName, value); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformGetOptionProc -- sl@0: * sl@0: * Called by generic layer to handle requests for sl@0: * the values of channel specific options. As this sl@0: * channel type does not have such, it simply passes sl@0: * all requests downstream. sl@0: * sl@0: * Sideeffects: sl@0: * As defined by the channel downstream. sl@0: * sl@0: * Result: sl@0: * A standard TCL error code. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformGetOptionProc (instanceData, interp, optionName, dsPtr) sl@0: ClientData instanceData; sl@0: Tcl_Interp* interp; sl@0: CONST char* optionName; sl@0: Tcl_DString* dsPtr; sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); sl@0: Tcl_DriverGetOptionProc *getOptionProc; sl@0: sl@0: getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); sl@0: if (getOptionProc != NULL) { sl@0: return (*getOptionProc)(Tcl_GetChannelInstanceData(downChan), sl@0: interp, optionName, dsPtr); sl@0: } else if (optionName == (CONST char*) NULL) { sl@0: /* sl@0: * Request is query for all options, this is ok. sl@0: */ sl@0: return TCL_OK; sl@0: } sl@0: /* sl@0: * Request for a specific option has to fail, we don't have any. sl@0: */ sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformWatchProc -- sl@0: * sl@0: * Initialize the notifier to watch for events from sl@0: * this channel. sl@0: * sl@0: * Sideeffects: sl@0: * Sets up the notifier so that a future sl@0: * event on the channel will be seen by Tcl. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: /* ARGSUSED */ sl@0: static void sl@0: TransformWatchProc (instanceData, mask) sl@0: ClientData instanceData; /* Channel to watch */ sl@0: int mask; /* Events of interest */ sl@0: { sl@0: /* The caller expressed interest in events occuring for this sl@0: * channel. We are forwarding the call to the underlying sl@0: * channel now. sl@0: */ sl@0: sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: Tcl_Channel downChan; sl@0: sl@0: dataPtr->watchMask = mask; sl@0: sl@0: /* No channel handlers any more. We will be notified automatically sl@0: * about events on the channel below via a call to our sl@0: * 'TransformNotifyProc'. But we have to pass the interest down now. sl@0: * We are allowed to add additional 'interest' to the mask if we want sl@0: * to. But this transformation has no such interest. It just passes sl@0: * the request down, unchanged. sl@0: */ sl@0: sl@0: downChan = Tcl_GetStackedChannel(dataPtr->self); sl@0: sl@0: (Tcl_GetChannelType(downChan)) sl@0: ->watchProc(Tcl_GetChannelInstanceData(downChan), mask); sl@0: sl@0: /* sl@0: * Management of the internal timer. sl@0: */ sl@0: sl@0: if ((dataPtr->timer != (Tcl_TimerToken) NULL) && sl@0: (!(mask & TCL_READABLE) || (ResultLength(&dataPtr->result) == 0))) { sl@0: sl@0: /* A pending timer exists, but either is there no (more) sl@0: * interest in the events it generates or nothing is availablee sl@0: * for reading, so remove it. sl@0: */ sl@0: sl@0: Tcl_DeleteTimerHandler (dataPtr->timer); sl@0: dataPtr->timer = (Tcl_TimerToken) NULL; sl@0: } sl@0: sl@0: if ((dataPtr->timer == (Tcl_TimerToken) NULL) && sl@0: (mask & TCL_READABLE) && (ResultLength (&dataPtr->result) > 0)) { sl@0: sl@0: /* There is no pending timer, but there is interest in readable sl@0: * events and we actually have data waiting, so generate a timer sl@0: * to flush that. sl@0: */ sl@0: sl@0: dataPtr->timer = Tcl_CreateTimerHandler (FLUSH_DELAY, sl@0: TransformChannelHandlerTimer, (ClientData) dataPtr); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformGetFileHandleProc -- sl@0: * sl@0: * Called from Tcl_GetChannelHandle to retrieve sl@0: * OS specific file handle from inside this channel. sl@0: * sl@0: * Sideeffects: sl@0: * None. sl@0: * sl@0: * Result: sl@0: * The appropriate Tcl_File or NULL if not sl@0: * present. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: static int sl@0: TransformGetFileHandleProc (instanceData, direction, handlePtr) sl@0: ClientData instanceData; /* Channel to query */ sl@0: int direction; /* Direction of interest */ sl@0: ClientData* handlePtr; /* Place to store the handle into */ sl@0: { sl@0: /* sl@0: * Return the handle belonging to parent channel. sl@0: * IOW, pass the request down and the result up. sl@0: */ sl@0: sl@0: TransformChannelData* dataPtr = (TransformChannelData*) instanceData; sl@0: sl@0: return Tcl_GetChannelHandle(Tcl_GetStackedChannel(dataPtr->self), sl@0: direction, handlePtr); sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformNotifyProc -- sl@0: * sl@0: * ------------------------------------------------* sl@0: * Handler called by Tcl to inform us of activity sl@0: * on the underlying channel. sl@0: * ------------------------------------------------* sl@0: * sl@0: * Sideeffects: sl@0: * May process the incoming event by itself. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: TransformNotifyProc (clientData, mask) sl@0: ClientData clientData; /* The state of the notified transformation */ sl@0: int mask; /* The mask of occuring events */ sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) clientData; sl@0: sl@0: /* sl@0: * An event occured in the underlying channel. This sl@0: * transformation doesn't process such events thus returns the sl@0: * incoming mask unchanged. sl@0: */ sl@0: sl@0: if (dataPtr->timer != (Tcl_TimerToken) NULL) { sl@0: /* sl@0: * Delete an existing timer. It was not fired, yet we are sl@0: * here, so the channel below generated such an event and we sl@0: * don't have to. The renewal of the interest after the sl@0: * execution of channel handlers will eventually cause us to sl@0: * recreate the timer (in TransformWatchProc). sl@0: */ sl@0: sl@0: Tcl_DeleteTimerHandler (dataPtr->timer); sl@0: dataPtr->timer = (Tcl_TimerToken) NULL; sl@0: } sl@0: sl@0: return mask; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * TransformChannelHandlerTimer -- sl@0: * sl@0: * Called by the notifier (-> timer) to flush out sl@0: * information waiting in the input buffer. sl@0: * sl@0: * Sideeffects: sl@0: * As of 'Tcl_NotifyChannel'. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static void sl@0: TransformChannelHandlerTimer (clientData) sl@0: ClientData clientData; /* Transformation to query */ sl@0: { sl@0: TransformChannelData* dataPtr = (TransformChannelData*) clientData; sl@0: sl@0: dataPtr->timer = (Tcl_TimerToken) NULL; sl@0: sl@0: if (!(dataPtr->watchMask & TCL_READABLE) || sl@0: (ResultLength (&dataPtr->result) == 0)) { sl@0: /* The timer fired, but either is there no (more) sl@0: * interest in the events it generates or nothing is available sl@0: * for reading, so ignore it and don't recreate it. sl@0: */ sl@0: sl@0: return; sl@0: } sl@0: sl@0: Tcl_NotifyChannel(dataPtr->self, TCL_READABLE); sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ResultClear -- sl@0: * sl@0: * Deallocates any memory allocated by 'ResultAdd'. sl@0: * sl@0: * Sideeffects: sl@0: * See above. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static void sl@0: ResultClear (r) sl@0: ResultBuffer* r; /* Reference to the buffer to clear out */ sl@0: { sl@0: r->used = 0; sl@0: sl@0: if (r->allocated) { sl@0: ckfree((char*) r->buf); sl@0: r->buf = UCHARP (NULL); sl@0: r->allocated = 0; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ResultInit -- sl@0: * sl@0: * Initializes the specified buffer structure. The sl@0: * structure will contain valid information for an sl@0: * emtpy buffer. sl@0: * sl@0: * Sideeffects: sl@0: * See above. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static void sl@0: ResultInit (r) sl@0: ResultBuffer* r; /* Reference to the structure to initialize */ sl@0: { sl@0: r->used = 0; sl@0: r->allocated = 0; sl@0: r->buf = UCHARP (NULL); sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ResultLength -- sl@0: * sl@0: * Returns the number of bytes stored in the buffer. sl@0: * sl@0: * Sideeffects: sl@0: * None. sl@0: * sl@0: * Result: sl@0: * An integer, see above too. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: ResultLength (r) sl@0: ResultBuffer* r; /* The structure to query */ sl@0: { sl@0: return r->used; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ResultCopy -- sl@0: * sl@0: * Copies the requested number of bytes from the sl@0: * buffer into the specified array and removes them sl@0: * from the buffer afterward. Copies less if there sl@0: * is not enough data in the buffer. sl@0: * sl@0: * Sideeffects: sl@0: * See above. sl@0: * sl@0: * Result: sl@0: * The number of actually copied bytes, sl@0: * possibly less than 'toRead'. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static int sl@0: ResultCopy (r, buf, toRead) sl@0: ResultBuffer* r; /* The buffer to read from */ sl@0: unsigned char* buf; /* The buffer to copy into */ sl@0: int toRead; /* Number of requested bytes */ sl@0: { sl@0: if (r->used == 0) { sl@0: /* Nothing to copy in the case of an empty buffer. sl@0: */ sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: if (r->used == toRead) { sl@0: /* We have just enough. Copy everything to the caller. sl@0: */ sl@0: sl@0: memcpy ((VOID*) buf, (VOID*) r->buf, (size_t) toRead); sl@0: r->used = 0; sl@0: return toRead; sl@0: } sl@0: sl@0: if (r->used > toRead) { sl@0: /* The internal buffer contains more than requested. sl@0: * Copy the requested subset to the caller, and shift sl@0: * the remaining bytes down. sl@0: */ sl@0: sl@0: memcpy ((VOID*) buf, (VOID*) r->buf, (size_t) toRead); sl@0: memmove ((VOID*) r->buf, (VOID*) (r->buf + toRead), sl@0: (size_t) r->used - toRead); sl@0: sl@0: r->used -= toRead; sl@0: return toRead; sl@0: } sl@0: sl@0: /* There is not enough in the buffer to satisfy the caller, so sl@0: * take everything. sl@0: */ sl@0: sl@0: memcpy((VOID*) buf, (VOID*) r->buf, (size_t) r->used); sl@0: toRead = r->used; sl@0: r->used = 0; sl@0: return toRead; sl@0: } sl@0: sl@0: /* sl@0: *------------------------------------------------------* sl@0: * sl@0: * ResultAdd -- sl@0: * sl@0: * Adds the bytes in the specified array to the sl@0: * buffer, by appending it. sl@0: * sl@0: * Sideeffects: sl@0: * See above. sl@0: * sl@0: * Result: sl@0: * None. sl@0: * sl@0: *------------------------------------------------------* sl@0: */ sl@0: sl@0: static void sl@0: ResultAdd (r, buf, toWrite) sl@0: ResultBuffer* r; /* The buffer to extend */ sl@0: unsigned char* buf; /* The buffer to read from */ sl@0: int toWrite; /* The number of bytes in 'buf' */ sl@0: { sl@0: if ((r->used + toWrite) > r->allocated) { sl@0: /* Extension of the internal buffer is required. sl@0: */ sl@0: sl@0: if (r->allocated == 0) { sl@0: r->allocated = toWrite + INCREMENT; sl@0: r->buf = UCHARP (ckalloc((unsigned) r->allocated)); sl@0: } else { sl@0: r->allocated += toWrite + INCREMENT; sl@0: r->buf = UCHARP (ckrealloc((char*) r->buf, sl@0: (unsigned) r->allocated)); sl@0: } sl@0: } sl@0: sl@0: /* now copy data */ sl@0: memcpy(r->buf + r->used, buf, (size_t) toWrite); sl@0: r->used += toWrite; sl@0: }