Tcl Source Code

Check-in [16bdf667aa]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:[5adc350683] Stop Tcl forcing EOF condition on channels to be permanent. It may be fleeting, and all parts of Tcl channel ecosystem have to deal with that. New assertions and tests to keep us on track.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 16bdf667aaf4504246959924826982cfb1071bfd
User & Date: dgp 2014-11-06 16:34:03
References
2015-04-20
12:52 Ticket [879a0747be] Segfault when reading a specific file status still Open with 4 other changes artifact: 55d533534c user: ferrieux
Context
2014-11-06
17:34
cleanup global namespace litter check-in: 1647ca4c08 user: dgp tags: trunk
16:56
merge trunk check-in: d5dbec3ae3 user: dgp tags: novem
16:34
[5adc350683] Stop Tcl forcing EOF condition on channels to be permanent. It may be fleeting, and all... check-in: 16bdf667aa user: dgp tags: trunk
16:24
[5adc350683] Stop Tcl forcing EOF condition on channels to be permanent. It may be fleeting, and all... check-in: bd09a69e14 user: dgp tags: core-8-5-branch
16:20
merge iogt fixes. Closed-Leaf check-in: 032cde93a3 user: dgp tags: bug-5adc350683-86
2014-11-05
06:36
[214cc0eb22] Restore [lappend $var] return value to the 8.6.1- behavior. If this is going to change,... check-in: f40e585e4b user: dgp tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to generic/tclIO.c.

4388
4389
4390
4391
4392
4393
4394















4395
4396
4397
4398
4399
4400
4401
....
4607
4608
4609
4610
4611
4612
4613

4614
4615
4616
4617
4618
4619
4620
....
4720
4721
4722
4723
4724
4725
4726







4727
4728
4729
4730
4731
4732
4733
....
4742
4743
4744
4745
4746
4747
4748





4749
4750
4751
4752
4753
4754
4755
....
4831
4832
4833
4834
4835
4836
4837











4838
4839
4840
4841
4842
4843
4844
....
4872
4873
4874
4875
4876
4877
4878

4879
4880
4881
4882
4883
4884
4885
....
4981
4982
4983
4984
4985
4986
4987





4988
4989
4990
4991
4992
4993
4994
....
5111
5112
5113
5114
5115
5116
5117











5118
5119
5120
5121
5122
5123
5124
....
5435
5436
5437
5438
5439
5440
5441

5442
5443
5444
5445
5446
5447
5448
....
5466
5467
5468
5469
5470
5471
5472

5473





5474





5475
5476
5477
5478
5479
5480
5481
....
5490
5491
5492
5493
5494
5495
5496
5497
5498
5499
5500
5501
5502
5503
5504
5505
5506
5507
5508
5509
....
5594
5595
5596
5597
5598
5599
5600
5601
5602
5603
5604
5605
5606
5607
5608
5609
5610
5611
5612
5613
5614
5615
5616
5617
5618
5619
5620
....
5630
5631
5632
5633
5634
5635
5636






























5637
5638
5639
5640
5641
5642
5643
....
5706
5707
5708
5709
5710
5711
5712





5713
5714
5715
5716
5717
5718
5719
....
6314
6315
6316
6317
6318
6319
6320
6321
6322
6323
6324
6325
6326
6327
6328
....
6524
6525
6526
6527
6528
6529
6530








6531
6532
6533
6534
6535
6536
6537
6538
6539
6540
6541
6542
6543
6544
6545
6546
6547
6548
6549
6550
6551







6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562

6563
6564
6565
6566
6567
6568
6569
....
6586
6587
6588
6589
6590
6591
6592

6593
6594
6595
6596
6597
6598
6599
....
7103
7104
7105
7106
7107
7108
7109
7110
7111
7112
7113
7114
7115
7116
7117
7118
7119
....
9491
9492
9493
9494
9495
9496
9497






























9498
9499
9500
9501
9502
9503
9504
9505
9506
9507
9508
9509
9510
9511
9512
9513
9514
9515
9516
9517
9518
9519
9520
9521
9522
9523
9524
9525
....
9572
9573
9574
9575
9576
9577
9578
9579
9580
9581
9582
9583
9584
9585
9586
9587
....
9624
9625
9626
9627
9628
9629
9630

9631
9632
9633
9634
9635
9636










9637
9638
9639
9640
9641





9642
9643
9644
9645
9646
9647
9648
    Tcl_Encoding encoding;
    char *dst, *dstEnd, *eol, *eof;
    Tcl_EncodingState oldState;

    if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) {
	return -1;
    }
















    /*
     * A binary version of Tcl_GetsObj. This could also handle encodings that
     * are ascii-7 pure (iso8859, utf-8, ...) with a final encoding conversion
     * done on objPtr.
     */

