示例#1
0
    /// <summary> AllocChannelBuffer -> ChannelBuffer
    /// 
    /// Create a new ChannelBuffer object
    /// </summary>

    internal ChannelBuffer( int length )
    {
      int n;

      n = length + BUFFER_PADDING + BUFFER_PADDING;
      buf = new byte[n];
      nextAdded = BUFFER_PADDING;
      nextRemoved = BUFFER_PADDING;
      bufLength = length + BUFFER_PADDING;
      next = null;
    }
示例#2
0
        /// <summary> AllocChannelBuffer -> ChannelBuffer
        ///
        /// Create a new ChannelBuffer object
        /// </summary>

        internal ChannelBuffer(int length)
        {
            int n;

            n           = length + BUFFER_PADDING + BUFFER_PADDING;
            buf         = new byte[n];
            nextAdded   = BUFFER_PADDING;
            nextRemoved = BUFFER_PADDING;
            bufLength   = length + BUFFER_PADDING;
            next        = null;
        }
示例#3
0
    /// <summary> DiscardOutputQueued -> discardQueued
    /// 
    /// Discards all output queued in the output queue of a channel.
    /// </summary>

    private void discardQueued()
    {
      ChannelBuffer buf;

      while ( outQueueHead != null )
      {
        buf = outQueueHead;
        outQueueHead = buf.next;
        recycleBuffer( buf, false );
      }
      outQueueHead = null;
      outQueueTail = null;
    }
示例#4
0
    /// <summary> RecycleBuffer -> recycleBuffer
    /// 
    /// Helper function to recycle output buffers. Ensures that
    /// that curOut is set to a buffer. Only if these conditions
    /// are met is the buffer released so that it can be
    /// garbage collected.
    /// </summary>

    private void recycleBuffer( ChannelBuffer buf, bool mustDiscard )
    {

      if ( mustDiscard )
        return;

      // Only save buffers which are at least as big as the requested
      // buffersize for the channel. This is to honor dynamic changes
      // of the buffersize made by the user.

      if ( ( buf.bufLength - tcl.lang.ChannelBuffer.BUFFER_PADDING ) < bufSize )
      {
        return;
      }

      if ( curOut == null )
      {
        curOut = buf;
        buf.nextRemoved = tcl.lang.ChannelBuffer.BUFFER_PADDING;
        buf.nextAdded = tcl.lang.ChannelBuffer.BUFFER_PADDING;
        buf.next = null;
      }
    }
