summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclIO.c203
-rw-r--r--tests/io.test16
-rw-r--r--tests/zlib.test2
3 files changed, 119 insertions, 102 deletions
diff --git a/generic/tclIO.c b/generic/tclIO.c
index 0ba4098..f76fa63 100644
--- a/generic/tclIO.c
+++ b/generic/tclIO.c
@@ -12,6 +12,7 @@
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
+#undef NDEBUG
#include "tclInt.h"
#include "tclIO.h"
#include <assert.h>
@@ -167,6 +168,7 @@ static void PreserveChannelBuffer(ChannelBuffer *bufPtr);
static void ReleaseChannelBuffer(ChannelBuffer *bufPtr);
static int IsShared(ChannelBuffer *bufPtr);
static void ChannelTimerProc(ClientData clientData);
+static int ChanRead(Channel *chanPtr, char *dst, int dstSize);
static int CheckChannelErrors(ChannelState *statePtr,
int direction);
static int CheckForDeadChannel(Tcl_Interp *interp,
@@ -382,20 +384,74 @@ ChanCloseHalf(
return chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, flags);
}
-static inline int
+/*
+ *---------------------------------------------------------------------------
+ *
+ * ChanRead --
+ *
+ * Read up to dstSize bytes using the inputProc of chanPtr, store
+ * them at dst, and return the number of bytes stored.
+ *
+ * Results:
+ * The return value of the driver inputProc,
+ * - number of bytes stored at dst, ot
+ * - -1 on error, with a Posix error code available to the
+ * caller by calling Tcl_GetErrno().
+ *
+ * Side effects:
+ * The CHANNEL_BLOCKED and CHANNEL_EOF flags of the channel state are
+ * set as appropriate.
+ * On EOF, the inputEncodingFlags are set to perform ending operations
+ * on decoding.
+ * TODO - Is this really the right place for that?
+ *
+ *---------------------------------------------------------------------------
+ */
+static int
ChanRead(
Channel *chanPtr,
char *dst,
- int dstSize,
- int *errnoPtr)
+ int dstSize)
{
+ int bytesRead, result;
+
+ /*
+ * If the caller asked for zero bytes, we'd force the inputProc
+ * to return zero bytes, and then misinterpret that as EOF.
+ */
+ assert(dstSize > 0);
+
if (WillRead(chanPtr) < 0) {
- *errnoPtr = Tcl_GetErrno();
return -1;
}
- return chanPtr->typePtr->inputProc(chanPtr->instanceData, dst, dstSize,
- errnoPtr);
+ bytesRead = chanPtr->typePtr->inputProc(chanPtr->instanceData,
+ dst, dstSize, &result);
+
+ /* Stop any flag leakage through stacked channel levels */
+ ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF);
+ if (bytesRead > 0) {
+ /*
+ * If we get a short read, signal up that we may be BLOCKED.
+ * We should avoid calling the driver because on some
+ * platforms we will block in the low level reading code even
+ * though the channel is set into nonblocking mode.
+ */
+
+ if (bytesRead < dstSize) {
+ SetFlag(chanPtr->state, CHANNEL_BLOCKED);
+ }
+ } else if (bytesRead == 0) {
+ SetFlag(chanPtr->state, CHANNEL_EOF);
+ chanPtr->state->inputEncodingFlags |= TCL_ENCODING_END;
+ } else if (bytesRead < 0) {
+ if ((result == EWOULDBLOCK) || (result == EAGAIN)) {
+ SetFlag(chanPtr->state, CHANNEL_BLOCKED);
+ result = EAGAIN;
+ }
+ Tcl_SetErrno(result);
+ }
+ return bytesRead;
}
static inline Tcl_WideInt
@@ -5357,7 +5413,7 @@ Tcl_ReadRaw(
Channel *chanPtr = (Channel *) chan;
ChannelState *statePtr = chanPtr->state;
/* State info for channel */
- int nread, result, copied, copiedNow;
+ int nread, copied, copiedNow = INT_MAX;
/*
* The check below does too much because it will reject a call to this
@@ -5382,74 +5438,31 @@ Tcl_ReadRaw(
*/
Tcl_Preserve(chanPtr);
- for (copied = 0; copied < bytesToRead; copied += copiedNow) {
- copiedNow = CopyBuffer(chanPtr, bufPtr + copied,
- bytesToRead - copied);
- if (copiedNow == 0) {
- if (GotFlag(statePtr, CHANNEL_EOF)) {
- goto done;
- }
- if (GotFlag(statePtr, CHANNEL_BLOCKED)) {
- if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) {
- goto done;
- }
- ResetFlag(statePtr, CHANNEL_BLOCKED);
- }
-
- /*
- * Now go to the driver to get as much as is possible to
- * fill the remaining request. Do all the error handling by
- * ourselves. The code was stolen from 'GetInput' and
- * slightly adapted (different return value here).
- *
- * The case of 'bytesToRead == 0' at this point cannot
- * happen.
- */
-
- nread = ChanRead(chanPtr, bufPtr + copied,
- bytesToRead - copied, &result);
-
- if (nread > 0) {
- /*
- * If we get a short read, signal up that we may be BLOCKED.
- * We should avoid calling the driver because on some
- * platforms we will block in the low level reading code even
- * though the channel is set into nonblocking mode.
- */
-
- if (nread < (bytesToRead - copied)) {
- SetFlag(statePtr, CHANNEL_BLOCKED);
- }
- } else if (nread == 0) {
- SetFlag(statePtr, CHANNEL_EOF);
- statePtr->inputEncodingFlags |= TCL_ENCODING_END;
-
- } else if (nread < 0) {
- if ((result == EWOULDBLOCK) || (result == EAGAIN)) {
- if (copied > 0) {
- /*
- * Information that was copied earlier has precedence
- * over EAGAIN/WOULDBLOCK handling.
- */
-
- goto done;
- }
+ for (copied = 0; bytesToRead > 0 && copiedNow > 0;
+ bufPtr+=copiedNow, bytesToRead-=copiedNow, copied+=copiedNow) {
+ copiedNow = CopyBuffer(chanPtr, bufPtr, bytesToRead);
+ }
- SetFlag(statePtr, CHANNEL_BLOCKED);
- result = EAGAIN;
- }
+ if (bytesToRead > 0) {
+ /*
+ * Now go to the driver to get as much as is possible to
+ * fill the remaining request. Since we're directly filling
+ * the caller's buffer, retain the blocked flag.
+ */
- Tcl_SetErrno(result);
+ nread = ChanRead(chanPtr, bufPtr, bytesToRead);
+ if (nread < 0) {
+ if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) {
copied = -1;
- goto done;
}
-
+ } else {
copied += nread;
- goto done;
+ }
+ if (copied != 0) {
+ ResetFlag(statePtr, CHANNEL_EOF);
}
}
- done:
Tcl_Release(chanPtr);
return copied;
}
@@ -5625,11 +5638,10 @@ DoReadChars(
Tcl_Preserve(chanPtr);
}
if (result != 0) {
- if (result == EAGAIN) {
- break;
+ if (!GotFlag(statePtr, CHANNEL_BLOCKED)) {
+ copied = -1;
}
- copied = -1;
- goto done;
+ break;
}
} else {
copied += copiedNow;
@@ -5637,14 +5649,15 @@ DoReadChars(
}
}
- ResetFlag(statePtr, CHANNEL_BLOCKED);
-
/*
- * Update the notifier state so we don't block while there is still data
- * in the buffers.
+ * Failure to fill a channel buffer may have left channel reporting
+ * a "blocked" state, but so long as we fulfilled the request here,
+ * the caller does not consider us blocked.
*/
+ if (toRead == 0) {
+ ResetFlag(statePtr, CHANNEL_BLOCKED);
+ }
- done:
/*
* Regenerate the top channel, in case it was changed due to
* self-modifying reflected transforms.
@@ -5654,6 +5667,11 @@ DoReadChars(
chanPtr = statePtr->topChanPtr;
Tcl_Preserve(chanPtr);
}
+
+ /*
+ * Update the notifier state so we don't block while there is still data
+ * in the buffers.
+ */
UpdateInterest(chanPtr);
Tcl_Release(chanPtr);
return copied;
@@ -6532,32 +6550,15 @@ GetInput(
}
PreserveChannelBuffer(bufPtr);
- nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result);
- if (nread > 0) {
- result = 0;
- bufPtr->nextAdded += nread;
-
- /*
- * If we get a short read, signal up that we may be BLOCKED. We should
- * avoid calling the driver because on some platforms we will block in
- * the low level reading code even though the channel is set into
- * nonblocking mode.
- */
+ nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead);
- if (nread < toRead) {
- SetFlag(statePtr, CHANNEL_BLOCKED);
- }
- } else if (nread == 0) {
+ if (nread < 0) {
+ result = Tcl_GetErrno();
+ } else {
result = 0;
- SetFlag(statePtr, CHANNEL_EOF);
- statePtr->inputEncodingFlags |= TCL_ENCODING_END;
- } else if (nread < 0) {
- if ((result == EWOULDBLOCK) || (result == EAGAIN)) {
- SetFlag(statePtr, CHANNEL_BLOCKED);
- result = EAGAIN;
- }
- Tcl_SetErrno(result);
+ bufPtr->nextAdded += nread;
}
+
ReleaseChannelBuffer(bufPtr);
return result;
}
@@ -9221,6 +9222,7 @@ DoRead(
ResetFlag(statePtr, CHANNEL_BLOCKED);
moreData:
+
code = GetInput(chanPtr);
bufPtr = statePtr->inQueueHead;
@@ -9328,6 +9330,9 @@ DoRead(
break;
}
}
+ if (bytesToRead == 0) {
+ ResetFlag(statePtr, CHANNEL_BLOCKED);
+ }
Tcl_Release(chanPtr);
return (int)(p - dst);
diff --git a/tests/io.test b/tests/io.test
index d05b882..8a8775e 100644
--- a/tests/io.test
+++ b/tests/io.test
@@ -6650,11 +6650,23 @@ test io-52.4 {TclCopyChannel} {fcopy} {
fconfigure $f1 -translation lf -blocking 0
fconfigure $f2 -translation cr -blocking 0
fcopy $f1 $f2 -size 40
- set result [list [fconfigure $f1 -blocking] [fconfigure $f2 -blocking]]
+ set result [list [fblocked $f1] [fconfigure $f1 -blocking] [fconfigure $f2 -blocking]]
+ close $f1
+ close $f2
+ lappend result [file size $path(test1)]
+} {0 0 0 40}
+test io-52.4.1 {TclCopyChannel} {fcopy} {
+ file delete $path(test1)
+ set f1 [open $thisScript]
+ set f2 [open $path(test1) w]
+ fconfigure $f1 -translation lf -blocking 0 -buffersize 10000000
+ fconfigure $f2 -translation cr -blocking 0
+ fcopy $f1 $f2 -size 40
+ set result [list [fblocked $f1] [fconfigure $f1 -blocking] [fconfigure $f2 -blocking]]
close $f1
close $f2
lappend result [file size $path(test1)]
-} {0 0 40}
+} {0 0 0 40}
test io-52.5 {TclCopyChannel, all} {fcopy} {
file delete $path(test1)
set f1 [open $thisScript]
diff --git a/tests/zlib.test b/tests/zlib.test
index 4e51ebb..2346ec7 100644
--- a/tests/zlib.test
+++ b/tests/zlib.test
@@ -215,7 +215,7 @@ test zlib-8.5 {transformation and flushing and fileevents: Bug 3525907} -setup {
set ::res
} -cleanup {
catch {close $r}
-} -result {qwertyuiop MIDDLE asdfghjkl}
+} -result {qwertyuiop MIDDLE asdfghjkl {}}
test zlib-8.6 {transformation and fconfigure} -setup {
set file [makeFile {} test.z]
set fd [open $file wb]