................................................................................
	     * pointing at the EOF character, but don't store the EOF
	     * character in the output string.
	     */

	    dstEnd = eof;
	    SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	    statePtr->inputEncodingFlags |= TCL_ENCODING_END;

	}
	if (GotFlag(statePtr, CHANNEL_EOF)) {
	    skip = 0;
	    eol = dstEnd;
	    if (eol == objPtr->bytes + oldLength) {
		/*
		 * If we didn't append any bytes before encountering EOF,
................................................................................

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */

  done:







    /*
     * Regenerate the top channel, in case it was changed due to
     * self-modifying reflected transforms.
     */
    if (chanPtr != statePtr->topChanPtr) {
	TclChannelRelease((Tcl_Channel)chanPtr);
	chanPtr = statePtr->topChanPtr;
................................................................................
 *---------------------------------------------------------------------------
 *
 * TclGetsObjBinary --
 *
 *	A variation of Tcl_GetsObj that works directly on the buffers until
 *	end-of-line or end-of-file has been seen. Bytes read from the input
 *	channel return as a ByteArray obj.





 *
 * Results:
 *	Number of characters accumulated in the object or -1 if error,
 *	blocked, or EOF. If -1, use Tcl_GetErrno() to retrieve the POSIX error
 *	code for the error or condition that occurred.
 *
 * Side effects:
................................................................................
	    if (GetInput(chanPtr) != 0) {
		goto restore;
	    }
	    bufPtr = statePtr->inQueueTail;
	    if (bufPtr == NULL) {
		goto restore;
	    }











	}

	dst = (unsigned char *) RemovePoint(bufPtr);
	dstEnd = dst + BytesLeft(bufPtr);

	/*
	 * Remember if EOF char is seen, then look for EOL anyhow, because the
................................................................................
	     * EOF character was seen. On EOF, leave current file position
	     * pointing at the EOF character, but don't store the EOF
	     * character in the output string.
	     */

	    SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	    statePtr->inputEncodingFlags |= TCL_ENCODING_END;

	}
	if (GotFlag(statePtr, CHANNEL_EOF)) {
	    skip = 0;
	    eol = dstEnd;
	    if ((dst == dstEnd) && (byteLen == oldLength)) {
		/*
		 * If we didn't append any bytes before encountering EOF,
................................................................................

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */

  done:





    UpdateInterest(chanPtr);
    TclChannelRelease((Tcl_Channel)chanPtr);
    return copiedTotal;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
	bufPtr = statePtr->inQueueTail;
	gsPtr->bufPtr = bufPtr;
	if (bufPtr == NULL) {
	    gsPtr->charsWrote = 0;
	    gsPtr->rawRead = 0;
	    return -1;
	}











    }

    /*
     * Convert some of the bytes from the channel buffer to UTF-8. Space in
     * objPtr's string rep is used to hold the UTF-8 characters. Grow the
     * string rep if we need more space.
     */
................................................................................
    int bytesToRead)		/* Maximum number of bytes to read. */
{
    Channel *chanPtr = (Channel *) chan;
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */
    int copied = 0;


    if (CheckChannelErrors(statePtr, TCL_READABLE | CHANNEL_RAW_MODE) != 0) {
	return -1;
    }

    /* First read bytes from the push-back buffers. */

    while (chanPtr->inQueueHead && bytesToRead > 0) {
................................................................................
	    if (chanPtr->inQueueHead == NULL) {
		chanPtr->inQueueTail = NULL;
	    }
	    RecycleBuffer(chanPtr->state, bufPtr, 0);
	}
    }


    /* Go to the driver if more data needed. */











    if (bytesToRead > 0) {

	int nread = ChanRead(chanPtr, readBuf, bytesToRead);

	if (nread > 0) {
	    /* Successful read (short is OK) - add to bytes copied */
	    copied += nread;
................................................................................
	     * there is no false report of an EOF.
	     * When !CHANNEL_BLOCKED, the error is real and passes on
	     * to caller.
	     */
	    if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) {
		copied = -1;
	    }
	} else if (copied > 0) {
	    /*
	     * nread == 0.  Driver is at EOF, but if copied>0 bytes
	     * from pushback, then we should not signal it yet.
	     */
	    ResetFlag(statePtr, CHANNEL_EOF);
	}
    }
    return copied;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
				 * will be appended to the object. Otherwise,
				 * the data will replace the existing contents
				 * of the object. */
{
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */
    ChannelBuffer *bufPtr;
    int factor, copied, copiedNow, result;
    Tcl_Encoding encoding;
    int binaryMode;
#define UTF_EXPANSION_FACTOR	1024

    /*
     * This operation should occur at the top of a channel stack.
     */

    chanPtr = statePtr->topChanPtr;
    encoding = statePtr->encoding;
    factor = UTF_EXPANSION_FACTOR;
    TclChannelPreserve((Tcl_Channel)chanPtr);

    binaryMode = (encoding == NULL)
	    && (statePtr->inputTranslation == TCL_TRANSLATE_LF) 
	    && (statePtr->inEofChar == '\0');

    if (appendFlag == 0) {
	if (binaryMode) {
................................................................................
	     * Probably not needed anymore.
	     */

	    TclGetString(objPtr);
	}
    }































    /* Must clear the BLOCKED flag here since we check before reading */
    ResetFlag(statePtr, CHANNEL_BLOCKED);
    for (copied = 0; (unsigned) toRead > 0; ) {
	copiedNow = -1;
	if (statePtr->inQueueHead != NULL) {
	    if (binaryMode) {
		copiedNow = ReadBytes(statePtr, objPtr, toRead);
................................................................................
	TclChannelPreserve((Tcl_Channel)chanPtr);
    }

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */





    UpdateInterest(chanPtr);
    TclChannelRelease((Tcl_Channel)chanPtr);
    return copied;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
	 * EOF character was seen in EOL translated range. Leave current file
	 * position pointing at the EOF character, but don't store the EOF
	 * character in the output string.
	 */

	SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	statePtr->inputEncodingFlags |= TCL_ENCODING_END;
	ResetFlag(statePtr, INPUT_SAW_CR);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_Ungets --
................................................................................
    int toRead;			/* How much to read? */
    int result;			/* Of calling driver. */
    int nread;			/* How much was read from channel? */
    ChannelBuffer *bufPtr;	/* New buffer to add to input queue. */
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */









    /*
     * Prevent reading from a dead channel -- a channel that has been closed
     * but not yet deallocated, which can happen if the exit handler for
     * channel cleanup has run but the channel is still registered in some
     * interpreter.
     */

    if (CheckForDeadChannel(NULL, statePtr)) {
	return EINVAL;
    }

    /* 
     * For a channel at EOF do not bother allocating buffers; there's
     * nothing more to read.  Avoid calling the driver inputproc in
     * case some of them do not react well to additional calls after
     * they've reported an eof state..
     * TODO: Candidate for a can't happen panic.
     */

    if (GotFlag(statePtr, CHANNEL_EOF)) {
	return 0;







    }

    /*
     * First check for more buffers in the pushback area of the topmost
     * channel in the stack and use them. They can be the result of a
     * transformation which went away without reading all the information
     * placed in the area when it was stacked.
     */

    if (chanPtr->inQueueHead != NULL) {


	assert(statePtr->inQueueHead == NULL);

	statePtr->inQueueHead = chanPtr->inQueueHead;
	statePtr->inQueueTail = chanPtr->inQueueTail;
	chanPtr->inQueueHead = NULL;
	chanPtr->inQueueTail = NULL;
	return 0;
................................................................................
	bufPtr = statePtr->saveInBufPtr;
	statePtr->saveInBufPtr = NULL;

	/*
	 * Check the actual buffersize against the requested buffersize.
	 * Saved buffers of the wrong size are squashed. This is done
	 * to honor dynamic changes of the buffersize made by the user.

	 */

	if ((bufPtr != NULL)
		&& (bufPtr->bufLength - BUFFER_PADDING != statePtr->bufSize)) {
	    ReleaseChannelBuffer(bufPtr);
	    bufPtr = NULL;
	}
................................................................................
int
Tcl_Eof(
    Tcl_Channel chan)		/* Does this channel have EOF? */
{
    ChannelState *statePtr = ((Channel *) chan)->state;
				/* State of real channel structure. */

    return (GotFlag(statePtr, CHANNEL_STICKY_EOF) ||
	    (GotFlag(statePtr, CHANNEL_EOF) &&
	    (Tcl_InputBuffered(chan) == 0))) ? 1 : 0;
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_InputBlocked --
 *
................................................................................
    Channel *chanPtr,		/* The channel from which to read. */
    char *dst,			/* Where to store input read. */
    int bytesToRead,		/* Maximum number of bytes to read. */
    int allowShortReads)	/* Allow half-blocking (pipes,sockets) */
{
    ChannelState *statePtr = chanPtr->state;
    char *p = dst;































    TclChannelPreserve((Tcl_Channel)chanPtr);
    while (bytesToRead) {
	/*
	 * Each pass through the loop is intended to process up to 
	 * one channel buffer.
	 */

	int bytesRead, bytesWritten;
	ChannelBuffer *bufPtr = statePtr->inQueueHead;

	/*
	 * When there's no buffered data to read, and we're at EOF,
	 * escape to the caller.
	 */

	if (statePtr->flags & CHANNEL_EOF
		&& (bufPtr == NULL || IsBufferEmpty(bufPtr))) {
	    break;
	}

	/*
	 * Don't read more data if we have what we need. 
	 */

	while (!bufPtr ||			/* We got no buffer!   OR */
		(!IsBufferFull(bufPtr) && 	/* Our buffer has room AND */
		(BytesLeft(bufPtr) < bytesToRead) ) ) {
................................................................................
		break;
	    }

	    /*
	     * 1) We're @EOF because we saw eof char.
	     */

	    if (statePtr->inEofChar
		    && RemovePoint(bufPtr)[0] == statePtr->inEofChar) {
		UpdateInterest(chanPtr);
		break;
	    }

	    /*
	     * 2) The buffer holds a \r while in CRLF translation,
	     *    followed by the end of the buffer.
................................................................................

	if (IsBufferEmpty(bufPtr)) {
	    statePtr->inQueueHead = bufPtr->nextPtr;
	    if (statePtr->inQueueHead == NULL) {
		statePtr->inQueueTail = NULL;
	    }
	    RecycleBuffer(statePtr, bufPtr, 0);

	}

	if ((GotFlag(statePtr, CHANNEL_NONBLOCKING) || allowShortReads)
		&& GotFlag(statePtr, CHANNEL_BLOCKED)) {
	    break;
	}










    }
    if (bytesToRead == 0) {
	ResetFlag(statePtr, CHANNEL_BLOCKED);
    }






    TclChannelRelease((Tcl_Channel)chanPtr);
    return (int)(p - dst);
}
 
/*
 *----------------------------------------------------------------------
 *






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>
>
>







 







>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
|
>
>
>
>
>

>
>
>
>
>







 







|

|
<

<







 







|
|


<
<
<
<
<
<
<
|
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>







 







|







 







>
>
>
>
>
>
>
>











|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
|
<









>







 







>







 







<
|
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











<
<
<
<
<
<
<
<
<
<







 







|
<







 







>






>
>
>
>
>
>
>
>
>
>





>
>
>
>
>







4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
....
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
....
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
....
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
....
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
....
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
....
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
....
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
....
5491
5492
5493
5494
5495
5496
5497
5498
5499
5500
5501
5502
5503
5504
5505
....
5523
5524
5525
5526
5527
5528
5529
5530
5531
5532
5533
5534
5535
5536
5537
5538
5539
5540
5541
5542
5543
5544
5545
5546
5547
5548
5549
....
5558
5559
5560
5561
5562
5563
5564
5565
5566
5567

5568

5569
5570
5571
5572
5573
5574
5575
....
5660
5661
5662
5663
5664
5665
5666
5667
5668
5669
5670







5671

5672
5673
5674
5675
5676
5677
5678
....
5688
5689
5690
5691
5692
5693
5694
5695
5696
5697
5698
5699
5700
5701
5702
5703
5704
5705
5706
5707
5708
5709
5710
5711
5712
5713
5714
5715
5716
5717
5718
5719
5720
5721
5722
5723
5724
5725
5726
5727
5728
5729
5730
5731
....
5794
5795
5796
5797
5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
5812
....
6407
6408
6409
6410
6411
6412
6413
6414
6415
6416
6417
6418
6419
6420
6421
....
6617
6618
6619
6620
6621
6622
6623
6624
6625
6626
6627
6628
6629
6630
6631
6632
6633
6634
6635
6636
6637
6638
6639
6640
6641
6642
6643
6644
6645
6646
6647
6648
6649
6650
6651
6652
6653
6654
6655
6656
6657
6658
6659
6660

6661
6662
6663
6664
6665
6666
6667
6668
6669
6670
6671
6672
6673
6674
6675
6676
6677
....
6694
6695
6696
6697
6698
6699
6700
6701
6702
6703
6704
6705
6706
6707
6708
....
7212
7213
7214
7215
7216
7217
7218

7219

7220
7221
7222
7223
7224
7225
7226
....
9598
9599
9600
9601
9602
9603
9604
9605
9606
9607
9608
9609
9610
9611
9612
9613
9614
9615
9616
9617
9618
9619
9620
9621
9622
9623
9624
9625
9626
9627
9628
9629
9630
9631
9632
9633
9634
9635
9636
9637
9638
9639
9640
9641
9642
9643
9644
9645










9646
9647
9648
9649
9650
9651
9652
....
9699
9700
9701
9702
9703
9704
9705
9706

9707
9708
9709
9710
9711
9712
9713
....
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765
9766
9767
9768
9769
9770
9771
9772
9773
9774
9775
9776
9777
9778
9779
9780
9781
9782
9783
9784
9785
9786
9787
9788
9789
9790
    Tcl_Encoding encoding;
    char *dst, *dstEnd, *eol, *eof;
    Tcl_EncodingState oldState;

    if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) {
	return -1;
    }

    /*
     * If we're sitting ready to read the eofchar, there's no need to
     * do it.
     */

    if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) {
	SetFlag(statePtr, CHANNEL_EOF);
	assert( statePtr->inputEncodingFlags & TCL_ENCODING_END );
	assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) );

	/* TODO: Do we need this? */
	UpdateInterest(chanPtr);
	return -1;
    }

    /*
     * A binary version of Tcl_GetsObj. This could also handle encodings that
     * are ascii-7 pure (iso8859, utf-8, ...) with a final encoding conversion
     * done on objPtr.
     */

................................................................................
	     * pointing at the EOF character, but don't store the EOF
	     * character in the output string.
	     */

	    dstEnd = eof;
	    SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	    statePtr->inputEncodingFlags |= TCL_ENCODING_END;
	    ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR);
	}
	if (GotFlag(statePtr, CHANNEL_EOF)) {
	    skip = 0;
	    eol = dstEnd;
	    if (eol == objPtr->bytes + oldLength) {
		/*
		 * If we didn't append any bytes before encountering EOF,
................................................................................

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */

  done:
	assert(!GotFlag(statePtr, CHANNEL_EOF)
		|| GotFlag(statePtr, CHANNEL_STICKY_EOF)
		|| Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0);

	assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)
		== (CHANNEL_EOF|CHANNEL_BLOCKED)) );

    /*
     * Regenerate the top channel, in case it was changed due to
     * self-modifying reflected transforms.
     */
    if (chanPtr != statePtr->topChanPtr) {
	TclChannelRelease((Tcl_Channel)chanPtr);
	chanPtr = statePtr->topChanPtr;
................................................................................
 *---------------------------------------------------------------------------
 *
 * TclGetsObjBinary --
 *
 *	A variation of Tcl_GetsObj that works directly on the buffers until
 *	end-of-line or end-of-file has been seen. Bytes read from the input
 *	channel return as a ByteArray obj.
 *
 *	WARNING!  The notion of "binary" used here is different from
 *	notions of "binary" used in other places.  In particular, this
 *	"binary" routine may be called when an -eofchar is set on the
 * 	channel.
 *
 * Results:
 *	Number of characters accumulated in the object or -1 if error,
 *	blocked, or EOF. If -1, use Tcl_GetErrno() to retrieve the POSIX error
 *	code for the error or condition that occurred.
 *
 * Side effects:
................................................................................
	    if (GetInput(chanPtr) != 0) {
		goto restore;
	    }
	    bufPtr = statePtr->inQueueTail;
	    if (bufPtr == NULL) {
		goto restore;
	    }
	} else {
	    /*
	     * Incoming CHANNEL_STICKY_EOF is filtered out on entry.
	     * A new CHANNEL_STICKY_EOF set in this routine leads to
	     * return before coming back here.  When we are not dealing
	     * with CHANNEL_STICKY_EOF, a CHANNEL_EOF implies an
	     * empty buffer.  Here the buffer is non-empty so we know
	     * we're a non-EOF */

	    assert ( !GotFlag(statePtr, CHANNEL_STICKY_EOF) );
	    assert ( !GotFlag(statePtr, CHANNEL_EOF) );
	}

	dst = (unsigned char *) RemovePoint(bufPtr);
	dstEnd = dst + BytesLeft(bufPtr);

	/*
	 * Remember if EOF char is seen, then look for EOL anyhow, because the
................................................................................
	     * EOF character was seen. On EOF, leave current file position
	     * pointing at the EOF character, but don't store the EOF
	     * character in the output string.
	     */

	    SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	    statePtr->inputEncodingFlags |= TCL_ENCODING_END;
	    ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR);
	}
	if (GotFlag(statePtr, CHANNEL_EOF)) {
	    skip = 0;
	    eol = dstEnd;
	    if ((dst == dstEnd) && (byteLen == oldLength)) {
		/*
		 * If we didn't append any bytes before encountering EOF,
................................................................................

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */

  done:
	assert(!GotFlag(statePtr, CHANNEL_EOF)
		|| GotFlag(statePtr, CHANNEL_STICKY_EOF)
		|| Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0);
	assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)
		== (CHANNEL_EOF|CHANNEL_BLOCKED)) );
    UpdateInterest(chanPtr);
    TclChannelRelease((Tcl_Channel)chanPtr);
    return copiedTotal;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
	bufPtr = statePtr->inQueueTail;
	gsPtr->bufPtr = bufPtr;
	if (bufPtr == NULL) {
	    gsPtr->charsWrote = 0;
	    gsPtr->rawRead = 0;
	    return -1;
	}
    } else {
	/*
	 * Incoming CHANNEL_STICKY_EOF is filtered out on entry.
	 * A new CHANNEL_STICKY_EOF set in this routine leads to
	 * return before coming back here.  When we are not dealing
	 * with CHANNEL_STICKY_EOF, a CHANNEL_EOF implies an
	 * empty buffer.  Here the buffer is non-empty so we know
	 * we're a non-EOF */

	assert ( !GotFlag(statePtr, CHANNEL_STICKY_EOF) );
	assert ( !GotFlag(statePtr, CHANNEL_EOF) );
    }

    /*
     * Convert some of the bytes from the channel buffer to UTF-8. Space in
     * objPtr's string rep is used to hold the UTF-8 characters. Grow the
     * string rep if we need more space.
     */