示例#5
0
    /// <summary> FlushChannel -> flushChannel
    /// 
    /// This function flushes as much of the queued output as is possible
    /// now. If calledFromAsyncFlush is true, it is being called in an
    /// event handler to flush channel output asynchronously.
    /// 
    /// Return 0 if successful, else the error code that was returned by the
    /// channel type operation.
    /// 
    /// May produce output on a channel. May block indefinitely if the
    /// channel is synchronous. May schedule an async flush on the channel.
    /// May recycle memory for buffers in the output queue.
    /// 
    /// </summary>
    /// <param name="interp">                Interp object.
    /// </param>
    /// <param name="calledFromAsyncFlush">  True if called from an asynchronous
    /// flush callback.
    /// </param>

    internal int flushChannel( Interp interp, bool calledFromAsyncFlush )
    {
      //ChannelState *statePtr = chanPtr->state;
      ChannelBuffer buf;
      int toWrite; // Amount of output data in current
      // buffer available to be written.
      int written; // Amount of output data actually
      // written in current round.
      int errorCode = 0; // Stores POSIX error codes from
      // channel driver operations.
      bool wroteSome = false; // Set to true if any data was
      // written to the driver.

      // Prevent writing on a dead channel -- a channel that has been closed
      // but not yet deallocated. This can occur if the exit handler for the
      // channel deallocation runs before all channels are deregistered in
      // all interpreters.

      //if (CheckForDeadChannel(interp, statePtr)) return -1;

      // Loop over the queued buffers and attempt to flush as
      // much as possible of the queued output to the channel.

      while ( true )
      {
        // If the queue is empty and there is a ready current buffer, OR if
        // the current buffer is full, then move the current buffer to the
        // queue.

        if ( ( ( curOut != null ) && ( curOut.nextAdded == curOut.bufLength ) ) || ( bufferReady && ( outQueueHead == null ) ) )
        {
          bufferReady = false;
          curOut.next = null;
          if ( outQueueHead == null )
          {
            outQueueHead = curOut;
          }
          else
          {
            outQueueTail.next = curOut;
          }
          outQueueTail = curOut;
          curOut = null;
        }
        buf = outQueueHead;

        // If we are not being called from an async flush and an async
        // flush is active, we just return without producing any output.

        if ( ( !calledFromAsyncFlush ) && bgFlushScheduled )
        {
          return 0;
        }

        // If the output queue is still empty, break out of the while loop.

        if ( buf == null )
        {
          break; // Out of the "while (1)".
        }

        // Produce the output on the channel.

        toWrite = buf.nextAdded - buf.nextRemoved;
        //written = (chanPtr->typePtr->outputProc) (chanPtr->instanceData,
        //        bufPtr->buf + bufPtr->nextRemoved, toWrite,
        //        &errorCode);
        try
        {
          output.Write( buf.buf, buf.nextRemoved, toWrite );
          written = toWrite;
        }
        catch ( IOException ex )
        {
          // FIXME: How can we recover and get posix errors?
          SupportClass.WriteStackTrace( ex, System.Console.Error );
          errorCode = TclPosixException.EIO; // Generic I/O error ???
          written = -1;
        }

        // If the write failed completely attempt to start the asynchronous
        // flush mechanism and break out of this loop - do not attempt to
        // write any more output at this time.

        if ( written < 0 )
        {
          // If the last attempt to write was interrupted, simply retry.

          if ( errorCode == TclPosixException.EINTR )
          {
            errorCode = 0;
            continue;
          }

          // If the channel is non-blocking and we would have blocked,
          // start a background flushing handler and break out of the loop.

          if ( ( errorCode == TclPosixException.EWOULDBLOCK ) || ( errorCode == TclPosixException.EAGAIN ) )
          {
            // This used to check for CHANNEL_NONBLOCKING, and panic
            // if the channel was blocking.  However, it appears
            // that setting stdin to -blocking 0 has some effect on
            // the stdout when it's a tty channel (dup'ed underneath)

            if ( !bgFlushScheduled )
            {
              bgFlushScheduled = true;
              updateInterest();
            }
            errorCode = 0;
            break;
          }

          // Decide whether to report the error upwards or defer it.

          if ( calledFromAsyncFlush )
          {
            if ( unreportedError == 0 )
            {
              unreportedError = errorCode;
            }
          }
          else
          {
            // FIXME: Need to figure out what to do here!
            //Tcl_SetErrno(errorCode);
            //if (interp != NULL) {
            //    // Casting away CONST here is safe because the
            //    // TCL_VOLATILE flag guarantees CONST treatment
            //    // of the Posix error string.
            //    Tcl_SetResult(interp,
            //            (char *) Tcl_PosixError(interp), TCL_VOLATILE);
          }

          // When we get an error we throw away all the output
          // currently queued.

          discardQueued();
          continue;
        }
        else
        {
          wroteSome = true;
        }

        buf.nextRemoved += written;

        // If this buffer is now empty, recycle it.

        if ( buf.nextRemoved == buf.nextAdded )
        {
          outQueueHead = buf.next;
          if ( outQueueHead == null )
          {
            outQueueTail = null;
          }
          recycleBuffer( buf, false );
        }
      } // Closes "while (1)".

      // If we wrote some data while flushing in the background, we are done.
      // We can't finish the background flush until we run out of data and
      // the channel becomes writable again.  This ensures that all of the
      // pending data has been flushed at the system level.

      if ( bgFlushScheduled )
      {
        if ( wroteSome )
        {
          return errorCode;
        }
        else if ( outQueueHead == null )
        {
          bgFlushScheduled = false;
          // FIXME: What is this watchProc?
          //(chanPtr->typePtr->watchProc)(chanPtr->instanceData,
          //        statePtr->interestMask);
        }
      }

      // If the channel is flagged as closed, delete it when the refCount
      // drops to zero, the output queue is empty and there is no output
      // in the current output buffer.

      if ( closed && ( refCount <= 0 ) && ( outQueueHead == null ) && ( ( curOut == null ) || ( curOut.nextAdded == curOut.nextRemoved ) ) )
      {
        return closeChannel( interp, errorCode );
      }
      return errorCode;
    }