................................................................................
    int bytesToRead)		/* Maximum number of bytes to read. */
{
    Channel *chanPtr = (Channel *) chan;
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */
    int copied = 0;

    assert(bytesToRead > 0);
    if (CheckChannelErrors(statePtr, TCL_READABLE | CHANNEL_RAW_MODE) != 0) {
	return -1;
    }

    /* First read bytes from the push-back buffers. */

    while (chanPtr->inQueueHead && bytesToRead > 0) {
................................................................................
	    if (chanPtr->inQueueHead == NULL) {
		chanPtr->inQueueTail = NULL;
	    }
	    RecycleBuffer(chanPtr->state, bufPtr, 0);
	}
    }

    /*
     * Go to the driver only if we got nothing from pushback.
     * Have to do it this way to avoid EOF mis-timings when we
     * consider the ability that EOF may not be a permanent
     * condition in the driver, and in that case we have to
     * synchronize.
     */

    if (copied) {
	return copied;
    }

    /* This test not needed. */
    if (bytesToRead > 0) {

	int nread = ChanRead(chanPtr, readBuf, bytesToRead);

	if (nread > 0) {
	    /* Successful read (short is OK) - add to bytes copied */
	    copied += nread;
................................................................................
	     * there is no false report of an EOF.
	     * When !CHANNEL_BLOCKED, the error is real and passes on
	     * to caller.
	     */
	    if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) {
		copied = -1;
	    }
	} else {
	    /*
	     * nread == 0.  Driver is at EOF. Let that state filter up.

	     */

	}
    }
    return copied;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
				 * will be appended to the object. Otherwise,
				 * the data will replace the existing contents
				 * of the object. */
{
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */
    ChannelBuffer *bufPtr;
    int copied, copiedNow, result;
    Tcl_Encoding encoding = statePtr->encoding;
    int binaryMode;
#define UTF_EXPANSION_FACTOR	1024







    int factor = UTF_EXPANSION_FACTOR;


    binaryMode = (encoding == NULL)
	    && (statePtr->inputTranslation == TCL_TRANSLATE_LF) 
	    && (statePtr->inEofChar == '\0');

    if (appendFlag == 0) {
	if (binaryMode) {
................................................................................
	     * Probably not needed anymore.
	     */

	    TclGetString(objPtr);
	}
    }

    /*
     * Early out when next read will see eofchar.
     *
     * NOTE: See DoRead for argument that it's a bug (one we're keeping)
     * to have this escape before the one for zero-char read request.
     */

    if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) {
	SetFlag(statePtr, CHANNEL_EOF);
	assert( statePtr->inputEncodingFlags & TCL_ENCODING_END );
	assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) );

	UpdateInterest(chanPtr);
	return 0;
    }

    /* Special handling for zero-char read request. */
    if (toRead == 0) {
	ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF);
	UpdateInterest(chanPtr);
	return 0;
    }

    /*
     * This operation should occur at the top of a channel stack.
     */

    chanPtr = statePtr->topChanPtr;
    TclChannelPreserve((Tcl_Channel)chanPtr);

    /* Must clear the BLOCKED flag here since we check before reading */
    ResetFlag(statePtr, CHANNEL_BLOCKED);
    for (copied = 0; (unsigned) toRead > 0; ) {
	copiedNow = -1;
	if (statePtr->inQueueHead != NULL) {
	    if (binaryMode) {
		copiedNow = ReadBytes(statePtr, objPtr, toRead);
................................................................................
	TclChannelPreserve((Tcl_Channel)chanPtr);
    }

    /*
     * Update the notifier state so we don't block while there is still data
     * in the buffers.
     */
	assert(!GotFlag(statePtr, CHANNEL_EOF)
		|| GotFlag(statePtr, CHANNEL_STICKY_EOF)
		|| Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0);
	assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)
		== (CHANNEL_EOF|CHANNEL_BLOCKED)) );
    UpdateInterest(chanPtr);
    TclChannelRelease((Tcl_Channel)chanPtr);
    return copied;
}
 
/*
 *---------------------------------------------------------------------------
................................................................................
	 * EOF character was seen in EOL translated range. Leave current file
	 * position pointing at the EOF character, but don't store the EOF
	 * character in the output string.
	 */

	SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF);
	statePtr->inputEncodingFlags |= TCL_ENCODING_END;
	ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR);
    }
}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_Ungets --
................................................................................
    int toRead;			/* How much to read? */
    int result;			/* Of calling driver. */
    int nread;			/* How much was read from channel? */
    ChannelBuffer *bufPtr;	/* New buffer to add to input queue. */
    ChannelState *statePtr = chanPtr->state;
				/* State info for channel */

    /* 
     * Verify that all callers know better than to call us when
     * it's recorded that the next char waiting to be read is the
     * eofchar.
     */

    assert( !GotFlag(statePtr, CHANNEL_STICKY_EOF) );

    /*
     * Prevent reading from a dead channel -- a channel that has been closed
     * but not yet deallocated, which can happen if the exit handler for
     * channel cleanup has run but the channel is still registered in some
     * interpreter.
     */

    if (CheckForDeadChannel(NULL, statePtr)) {
	return EINVAL;
    }

    /*
     * WARNING: There was once a comment here claiming that it was
     * a bad idea to make another call to the inputproc of a channel
     * driver when EOF has already been detected on the channel.  Through
     * much of Tcl's history, this warning was then completely negated
     * by having all (most?) read paths clear the EOF setting before
     * reaching here.  So we had a guard that was never triggered.
     *
     * Don't be tempted to restore the guard.  Even if EOF is set on
     * the channel, continue through and call the inputproc again.  This
     * is the way to enable the ability to [read] again beyond the EOF,
     * which seems a strange thing to do, but for which use cases exist
     * [Tcl Bug 5adc350683] and which may even be essential for channels
     * representing things like ttys or other devices where the stream
     * might take the logical form of a series of 'files' separated by
     * an EOF condition.
     */


    /*
     * First check for more buffers in the pushback area of the topmost
     * channel in the stack and use them. They can be the result of a
     * transformation which went away without reading all the information
     * placed in the area when it was stacked.
     */

    if (chanPtr->inQueueHead != NULL) {

	/* TODO: Tests to cover this. */
	assert(statePtr->inQueueHead == NULL);

	statePtr->inQueueHead = chanPtr->inQueueHead;
	statePtr->inQueueTail = chanPtr->inQueueTail;
	chanPtr->inQueueHead = NULL;
	chanPtr->inQueueTail = NULL;
	return 0;
................................................................................
	bufPtr = statePtr->saveInBufPtr;
	statePtr->saveInBufPtr = NULL;

	/*
	 * Check the actual buffersize against the requested buffersize.
	 * Saved buffers of the wrong size are squashed. This is done
	 * to honor dynamic changes of the buffersize made by the user.
	 * TODO: Tests to cover this.
	 */

	if ((bufPtr != NULL)
		&& (bufPtr->bufLength - BUFFER_PADDING != statePtr->bufSize)) {
	    ReleaseChannelBuffer(bufPtr);
	    bufPtr = NULL;
	}
................................................................................
int
Tcl_Eof(
    Tcl_Channel chan)		/* Does this channel have EOF? */
{
    ChannelState *statePtr = ((Channel *) chan)->state;
				/* State of real channel structure. */


    return GotFlag(statePtr, CHANNEL_EOF) ? 1 : 0;

}
 
/*
 *----------------------------------------------------------------------
 *
 * Tcl_InputBlocked --
 *
................................................................................
    Channel *chanPtr,		/* The channel from which to read. */
    char *dst,			/* Where to store input read. */
    int bytesToRead,		/* Maximum number of bytes to read. */
    int allowShortReads)	/* Allow half-blocking (pipes,sockets) */
{
    ChannelState *statePtr = chanPtr->state;
    char *p = dst;

    assert (bytesToRead >= 0);

    /*
     * Early out when we know a read will get the eofchar.
     *
     * NOTE: This seems to be a bug.  The special handling for
     * a zero-char read request ought to come first.  As coded
     * the EOF due to eofchar has distinguishing behavior from
     * the EOF due to reported EOF on the underlying device, and
     * that seems undesirable.  However recent history indicates
     * that new inconsistent behavior in a patchlevel has problems
     * too.  Keep on keeping on for now.
     */

    if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) {
	SetFlag(statePtr, CHANNEL_EOF);
	assert( statePtr->inputEncodingFlags & TCL_ENCODING_END );
	assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) );

	UpdateInterest(chanPtr);
	return 0;
    }

    /* Special handling for zero-char read request. */
    if (bytesToRead == 0) {
	ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF);
	UpdateInterest(chanPtr);
	return 0;
    }

    TclChannelPreserve((Tcl_Channel)chanPtr);
    while (bytesToRead) {
	/*
	 * Each pass through the loop is intended to process up to 
	 * one channel buffer.
	 */

	int bytesRead, bytesWritten;
	ChannelBuffer *bufPtr = statePtr->inQueueHead;











	/*
	 * Don't read more data if we have what we need. 
	 */

	while (!bufPtr ||			/* We got no buffer!   OR */
		(!IsBufferFull(bufPtr) && 	/* Our buffer has room AND */
		(BytesLeft(bufPtr) < bytesToRead) ) ) {
................................................................................
		break;
	    }

	    /*
	     * 1) We're @EOF because we saw eof char.
	     */

	    if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) {

		UpdateInterest(chanPtr);
		break;
	    }

	    /*
	     * 2) The buffer holds a \r while in CRLF translation,
	     *    followed by the end of the buffer.
................................................................................

	if (IsBufferEmpty(bufPtr)) {
	    statePtr->inQueueHead = bufPtr->nextPtr;
	    if (statePtr->inQueueHead == NULL) {
		statePtr->inQueueTail = NULL;
	    }
	    RecycleBuffer(statePtr, bufPtr, 0);
	    bufPtr = statePtr->inQueueHead;
	}

	if ((GotFlag(statePtr, CHANNEL_NONBLOCKING) || allowShortReads)
		&& GotFlag(statePtr, CHANNEL_BLOCKED)) {
	    break;
	}

	/*
	 * When there's no buffered data to read, and we're at EOF,
	 * escape to the caller.
	 */

	if (GotFlag(statePtr, CHANNEL_EOF)
		&& (bufPtr == NULL || IsBufferEmpty(bufPtr))) {
	    break;
	}
    }
    if (bytesToRead == 0) {
	ResetFlag(statePtr, CHANNEL_BLOCKED);
    }

	assert(!GotFlag(statePtr, CHANNEL_EOF)
		|| GotFlag(statePtr, CHANNEL_STICKY_EOF)
		|| Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0);
	assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)
		== (CHANNEL_EOF|CHANNEL_BLOCKED)) );
    TclChannelRelease((Tcl_Channel)chanPtr);
    return (int)(p - dst);
}
 