示例#6
0
    /// <summary> CloseChannel -> closeChannel
    /// 
    /// Utility procedure to close a channel and free associated resources.
    /// 
    /// If the channel was stacked, then the it will copy the necessary
    /// elements of the NEXT channel into the TOP channel, in essence
    /// unstacking the channel.  The NEXT channel will then be freed.
    /// 
    /// If the channel was not stacked, then we will free all the bits
    /// for the TOP channel, including the data structure itself.
    /// 
    /// Returns 1 if the channel was stacked, 0 otherwise.
    /// </summary>

    protected internal int closeChannel( Interp interp, int errorCode )
    {
      int result = 0;
      //ChannelState *statePtr;		// state of the channel stack.
      //ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);

      //if (chanPtr == NULL) {
      //    return result;
      //}
      //statePtr = chanPtr->state;

      // Discard a leftover buffer in the current output buffer field.

      if ( curOut != null )
      {
        //ckfree((char *) statePtr->curOutPtr);
        curOut = null;
      }

      // The caller guarantees that there are no more buffers
      // queued for output.

      if ( outQueueHead != null )
      {
        throw new TclRuntimeError( "TclFlush, closed channel: queued output left" );
      }

      // If the EOF character is set in the channel, append that to the
      // output device.

      if ( eofChar != 0 )
      {
        try
        {
          output.WriteByte( (byte)eofChar );
        }
        catch ( IOException ex )
        {
          // FIXME: How can we recover here??
          SupportClass.WriteStackTrace( ex, Console.Error );
        }
      }

      // Remove this channel from of the list of all channels.

      //Tcl_CutChannel((Tcl_Channel) chanPtr);

      // Close and free the channel driver state.

      //if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) {
      //    result = (chanPtr->typePtr->closeProc)(chanPtr->instanceData, interp);
      //} else {
      //    result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp,
      //            0);
      //}

      // Some resources can be cleared only if the bottom channel
      // in a stack is closed. All the other channels in the stack
      // are not allowed to remove.

      //if (chanPtr == statePtr->bottomChanPtr) {
      //    if (statePtr->channelName != (char *) NULL) {
      //        ckfree((char *) statePtr->channelName);
      //        statePtr->channelName = NULL;
      //    }

      //    Tcl_FreeEncoding(statePtr->encoding);
      //    if (statePtr->outputStage != NULL) {
      //        ckfree((char *) statePtr->outputStage);
      //       statePtr->outputStage = (char *) NULL;
      //    }
      //}

      // If we are being called synchronously, report either
      // any latent error on the channel or the current error.

      if ( unreportedError != 0 )
      {
        errorCode = unreportedError;
      }
      if ( errorCode == 0 )
      {
        errorCode = result;
        if ( errorCode != 0 )
        {
          // FIXME: How can we deal with this errno issue?
          //Tcl_SetErrno(errorCode);
        }
      }

      // Cancel any outstanding timer.

      //Tcl_DeleteTimerHandler(statePtr->timer);

      // Mark the channel as deleted by clearing the type structure.

      //if (chanPtr->downChanPtr != (Channel *) NULL) {
      //    Channel *downChanPtr = chanPtr->downChanPtr;

      //    statePtr->nextCSPtr	= tsdPtr->firstCSPtr;
      //    tsdPtr->firstCSPtr = statePtr;

      //    statePtr->topChanPtr = downChanPtr;
      //    downChanPtr->upChanPtr = (Channel *) NULL;
      //    chanPtr->typePtr = NULL;

      //    Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);
      //    return Tcl_Close(interp, (Tcl_Channel) downChanPtr);
      //}

      // There is only the TOP Channel, so we free the remaining
      // pointers we have and then ourselves.  Since this is the
      // last of the channels in the stack, make sure to free the
      // ChannelState structure associated with it.  We use
      // Tcl_EventuallyFree to allow for any last

      //chanPtr->typePtr = NULL;

      //Tcl_EventuallyFree((ClientData) statePtr, TCL_DYNAMIC);
      //Tcl_EventuallyFree((ClientData) chanPtr, TCL_DYNAMIC);

      return errorCode;
    }