/*
 *----------------------------------------------------------------------
 *

Changes to generic/tclIOGT.c.

183
184
185
186
187
188
189

190
191
192
193
194
195
196
...
288
289
290
291
292
293
294

295
296
297
298
299
300
301
...
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
...
672
673
674
675
676
677
678











679
680
681
682
683
684
685
...
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
...
742
743
744
745
746
747
748
749
750




751
752
753
754
755
756
757
...
854
855
856
857
858
859
860

861
862
863
864
865
866
867
...
927
928
929
930
931
932
933

934
935
936
937
938
939
940
     * General section. Data to integrate the transformation into the channel
     * system.
     */

    Tcl_Channel self;		/* Our own Channel handle. */
    int readIsFlushed;		/* Flag to note whether in.flushProc was
				 * called or not. */

    int flags;			/* Currently CHANNEL_ASYNC or zero. */
    int watchMask;		/* Current watch/event/interest mask. */
    int mode;			/* Mode of parent channel, OR'ed combination
				 * of TCL_READABLE, TCL_WRITABLE. */
    Tcl_TimerToken timer;	/* Timer for automatic flushing of information
				 * sitting in an internal buffer. Required for
				 * full fileevent support. */
................................................................................

    dataPtr = ckalloc(sizeof(TransformChannelData));

    dataPtr->refCount = 1;
    Tcl_DStringInit(&ds);
    Tcl_GetChannelOption(interp, chan, "-blocking", &ds);
    dataPtr->readIsFlushed = 0;

    dataPtr->flags = 0;
    if (ds.string[0] == '0') {
	dataPtr->flags |= CHANNEL_ASYNC;
    }
    Tcl_DStringFree(&ds);

    dataPtr->watchMask = 0;
................................................................................

    /*
     * Should assert(dataPtr->mode & TCL_READABLE);
     */

    if (toRead == 0 || dataPtr->self == NULL) {
	/*
	 * Catch a no-op.
	 */
	return 0;
    }

    gotBytes = 0;
    downChan = Tcl_GetStackedChannel(dataPtr->self);

................................................................................
	    if (dataPtr->maxRead < toRead) {
		toRead = dataPtr->maxRead;
	    }
	} /* else: 'maxRead < 0' == Accept the current value of toRead. */
	if (toRead <= 0) {
	    break;
	}












	/*
	 * Get bytes from the underlying channel.
	 */

	read = Tcl_ReadRaw(downChan, buf, toRead);
	if (read < 0) {
................................................................................
	} else if (read == 0) {

	    /*
	     * Zero returned from Tcl_ReadRaw() always indicates EOF
	     * on the down channel.
	     */

	    if (dataPtr->readIsFlushed) {
		/*
		 * Already flushed, nothing to do anymore.
		 */

		break;
	    }

	    dataPtr->readIsFlushed = 1;
	    ExecuteCallback(dataPtr, NULL, A_FLUSH_READ, NULL, 0,
		    TRANSMIT_IBUF, P_PRESERVE);

	    if (ResultEmpty(&dataPtr->result)) {
		/*
		 * We had nothing to flush.
................................................................................
	if (ExecuteCallback(dataPtr, NULL, A_READ, UCHARP(buf), read,
		TRANSMIT_IBUF, P_PRESERVE) != TCL_OK) {
	    *errorCodePtr = EINVAL;
	    gotBytes = -1;
	    break;
	}
    } /* while toRead > 0 */
    ReleaseData(dataPtr);





    return gotBytes;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TransformOutputProc --
................................................................................
    }

    if (dataPtr->mode & TCL_READABLE) {
	ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT,
		P_NO_PRESERVE);
	ResultClear(&dataPtr->result);
	dataPtr->readIsFlushed = 0;

    }
    ReleaseData(dataPtr);

    return parentSeekProc(Tcl_GetChannelInstanceData(parent), offset, mode,
	    errorCodePtr);
}
 
................................................................................
    }

    if (dataPtr->mode & TCL_READABLE) {
	ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT,
		P_NO_PRESERVE);
	ResultClear(&dataPtr->result);
	dataPtr->readIsFlushed = 0;

    }
    ReleaseData(dataPtr);

    /*
     * If we have a wide seek capability, we should stick with that.
     */







>







 







>







 







|







 







>
>
>
>
>
>
>
>
>
>
>







 







|
<
<
<
<
<
<
<







 







<

>
>
>
>







 







>







 







>







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
...
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
...
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
...
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
...
720
721
722
723
724
725
726
727







728
729
730
731
732
733
734
...
748
749
750
751
752
753
754

755
756
757
758
759
760
761
762
763
764
765
766
...
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
...
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
     * General section. Data to integrate the transformation into the channel
     * system.
     */

    Tcl_Channel self;		/* Our own Channel handle. */
    int readIsFlushed;		/* Flag to note whether in.flushProc was
				 * called or not. */
    int eofPending;		/* Flag: EOF seen down, not raised up */
    int flags;			/* Currently CHANNEL_ASYNC or zero. */
    int watchMask;		/* Current watch/event/interest mask. */
    int mode;			/* Mode of parent channel, OR'ed combination
				 * of TCL_READABLE, TCL_WRITABLE. */
    Tcl_TimerToken timer;	/* Timer for automatic flushing of information
				 * sitting in an internal buffer. Required for
				 * full fileevent support. */
................................................................................

    dataPtr = ckalloc(sizeof(TransformChannelData));

    dataPtr->refCount = 1;
    Tcl_DStringInit(&ds);
    Tcl_GetChannelOption(interp, chan, "-blocking", &ds);
    dataPtr->readIsFlushed = 0;
    dataPtr->eofPending = 0;
    dataPtr->flags = 0;
    if (ds.string[0] == '0') {
	dataPtr->flags |= CHANNEL_ASYNC;
    }
    Tcl_DStringFree(&ds);

    dataPtr->watchMask = 0;
................................................................................

    /*
     * Should assert(dataPtr->mode & TCL_READABLE);
     */

    if (toRead == 0 || dataPtr->self == NULL) {
	/*
	 * Catch a no-op. TODO: Is this a panic()?
	 */
	return 0;
    }

    gotBytes = 0;
    downChan = Tcl_GetStackedChannel(dataPtr->self);

................................................................................
	    if (dataPtr->maxRead < toRead) {
		toRead = dataPtr->maxRead;
	    }
	} /* else: 'maxRead < 0' == Accept the current value of toRead. */
	if (toRead <= 0) {
	    break;
	}
	if (dataPtr->eofPending) {
	    /*
	     * Already saw EOF from downChan; don't ask again.
	     * NOTE: Could move this up to avoid the last maxRead
	     * execution.  Believe this would still be correct behavior,
	     * but the test suite tests the whole command callback 
	     * sequence, so leave it unchanged for now.
	     */

	    break;
	}

	/*
	 * Get bytes from the underlying channel.
	 */

	read = Tcl_ReadRaw(downChan, buf, toRead);
	if (read < 0) {
................................................................................
	} else if (read == 0) {

	    /*
	     * Zero returned from Tcl_ReadRaw() always indicates EOF
	     * on the down channel.
	     */

	    dataPtr->eofPending = 1;







	    dataPtr->readIsFlushed = 1;
	    ExecuteCallback(dataPtr, NULL, A_FLUSH_READ, NULL, 0,
		    TRANSMIT_IBUF, P_PRESERVE);

	    if (ResultEmpty(&dataPtr->result)) {
		/*
		 * We had nothing to flush.
................................................................................
	if (ExecuteCallback(dataPtr, NULL, A_READ, UCHARP(buf), read,
		TRANSMIT_IBUF, P_PRESERVE) != TCL_OK) {
	    *errorCodePtr = EINVAL;
	    gotBytes = -1;
	    break;
	}
    } /* while toRead > 0 */


    if (gotBytes == 0) {
	dataPtr->eofPending = 0;
    }
    ReleaseData(dataPtr);
    return gotBytes;
}
 
/*
 *----------------------------------------------------------------------
 *
 * TransformOutputProc --
................................................................................
    }

    if (dataPtr->mode & TCL_READABLE) {
	ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT,
		P_NO_PRESERVE);
	ResultClear(&dataPtr->result);
	dataPtr->readIsFlushed = 0;
	dataPtr->eofPending = 0;
    }
    ReleaseData(dataPtr);

    return parentSeekProc(Tcl_GetChannelInstanceData(parent), offset, mode,
	    errorCodePtr);
}
 
................................................................................
    }

    if (dataPtr->mode & TCL_READABLE) {
	ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT,
		P_NO_PRESERVE);
	ResultClear(&dataPtr->result);
	dataPtr->readIsFlushed = 0;
	dataPtr->eofPending = 0;
    }
    ReleaseData(dataPtr);

    /*
     * If we have a wide seek capability, we should stick with that.
     */

Changes to generic/tclIORTrans.c.

157
158
159
160
161
162
163

164
165
166
167
168
169
170
....
1078
1079
1080
1081
1082
1083
1084




1085
1086
1087
1088
1089
1090
1091
....
1092
1093
1094
1095
1096
1097
1098





1099
1100
1101
1102
1103
1104
1105
....
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
....
1207
1208
1209
1210
1211
1212
1213



1214
1215
1216
1217
1218
1219
1220
....
1762
1763
1764
1765
1766
1767
1768

1769
1770
1771
1772
1773
1774
1775
....
3314
3315
3316
3317
3318
3319
3320

3321
3322
3323
3324
3325
3326
3327
     * NOTE (9): Should we have predefined shared literals for the method
     * names?
     */

    int mode;			/* Mask of R/W mode */
    int nonblocking;		/* Flag: Channel is blocking or not. */
    int readIsDrained;		/* Flag: Read buffers are flushed. */

    int dead;			/* Boolean signal that some operations
				 * should no longer be attempted. */
    ResultBuffer result;
} ReflectedTransform;