示例#7
0
    /// <summary> WriteChars -> writeChars
    /// 
    /// Convert chars to the channel's external encoding and
    /// write the produced bytes into an output buffer, may queue the
    /// buffer for output if it gets full, and also remembers whether the
    /// current buffer is ready e.g. if it contains a newline and we are in
    /// line buffering mode.
    /// 
    /// The number of bytes written or -1 in case of error. If -1,
    /// Tcl_GetErrno will return the error code.
    /// 
    /// May buffer up output and may cause output to be produced on the
    /// channel.
    /// 
    /// </summary>
    /// <param name="src">         Chars to write.
    /// </param>
    /// <param name="srfOff">      First index in src array.
    /// </param>
    /// <param name="srfLen">      Number of chars to write.
    /// </param>

    internal int writeChars( char[] srcArray, int srcOff, int srcLen )
    {
      //ChannelState *statePtr = chanPtr->state;	// state info for channel
      ChannelBuffer buf;
      char[] stageArray;
      byte[] dstArray;
      int stage, src, dst;
      int saved, savedLF, sawLF, total, dstLen, stageMax;
      int endEncoding, result;
      bool consumedSomething;
      //Tcl_Encoding encoding;
      byte[] safe = new byte[ChannelBuffer.BUFFER_PADDING];
      IntPtr stageLen = new IntPtr( this );
      IntPtr toWrite = new IntPtr( this );
      IntPtr stageRead = new IntPtr( this );
      IntPtr dstWrote = new IntPtr( this );

      total = 0;
      sawLF = 0;
      savedLF = 0;
      saved = 0;
      //encoding = statePtr->encoding;
      src = 0;

      // Write the terminated escape sequence even if srcLen is 0.

      endEncoding = ( encodingEnd ? 0 : 1 );

      // Loop over all characters in src, storing them in staging buffer
      // with proper EOL translation.

      consumedSomething = true;
      while ( consumedSomething && ( srcLen + savedLF + endEncoding > 0 ) )
      {
        consumedSomething = false;
        if ( outputStage == null )
        {
          outputStage = new char[bufSize + 2];
        }
        stageArray = outputStage;
        stage = 0;
        stageMax = bufSize;
        stageLen.i = stageMax;

        toWrite.i = stageLen.i;
        if ( toWrite.i > srcLen )
        {
          toWrite.i = srcLen;
        }

        if ( savedLF != 0 )
        {
          // A '\n' was left over from last call to TranslateOutputEOL()
          // and we need to store it in the staging buffer.  If the
          // channel is line-based, we will need to flush the output
          // buffer (after translating the staging buffer).

          stageArray[stage++] = '\n';
          stageLen.i--;
          sawLF++;
        }
        if ( translateEOL( stageArray, stage, srcArray, src, stageLen, toWrite ) )
        {
          sawLF++;
        }

        stage -= savedLF;
        stageLen.i += savedLF;
        savedLF = 0;

        if ( stageLen.i > stageMax )
        {
          savedLF = 1;
          stageLen.i = stageMax;
        }
        src += toWrite.i;
        srcLen -= toWrite.i;

        // Loop over all characters in staging buffer, converting them
        // to external encoding, storing them in output buffer.

        while ( stageLen.i + saved + endEncoding > 0 )
        {
          buf = curOut;
          if ( buf == null )
          {
            buf = new ChannelBuffer( bufSize );
            curOut = buf;
          }
          // dst = buf.buf + buf.nextAdded;
          dstArray = buf.buf;
          dst = buf.nextAdded;
          dstLen = buf.bufLength - buf.nextAdded;

          if ( saved != 0 )
          {
            // Here's some translated bytes left over from the last
            // buffer that we need to stick at the beginning of this
            // buffer.

            Array.Copy( safe, 0, dstArray, dst, saved );
            buf.nextAdded += saved;
            dst += saved;
            dstLen -= saved;
            saved = 0;
          }

          result = unicodeToExternal( stageArray, stage, stageLen.i, dstArray, dst, dstLen + ChannelBuffer.BUFFER_PADDING, stageRead, dstWrote, null );

          // FIXME: Not clear how this condition is dealt with.
          //
          // Fix for SF #506297, reported by Martin Forssen
          // <*****@*****.**>.
          //
          // The encoding chosen in the script exposing the bug writes out
          // three intro characters when TCL_ENCODING_START is set, but does
          // not consume any input as TCL_ENCODING_END is cleared. As some
          // output was generated the enclosing loop calls UtfToExternal
          // again, again with START set. Three more characters in the out
          // and still no use of input ... To break this infinite loop we
          // remove TCL_ENCODING_START from the set of flags after the first
          // call (no condition is required, the later calls remove an unset
          // flag, which is a no-op). This causes the subsequent calls to
          // UtfToExternal to consume and convert the actual input.

          encodingStart = false;

          // The following can never happen since we use unicode characters.
          //
          //if ((result != 0) && ((stageRead.i + dstWrote.i) == 0)) {
          //    // We have an incomplete UTF-8 character at the end of the
          //    // staging buffer.  It will get moved to the beginning of the
          //    // staging buffer followed by more bytes from src.
          //
          //    src -= stageLen.i;
          //    srcLen += stageLen.i;
          //    stageLen.i = 0;
          //    savedLF = 0;
          //    break;
          //}
          buf.nextAdded += dstWrote.i;
          if ( buf.nextAdded > buf.bufLength )
          {
            // When translating from unicode to external encoding, we
            // allowed the translation to produce a character that
            // crossed the end of the output buffer, so that we would
            // get a completely full buffer before flushing it.  The
            // extra bytes will be moved to the beginning of the next
            // buffer.

            saved = buf.nextAdded - buf.bufLength;
            // ATK Array.Copy(SupportClass.ToByteArray((System.Array) dstArray), dst + dstLen, SupportClass.ToByteArray(safe), 0, saved);
            Array.Copy( dstArray, dst + dstLen, safe, 0, saved );
            buf.nextAdded = buf.bufLength;
          }
          if ( checkFlush( buf, ( sawLF != 0 ) ) != 0 )
          {
            return -1;
          }

          total += dstWrote.i;
          stage += stageRead.i;
          stageLen.i -= stageRead.i;
          sawLF = 0;

          consumedSomething = true;

          // If all translated characters are written to the buffer,
          // endEncoding is set to 0 because the escape sequence may be
          // output.

          if ( ( stageLen.i + saved == 0 ) && ( result == 0 ) )
          {
            endEncoding = 0;
          }
        }
      }

      // If nothing was written and it happened because there was no progress
      // in the UTF conversion, we throw an error.

      if ( !consumedSomething && ( total == 0 ) )
      {
        //Tcl_SetErrno (EINVAL);
        return -1;
      }
      return total;
    }