/*
 * Structure of the table mapping from transform handles to reflected
................................................................................

    Tcl_Preserve(rtPtr);

    /* TODO: Consider a more appropriate buffer size. */
    bufObj = Tcl_NewByteArrayObj(NULL, toRead);
    Tcl_IncrRefCount(bufObj);
    gotBytes = 0;




    while (toRead > 0) {
	/*
	 * Loop until the request is satisfied (or no data available from
	 * below, possibly EOF).
	 */

	copied = ResultCopy(&rtPtr->result, UCHARP(buf), toRead);
................................................................................
	toRead -= copied;
	buf += copied;
	gotBytes += copied;

	if (toRead == 0) {
	    goto stop;
	}






	/*
	 * The buffer is exhausted, but the caller wants even more. We now
	 * have to go to the underlying channel, get more bytes and then
	 * transform them for delivery. We may not get what we want (full EOF
	 * or temporarily out of data).
	 *
................................................................................

	if (readBytes == 0) {

	    /*
	     * Zero returned from Tcl_ReadRaw() always indicates EOF
	     * on the down channel.
	     */
	
		if (rtPtr->readIsDrained) {
		    goto stop;
		}

		/*
		 * Now this is a bit different. The partial data waiting is
		 * converted and returned.
		 */

		if (HAS(rtPtr->methods, METH_DRAIN)) {
		    if (!TransformDrain(rtPtr, errorCodePtr)) {
................................................................................
	    bufObj = Tcl_NewObj();
	    Tcl_IncrRefCount(bufObj);
	}
	Tcl_SetByteArrayLength(bufObj, 0);
    } /* while toRead > 0 */

 stop:



    Tcl_DecrRefCount(bufObj);
    Tcl_Release(rtPtr);
    return gotBytes;

 error:
    gotBytes = -1;
    goto stop;
................................................................................
    rtPtr->parent = parentChan;
    rtPtr->interp = interp;
    rtPtr->handle = handleObj;
    Tcl_IncrRefCount(handleObj);
    rtPtr->timer = NULL;
    rtPtr->mode = 0;
    rtPtr->readIsDrained = 0;

    rtPtr->nonblocking =
	    (((Channel *) parentChan)->state->flags & CHANNEL_NONBLOCKING);
    rtPtr->dead = 0;

    /*
     * Query parent for current blocking mode.
     */
................................................................................

    /* ASSERT: rtPtr->method & FLAG(METH_READ) */
    /* ASSERT: rtPtr->mode & TCL_READABLE */

    (void) InvokeTclMethod(rtPtr, "clear", NULL, NULL, NULL);

    rtPtr->readIsDrained = 0;

    ResultClear(&rtPtr->result);
}
 
static int
TransformLimit(
    ReflectedTransform *rtPtr,
    int *errorCodePtr,






>







 







>
>
>
>







 







>
>
>
>
>







 







|
|
<
|
<







 







>
>
>







 







>







 







>







157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
....
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
....
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
....
1171
1172
1173
1174
1175
1176
1177
1178
1179

1180

1181
1182
1183
1184
1185
1186
1187
....
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
....
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
....
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
     * NOTE (9): Should we have predefined shared literals for the method
     * names?
     */

    int mode;			/* Mask of R/W mode */
    int nonblocking;		/* Flag: Channel is blocking or not. */
    int readIsDrained;		/* Flag: Read buffers are flushed. */
    int eofPending;		/* Flag: EOF seen down, but not raised up */
    int dead;			/* Boolean signal that some operations
				 * should no longer be attempted. */
    ResultBuffer result;
} ReflectedTransform;

/*
 * Structure of the table mapping from transform handles to reflected
................................................................................

    Tcl_Preserve(rtPtr);

    /* TODO: Consider a more appropriate buffer size. */
    bufObj = Tcl_NewByteArrayObj(NULL, toRead);
    Tcl_IncrRefCount(bufObj);
    gotBytes = 0;
    if (rtPtr->eofPending) {
	goto stop;
    }
    rtPtr->readIsDrained = 0;
    while (toRead > 0) {
	/*
	 * Loop until the request is satisfied (or no data available from
	 * below, possibly EOF).
	 */

	copied = ResultCopy(&rtPtr->result, UCHARP(buf), toRead);
................................................................................
	toRead -= copied;
	buf += copied;
	gotBytes += copied;

	if (toRead == 0) {
	    goto stop;
	}

	if (rtPtr->eofPending) {
	    goto stop;
	}


	/*
	 * The buffer is exhausted, but the caller wants even more. We now
	 * have to go to the underlying channel, get more bytes and then
	 * transform them for delivery. We may not get what we want (full EOF
	 * or temporarily out of data).
	 *
................................................................................

	if (readBytes == 0) {

	    /*
	     * Zero returned from Tcl_ReadRaw() always indicates EOF
	     * on the down channel.
	     */

	    rtPtr->eofPending = 1;

	

		/*
		 * Now this is a bit different. The partial data waiting is
		 * converted and returned.
		 */

		if (HAS(rtPtr->methods, METH_DRAIN)) {
		    if (!TransformDrain(rtPtr, errorCodePtr)) {
................................................................................
	    bufObj = Tcl_NewObj();
	    Tcl_IncrRefCount(bufObj);
	}
	Tcl_SetByteArrayLength(bufObj, 0);
    } /* while toRead > 0 */

 stop:
    if (gotBytes == 0) {
	rtPtr->eofPending = 0;
    }
    Tcl_DecrRefCount(bufObj);
    Tcl_Release(rtPtr);
    return gotBytes;

 error:
    gotBytes = -1;
    goto stop;
................................................................................
    rtPtr->parent = parentChan;
    rtPtr->interp = interp;
    rtPtr->handle = handleObj;
    Tcl_IncrRefCount(handleObj);
    rtPtr->timer = NULL;
    rtPtr->mode = 0;
    rtPtr->readIsDrained = 0;
    rtPtr->eofPending = 0;
    rtPtr->nonblocking =
	    (((Channel *) parentChan)->state->flags & CHANNEL_NONBLOCKING);
    rtPtr->dead = 0;

    /*
     * Query parent for current blocking mode.
     */
................................................................................

    /* ASSERT: rtPtr->method & FLAG(METH_READ) */
    /* ASSERT: rtPtr->mode & TCL_READABLE */

    (void) InvokeTclMethod(rtPtr, "clear", NULL, NULL, NULL);

    rtPtr->readIsDrained = 0;
    rtPtr->eofPending = 0;
    ResultClear(&rtPtr->result);
}
 
static int
TransformLimit(
    ReflectedTransform *rtPtr,
    int *errorCodePtr,

Changes to tests/io.test.

8460
8461
8462
8463
8464
8465
8466




















8467
8468
8469
8470
8471
8472
8473
8474
8475
8476
8477
8478
    seek $f 0
    set code [catch {interp eval foo [list seek $f 0]} msg]
    # The string map converts the changing channel handle to a fixed string
    list $code [string map [list $f @@] $msg]
} -cleanup {
    close $f
} -result {1 {can not find channel named "@@"}}





















# ### ### ### ######### ######### #########

# cleanup
foreach file [list fooBar longfile script script2 output test1 pipe my_script \
	test2 test3 cat stdout kyrillic.txt utf8-fcopy.txt utf8-rp.txt] {
    removeFile $file
}
cleanupTests
}
namespace delete ::tcl::test::io
return






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












8460
8461
8462
8463
8464
8465
8466
8467
8468
8469
8470
8471
8472
8473
8474
8475
8476
8477
8478
8479
8480
8481
8482
8483
8484
8485
8486
8487
8488
8489
8490
8491
8492
8493
8494
8495
8496
8497
8498
    seek $f 0
    set code [catch {interp eval foo [list seek $f 0]} msg]
    # The string map converts the changing channel handle to a fixed string
    list $code [string map [list $f @@] $msg]
} -cleanup {
    close $f
} -result {1 {can not find channel named "@@"}}

test io-73.3 {[5adc350683] [gets] after EOF} -setup {
    set fn [makeFile {} io-73.3]
    set rfd [open $fn r]
    set wfd [open $fn a]
    chan configure $wfd -buffering line
    read $rfd
} -body {
    set result [eof $rfd]
    puts $wfd "more data"
    lappend result [eof $rfd]
    lappend result [gets $rfd]
    lappend result [eof $rfd]
    lappend result [gets $rfd]
    lappend result [eof $rfd]
} -cleanup {
    close $wfd
    close $rfd
    removeFile io-73.3
} -result {1 1 {more data} 0 {} 1}

# ### ### ### ######### ######### #########

# cleanup
foreach file [list fooBar longfile script script2 output test1 pipe my_script \
	test2 test3 cat stdout kyrillic.txt utf8-fcopy.txt utf8-rp.txt] {
    removeFile $file
}
cleanupTests
}
namespace delete ::tcl::test::io
return

Changes to tests/ioTrans.test.

593
594
595
596
597
598
599











































































































































































600
601
602
603
604
605
606
    set c [chan push [set c [tempchan]] [list foo $c]]
    lappend res [gets $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {}}












































































































































































# --- === *** ###########################
# method write (via puts)

test iortrans-5.1 {chan write, regular write} -setup {
    set res {}
} -match glob -body {






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    set c [chan push [set c [tempchan]] [list foo $c]]
    lappend res [gets $c]
} -cleanup {
    tempdone
    rename foo {}
} -result {{read rt* {test data
}} {}}

# Driver for a base channel that emits several short "files"
# with each terminated by a fleeting EOF
    proc driver {cmd args} {
        variable buffer
        variable index
        set chan [lindex $args 0]
        switch -- $cmd {
            initialize {
                set index($chan) 0
                set buffer($chan) .....
                return {initialize finalize watch read}
            }
            finalize {
                if {![info exists index($chan)]} {return}
                unset index($chan) buffer($chan)
                return
            }
            watch {}
            read {
                set n [lindex $args 1]
                if {![info exists index($chan)]} {
                    driver initialize $chan
                }
                set new [expr {$index($chan) + $n}]
                set result [string range $buffer($chan) $index($chan) $new-1]
                set index($chan) $new
                if {[string length $result] == 0} {
                    driver finalize $chan
                }
                return $result
            }
        }
    }

# Channel read transform that is just the identity - pass all through
    proc idxform {cmd handle args} {
      switch -- $cmd {
        initialize {
            return {initialize finalize read}
        }
        finalize {
            return
        }
        read {
            lassign $args buffer
            return $buffer
        }
      }
    }

# Test that all EOFs pass through full xform stack.  Proper data boundaries.
# Check robustness against buffer sizes.
test iortrans-4.10 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.10.1 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    chan configure $chan -buffersize 3
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.10.2 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] idxform]
    chan configure $chan -buffersize 5
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

rename idxform {}

# Channel read transform that delays the data and always returns something
    proc delayxform {cmd handle args} {
      variable store
      switch -- $cmd {
        initialize {
	    set store($handle) {}
            return {initialize finalize read drain}
        }
        finalize {
	    unset store($handle)
            return
        }
        read {
            lassign $args buffer
	    if {$store($handle) eq {}} {
		set reply [string index $buffer 0]
		set store($handle) [string range $buffer 1 end]
	    } else {
		set reply $store($handle)
		set store($handle) $buffer
	    }
            return $reply
        }
	drain {
	    delayxform read $handle {}
	}
      }
    }

# Test that all EOFs pass through full xform stack.  Proper data boundaries.
# Check robustness against buffer sizes.
test iortrans-4.11 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.11.1 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    chan configure $chan -buffersize 3
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}
test iortrans-4.11.2 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delayxform]
    chan configure $chan -buffersize 5
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

    rename delayxform {}

# Channel read transform that delays the data and may return {}
    proc delay2xform {cmd handle args} {
      variable store
      switch -- $cmd {
        initialize {
	    set store($handle) {}
            return {initialize finalize read drain}
        }
        finalize {
	    unset store($handle)
            return
        }
        read {
            lassign $args buffer
		set reply $store($handle)
		set store($handle) $buffer
            return $reply
        }
	drain {
	    delay2xform read $handle {}
	}
      }
    }

test iortrans-4.12 {[5adbc350683] chan read, handle fleeting EOF} -body {
    set chan [chan push [chan create read driver] delay2xform]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

    rename delay2xform {}
    rename driver {}


# --- === *** ###########################
# method write (via puts)

test iortrans-5.1 {chan write, regular write} -setup {
    set res {}
} -match glob -body {

Changes to tests/iogt.test.

867
868
869
870
871
872
873










































































874
875
876
877
878
879
880
881
    set res [read $f 3]
    testchannel unstack $f
    append res [read $f 3]
} -cleanup {
    close $f
} -result {xxxghi}
 










































































# cleanup
foreach file [list dummy dummyout __echo_srv__.tcl] {
    removeFile $file
}
cleanupTests
}
namespace delete ::tcl::test::iogt
return






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
    set res [read $f 3]
    testchannel unstack $f
    append res [read $f 3]
} -cleanup {
    close $f
} -result {xxxghi}
 

# Driver for a base channel that emits several short "files"
# with each terminated by a fleeting EOF
    proc driver {cmd args} {
        variable buffer
        variable index
        set chan [lindex $args 0]
        switch -- $cmd {
            initialize {
                set index($chan) 0
                set buffer($chan) .....
                return {initialize finalize watch read}
            }
            finalize {
                if {![info exists index($chan)]} {return}
                unset index($chan) buffer($chan)
                return
            }
            watch {}
            read {
                set n [lindex $args 1]
                if {![info exists index($chan)]} {
                    driver initialize $chan
                }
                set new [expr {$index($chan) + $n}]
                set result [string range $buffer($chan) $index($chan) $new-1]
                set index($chan) $new
                if {[string length $result] == 0} {
                    driver finalize $chan
                }
                return $result
            }
        }
    }

test iogt-7.0 {Handle fleeting EOF} -constraints {testchannel} -body {
    set chan [chan create read [namespace which driver]]
    identity -attach $chan
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

proc delay {op data} {
    variable store
    switch -- $op {
	create/write -	create/read  -
	delete/write -	delete/read  -
	flush/write -	write -
	clear_read   {;#ignore}
	flush/read  -
	read        {
	    if {![info exists store]} {set store {}}
	    set reply $store
	    set store $data
	    return $reply
	}
	query/maxRead {return -1}
    }
}

test iogt-7.1 {Handle fleeting EOF} -constraints {testchannel} -body {
    set chan [chan create read [namespace which driver]]
    testchannel transform $chan -command [namespace code delay]
    list [eof $chan] [read $chan] [eof $chan] [read $chan 0] [eof $chan] \
        [read $chan] [eof $chan]
} -cleanup {
    close $chan
} -result {0 ..... 1 {} 0 ..... 1}

rename delay {}
rename driver {}

# cleanup
foreach file [list dummy dummyout __echo_srv__.tcl] {
    removeFile $file
}
cleanupTests
}
namespace delete ::tcl::test::iogt
return