示例#8
0
    /// <summary> CheckFlush -> checkFlush
    /// 
    /// Helper function for writeBytes() and writeChars().  If the
    /// channel buffer is ready to be flushed, flush it.
    /// 
    /// The return value is -1 if there was a problem flushing the
    /// channel buffer, or 0 otherwise.
    /// 
    /// The buffer will be recycled if it is flushed.
    /// 
    /// </summary>
    /// <param name="buf">         Channel buffer to possibly flush.
    /// </param>
    /// <param name="newlineFlag"> True if a the channel buffer
    /// contains a newline.
    /// </param>

    internal int checkFlush( ChannelBuffer buf, bool newlineFlag )
    {
      // The current buffer is ready for output:
      // 1. if it is full.
      // 2. if it contains a newline and this channel is line-buffered.
      // 3. if it contains any output and this channel is unbuffered.

      if ( !bufferReady )
      {
        if ( buf.nextAdded == buf.bufLength )
        {
          bufferReady = true;
        }
        else if ( buffering == TclIO.BUFF_LINE )
        {
          if ( newlineFlag )
          {
            bufferReady = true;
          }
        }
        else if ( buffering == TclIO.BUFF_NONE )
        {
          bufferReady = true;
        }
      }
      if ( bufferReady )
      {
        if ( flushChannel( null, false ) != 0 )
        {
          return -1;
        }
      }
      return 0;
    }
示例#9
0
    /// <summary> WriteBytes -> writeBytes
    /// 
    /// Write a sequence of bytes into an output buffer, may queue the
    /// buffer for output if it gets full, and also remembers whether the
    /// current buffer is ready e.g. if it contains a newline and we are in
    /// line buffering mode.
    /// 
    /// The number of bytes written or -1 in case of error. If -1,
    /// Tcl_GetErrno will return the error code.
    /// 
    /// May buffer up output and may cause output to be produced on the
    /// channel.
    /// 
    /// </summary>
    /// <param name="src">         Bytes to write.
    /// </param>
    /// <param name="srfOff">      First index in src array.
    /// </param>
    /// <param name="srfLen">      Number of bytes to write.
    /// </param>

    internal int writeBytes( byte[] srcArray, int srcOff, int srcLen )
    {
      ChannelBuffer buf;
      byte[] dstArray;
      int dst, src, dstMax, sawLF, total, savedLF;
      IntPtr dstLen = new IntPtr( this );
      IntPtr toWrite = new IntPtr( this );

      total = 0;
      sawLF = 0;
      savedLF = 0;
      src = srcOff;

      // Loop over all bytes in src, storing them in output buffer with
      // proper EOL translation.

      while ( srcLen + savedLF > 0 )
      {
        buf = curOut;
        if ( buf == null )
        {
          buf = new ChannelBuffer( bufSize );
          curOut = buf;
        }
        //dst = bufPtr->buf + bufPtr->nextAdded;
        dstArray = buf.buf;
        dst = buf.nextAdded;
        dstMax = buf.bufLength - buf.nextAdded;
        dstLen.i = dstMax;

        toWrite.i = dstLen.i;
        if ( toWrite.i > srcLen )
        {
          toWrite.i = srcLen;
        }

        if ( savedLF != 0 )
        {
          // A '\n' was left over from last call to translateEOL()
          // and we need to store it in this buffer.  If the channel is
          // line-based, we will need to flush it.

          dstArray[dst++] = (byte)SupportClass.Identity( '\n' );
          dstLen.i--;
          sawLF++;
        }
        if ( translateEOL( dstArray, dst, srcArray, src, dstLen, toWrite ) )
        {
          sawLF++;
        }
        dstLen.i += savedLF;
        savedLF = 0;

        if ( dstLen.i > dstMax )
        {
          savedLF = 1;
          dstLen.i = dstMax;
        }
        buf.nextAdded += dstLen.i;
        if ( checkFlush( buf, ( sawLF != 0 ) ) != 0 )
        {
          return -1;
        }
        total += dstLen.i;
        src += toWrite.i;
        srcLen -= toWrite.i;
        sawLF = 0;
      }
      return total;
    }
    /// <summary> FilterInputBytes -> filterBytes
    /// 
    /// Helper function for getsObj. Appends Unicode characters
    /// onto the TclObject associated with the GetsState after
    /// converting them from raw bytes encoded in the Channel.
    /// 
    /// Consumes available bytes from channel buffers.  When channel
    /// buffers are exhausted, reads more bytes from channel device into
    /// a new channel buffer.  It is the caller's responsibility to
    /// free the channel buffers that have been exhausted.
    /// 
    /// The return value is -1 if there was an error reading from the
    /// channel, 0 otherwise.
    /// 
    /// FIXME: Doc modification of object's StringBuffer
    /// 
    /// Status object keeps track of how much data from channel buffers
    /// has been consumed and where characters should be stored.
    /// </summary>

    internal int filterBytes( GetsState gs )
    {
      ChannelBuffer buf;
      byte[] raw;
      int rawStart, rawEnd;
      char[] dst;
      int offset, toRead, spaceLeft, result, rawLen, length;
      TclObject obj;
      int ENCODING_LINESIZE = 20; // Lower bound on how many bytes
      // to convert at a time. Since we
      // don't know a priori how many
      // bytes of storage this many
      // source bytes will use, we
      // actually need at least
      // ENCODING_LINESIZE bytes of room.

      bool goto_read = false; // Set to true when jumping to the read
      // label, used to simulate a goto.

      obj = gs.obj;

      // Subtract the number of bytes that were removed from channel buffer
      // during last call.

      buf = gs.buf;
      if ( buf != null )
      {
        buf.nextRemoved += gs.rawRead.i;
        if ( buf.nextRemoved >= buf.nextAdded )
        {
          buf = buf.next;
        }
      }
      gs.totalChars += gs.charsWrote.i;

      while ( true )
      {
        if ( goto_read || ( buf == null ) || ( buf.nextAdded == tcl.lang.ChannelBuffer.BUFFER_PADDING ) )
        {
          // All channel buffers were exhausted and the caller still hasn't
          // seen EOL.  Need to read more bytes from the channel device.
          // Side effect is to allocate another channel buffer.

          //read:
          if ( blocked )
          {
            if ( !blocking )
            {
              gs.charsWrote.i = 0;
              gs.rawRead.i = 0;
              return -1;
            }
            blocked = false;
          }
          if ( Input != 0 )
          {
            gs.charsWrote.i = 0;
            gs.rawRead.i = 0;
            return -1;
          }
          buf = inQueueTail;
          gs.buf = buf;
        }

        // Convert some of the bytes from the channel buffer to characters.
        // Space in obj's string rep is used to hold the characters.

        rawStart = buf.nextRemoved;
        raw = buf.buf;
        rawEnd = buf.nextAdded;
        rawLen = rawEnd - rawStart;

        //dst = *gsPtr->dstPtr;
        //offset = dst - objPtr->bytes;
        toRead = ENCODING_LINESIZE;
        if ( toRead > rawLen )
        {
          toRead = rawLen;
        }
        //dstNeeded = toRead * TCL_UTF_MAX + 1;
        //spaceLeft = objPtr->length - offset - TCL_UTF_MAX - 1;
        //if (dstNeeded > spaceLeft) {
        //    length = offset * 2;
        //    if (offset < dstNeeded) {
        //        length = offset + dstNeeded;
        //    }
        //    length += TCL_UTF_MAX + 1;
        //    Tcl_SetObjLength(objPtr, length);
        //    spaceLeft = length - offset;
        //    dst = objPtr->bytes + offset;
        //    *gsPtr->dstPtr = dst;
        //}
        dst = new char[toRead];
        gs.state = encodingState;
        result = externalToUnicode( raw, rawStart, rawLen, dst, 0, toRead, gs.rawRead, null, gs.charsWrote );
        TclString.append( gs.obj, dst, 0, gs.charsWrote.i );

        // Make sure that if we go through 'gets', that we reset the
        // TCL_ENCODING_START flag still.

        encodingStart = false;

        if ( result == TCL_CONVERT_MULTIBYTE )
        {
          // The last few bytes in this channel buffer were the start of a
          // multibyte sequence.  If this buffer was full, then move them to
          // the next buffer so the bytes will be contiguous.  

          ChannelBuffer next;
          int extra;

          next = buf.next;
          if ( buf.nextAdded < buf.bufLength )
          {
            if ( gs.rawRead.i > 0 )
            {
              // Some raw bytes were converted to UTF-8.  Fall through,
              // returning those UTF-8 characters because a EOL might be
              // present in them.
            }
            else if ( eofCond )
            {
              // There was a partial character followed by EOF on the
              // device.  Fall through, returning that nothing was found.

              buf.nextRemoved = buf.nextAdded;
            }
            else
            {
              // There are no more cached raw bytes left.  See if we can
              // get some more.

              goto_read = true;
              goto read; //goto read;
            }
          }
          else
          {
            if ( next == null )
            {
              next = new ChannelBuffer( bufSize );
              buf.next = next;
              inQueueTail = next;
            }
            extra = rawLen - gs.rawRead.i;
            Array.Copy( raw, gs.rawRead.i, next.buf, tcl.lang.ChannelBuffer.BUFFER_PADDING - extra, extra );
            next.nextRemoved -= extra;
            buf.nextAdded -= extra;
          }
        }

        goto read_brk; // End loop in the normal case

read:
        ;
      }

read_brk:
      ;


      gs.buf = buf;
      return 0;
    }
    /// <summary> DiscardInputQueued -> discardQueued
    /// 
    /// Discards any input read from the channel but not yet consumed
    /// by Tcl reading commands.
    /// </summary>

    private void discardQueued( bool discardSavedBuffers )
    {
      ChannelBuffer buf, nxt;

      buf = inQueueHead;
      inQueueHead = null;
      inQueueTail = null;
      for ( ; buf != null; buf = nxt )
      {
        nxt = buf.next;
        recycleBuffer( buf, discardSavedBuffers );
      }

      // If discardSavedBuffers is true, must also discard any previously
      // saved buffer in the saveInBuf field.

      if ( discardSavedBuffers )
      {
        if ( saveInBuf != null )
        {
          saveInBuf = null;
        }
      }
    }
    /// <summary> DoReadChars -> doReadChars
    /// 
    /// Reads from the channel until the requested number of characters
    /// have been seen, EOF is seen, or the channel would block.  EOL
    /// and EOF translation is done.  If reading binary data, the raw
    /// bytes are wrapped in a Tcl byte array object.  Otherwise, the raw
    /// bytes are converted to characters using the channel's current
    /// encoding and stored in a Tcl string object.
    /// 
    /// </summary>
    /// <param name="obj">Input data is stored in this object.
    /// </param>
    /// <param name="toRead">Maximum number of characters to store,
    /// or -1 to read all available data (up to EOF
    /// or when channel blocks).
    /// </param>

    internal int doReadChars( TclObject obj, int toRead )
    {
      ChannelBuffer buf;
      int copied, copiedNow, result;
      IntPtr offset = new IntPtr( this );

      if ( (System.Object)encoding == null )
      {
        TclByteArray.setLength( null, obj, 0 );
      }
      else
      {
        TclString.empty( obj );
      }
      offset.i = 0;

      // if toRead is negative, read until EOF
      if ( toRead < 0 )
      {
        toRead = System.Int32.MaxValue;
      }

      {
        for ( copied = 0; toRead > 0; )
        {
          copiedNow = -1;
          if ( inQueueHead != null )
          {
            if ( (System.Object)encoding == null )
            {
              System.Diagnostics.Debug.WriteLine( "calling readBytes " + toRead );
              copiedNow = readBytes( obj, toRead, offset );
            }
            else
            {
              System.Diagnostics.Debug.WriteLine( "calling readChars " + toRead );
              copiedNow = readChars( obj, toRead );
            }

            // If the current buffer is empty recycle it.

            buf = inQueueHead;
            System.Diagnostics.Debug.WriteLine( "after read* buf.nextRemoved is " + buf.nextRemoved );
            System.Diagnostics.Debug.WriteLine( "after read* buf.nextAdded is " + buf.nextAdded );

            if ( buf.nextRemoved == buf.nextAdded )
            {
              System.Diagnostics.Debug.WriteLine( "recycling empty buffer" );
              ChannelBuffer next;

              next = buf.next;
              recycleBuffer( buf, false );
              inQueueHead = next;
              if ( next == null )
              {
                System.Diagnostics.Debug.WriteLine( "inQueueTail set to null" );
                inQueueTail = null;
              }
              else
              {
                System.Diagnostics.Debug.WriteLine( "inQueueTail is not null" );
              }
            }
          }
          if ( copiedNow < 0 )
          {
            System.Diagnostics.Debug.WriteLine( "copiedNow < 0" );
            if ( eofCond )
            {
              System.Diagnostics.Debug.WriteLine( "eofCond" );
              break;
            }
            if ( blocked )
            {
              System.Diagnostics.Debug.WriteLine( "blocked" );
              if ( !blocking )
              {
                break;
              }
              blocked = false;
            }
            result = Input;
            if ( result != 0 )
            {
              System.Diagnostics.Debug.WriteLine( "non-zero result" );
              if ( result == TclPosixException.EAGAIN )
              {
                break;
              }
              copied = -1;
              goto done_brk; //goto done
            }
          }
          else
          {
            copied += copiedNow;
            System.Diagnostics.Debug.WriteLine( "copied incremented to " + copied );
            toRead -= copiedNow;
            System.Diagnostics.Debug.WriteLine( "toRead decremented to " + toRead );
          }
        }

        blocked = false;

        if ( (System.Object)encoding == null )
        {
          TclByteArray.setLength( null, obj, offset.i );
          System.Diagnostics.Debug.WriteLine( "set byte array length to " + offset.i );
        }
      }

done_brk:
      ;
      // end done: block

      //done:
      updateInterest();

#if DEBUG
				System.Diagnostics.Debug.WriteLine("returning copied = " + copied);
				
				System.Diagnostics.Debug.WriteLine("returning string \"" + obj + "\"");
				obj.invalidateStringRep();
				
				System.Diagnostics.Debug.WriteLine("returning string \"" + obj + "\"");
#endif

      return copied;
    }
    /// <summary> CommonGetsCleanup -> commonGetsCleanup
    /// 
    /// Helper function used by getsObj to restore the channel after
    /// a "gets" operation.
    /// 
    /// </summary>

    internal void commonGetsCleanup()
    {
      ChannelBuffer buf, next;

      buf = inQueueHead;
      for ( ; buf != null; buf = next )
      {
        next = buf.next;
        if ( buf.nextRemoved < buf.nextAdded )
        {
          break;
        }
        recycleBuffer( buf, false );
      }
      inQueueHead = buf;
      if ( buf == null )
      {
        inQueueTail = null;
      }
      else
      {
        // If any multi-byte characters were split across channel buffer
        // boundaries, the split-up bytes were moved to the next channel
        // buffer by filterBytes().  Move the bytes back to their
        // original buffer because the caller could change the channel's
        // encoding which could change the interpretation of whether those
        // bytes really made up multi-byte characters after all.

        next = buf.next;
        for ( ; next != null; next = buf.next )
        {
          int extra;

          extra = buf.bufLength - buf.nextAdded;
          if ( extra > 0 )
          {
            Array.Copy( next.buf, tcl.lang.ChannelBuffer.BUFFER_PADDING - extra, buf.buf, buf.nextAdded, extra );
            buf.nextAdded += extra;
            next.nextRemoved = tcl.lang.ChannelBuffer.BUFFER_PADDING;
          }
          buf = next;
        }
      }
      if ( (System.Object)encoding != null )
      {
        //Tcl_FreeEncoding(encoding);
      }
    }