Tcl Source Code

Check-in [51d813943b]
Login

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

Overview
Comment:Fix for [fa3d9fd818fa0072], [fcopy $chan1 $chan2 -size $size] is not [puts -nonewline $chan2 [read $chan1 -size $size].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | main
Files: files | file ages | folders
SHA3-256: 51d813943bcaf835b9fc29a42afdcb3ed2fc3107c02d6408fbe26e1fec6bc070
User & Date: pooryorick 2023-04-03 21:20:28
References
2023-04-04
21:54
Starting with [51d813943bcaf835], chan-io-52.10 and io-52.10 are failing on the Windows (with Visua... check-in: 05e4362604 user: jan.nijtmans tags: core-8-branch
2023-04-03
21:28 Pending ticket [fa3d9fd818]: fcopy $chan1 $chan2 -size $size is not puts -nonewline $chan2 [read $chan1 -size $size plus 6 other changes artifact: 3a99a85328 user: pooryorick
Context
2023-04-04
21:49
TIP #628 addendum (various fixes to improve handling of 8.7 <-> 9.0 header differences) check-in: d29e69a635 user: jan.nijtmans tags: trunk, main
18:56
Merge trunk. check-in: 2510ec2a15 user: pooryorick tags: unchained
2023-04-03
21:20
Fix for [fa3d9fd818fa0072], [fcopy $chan1 $chan2 -size $size] is not [puts -nonewline $chan2 [read $... check-in: 51d813943b user: pooryorick tags: trunk, main
20:58
Fix for [fa3d9fd818fa0072], [fcopy $chan1 $chan2 -size $size] is not [puts -nonewline $chan2 [read $... check-in: 704a7e8389 user: pooryorick tags: bug-fa3d9fd818fa0072
19:58
Fix typo in test io-53.12.1. check-in: b154e3fedf user: pooryorick tags: trunk, main
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to doc/chan.n.

222
223
224
225
226
227
228
229


230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

250



251
252



































253









254
255
256

257
258

259
260

261

















262













263
264
265
266
267
268
269
270
271
272



273
274
275
276
277
278
279
translations occur during either input or output.  This translation is
typically used on UNIX platforms,
.RE
.RE
.TP
\fBchan copy \fIinputChan outputChan\fR ?\fB\-size \fIsize\fR? ?\fB\-command \fIcallback\fR?
.
Copies data from \fIinputChan\fR to \fIoutputChan\fR, leveraging internal


buffers to avoid extra copies and to avoid buffering too much data in main
memory when copying large files to slow destinations like network sockets.
.RS
.PP
If \fB\-size\fR is given, the size is in bytes if the two channels have the
same encoding and in characters otherwise, and only that amount is copied.
Otherwise, all data until the end of the file is copied.

\fBchan copy\fR blocks until the copy is complete and returns the number of
bytes or characters written to \fIoutputChan\fR.
.PP
If \fB\-command\fR is given, \fBchan copy\fR returns immediately, the copy is
carried out in the background, and then \fIcallback\fR is called with the
number of bytes written to \fIoutputChan\fR as its first argument, and the
error message for any error that occurred as its second argument.
\fIinputChan\fR and \fIoutputChan\fR are automatically configured for
non-blocking mode if needed.  Background copying only works correctly if the
event loop is active, e.g. via \fBvwait\fR or Tk.
.PP
During a background copy no other read or write operation may be performed on

\fIinputChan\fR or \fIoutputChan\fR.  If either \fIinputChan\fR or



\fIoutputChan\fR is closed while the copy is in progress copying ceases and
\fBno\fR callback is made.  If \fIinputChan\fR is closed all data already queued



































is written to \fIoutputChan\fR.









.PP
The should be no event handler established for \fIinputChan\fR  because it may
become readable during a background copy.  An attempt to read or write

from within an event handler results result in the error,  "channel busy".
.PP

Due to end-of-line translation the number of bytes read from \fIinputChan\fR
may be different than the number of bytes written to \fIoutputChan\fR.  Only

the number of bytes written to \fIoutputChan\fR is reported.

















.PP













\fBChan copy\fR reads the data according to the \fB\-encoding\fR,
\fB\-translation\fR, and \fB\-eofchar\fR of the source and writes to the
destination according to the configuration for that channel.  If the encoding
and translation of both channels is \fBbinary\fR and the \fB\-eofchar\fR of
both channels is the empty string, an identical copy is made.  If only the
encoding of the destination is \fBbinary\fR, Tcl's internal modified UTF-8
representation of the characters read from the source is written to the
destination. If only the encoding of the source is \fBbinary\fR, each byte read
becomes one Unicode character in the range of 0 to 255, and that character is
subject to the encoding and translation of the destination as it is written.



.RE
.TP
\fBchan create \fImode cmdPrefix\fR
.
Creates a new channel, called a \fBreflected\fR channel, with \fIcmdPrefix\fR
as its handler, and returns the name of the channel.  \fBcmdPrefix\fR is the
first words of a command that provides the interface for a \fBrefchan\fR.







|
>
>
|
|


<
<
<
|
<
<

|
|
|
|
|
|
|

|
>
|
>
>
>
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>

<
<
>
|

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

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







222
223
224
225
226
227
228
229
230
231
232
233
234
235



236


237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299


300
301
302
303
304

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338









339
340
341
342
343
344
345
346
347
348
translations occur during either input or output.  This translation is
typically used on UNIX platforms,
.RE
.RE
.TP
\fBchan copy \fIinputChan outputChan\fR ?\fB\-size \fIsize\fR? ?\fB\-command \fIcallback\fR?
.
Reads characters from \fIinputChan\fR and writes them to \fIoutputChan\fR until
all characters are copied, blocking until the copy is complete and returning
the number of characters copied.  Leverages internal buffers to avoid extra
copies and to avoid buffering too much data in main memory when copying large
files to slow destinations like network sockets.
.RS
.PP



\fB\-size\fR limits the number of characters copied.


.PP
If \fB\-command\fR is gviven, \fBchan copy\fR returns immediately, works in the
background, and calls \fIcallback\fR when the copy completes, providing as an
additional argument the number of characters written to \fIoutputChan\fR.  If
an error occurres during the background copy, another argument provides message
for the error.  \fIinputChan\fR and \fIoutputChan\fR are automatically
configured for non-blocking mode if needed.  Background copying only works
correctly if events are being processed, e.g. via \fBvwait\fR or Tk.
.PP
During a background copy no other read operation may be performed on
\fIinputChan\fR, and no write operation may be performed on
\fIoutputChan\fR.  However, write operations may by performed on
\fIinputChan\fR and read operations may be performed on \fIoutputChan\fR, as
exhibited by the bidirectional copy example below.
.PP
If either \fIinputChan\fR or \fIoutputChan\fR is closed while the copy is in
progress, copying ceases and \fBno\fR callback is made.  If \fIinputChan\fR is
closed all data already queued is written to \fIoutputChan\fR.
.PP
There should be no event handler established for \fIinputChan\fR  because it
may become readable during a background copy.  An attempt to read or write from
within an event handler results result in the error,  "channel busy".  Any
wrong-sided I/O attempted (by a \fBfileevent\fR handler or otherwise) results
in a
.QW "channel busy"
error.
.PP
.PP
.IP \fBEXAMPLES\fR
.PP
The first example transfers the contents of one channel exactly to
another. Note that when copying one file to another, it is better to
use \fBfile copy\fR which also copies file metadata (e.g. the file
access permissions) where possible.
.PP
.CS
fconfigure $in -translation binary
fconfigure $out -translation binary
\fBfcopy\fR $in $out
.CE
.PP
This second example shows how the callback gets
passed the number of bytes transferred.
It also uses vwait to put the application into the event loop.
Of course, this simplified example could be done without the command
callback.
.PP
.CS
proc Cleanup {in out bytes {error {}}} {
    global total
    set total $bytes
    close $in
    close $out
    if {[string length $error] != 0} {
        # error occurred during the copy
    }
}
set in [open $file1]
set out [socket $server $port]
\fBfcopy\fR $in $out -command [list Cleanup $in $out]
vwait total
.CE
.PP


The third example copies in chunks and tests for end of file
in the command callback.
.PP
.CS
proc CopyMore {in out chunk bytes {error {}}} {

    global total done
    incr total $bytes
    if {([string length $error] != 0) || [eof $in]} {
        set done $total
        close $in
        close $out
    } else {
        \fBfcopy\fR $in $out -size $chunk \e
            -command [list CopyMore $in $out $chunk]
    }
}
set in [open $file1]
set out [socket $server $port]
set chunk 1024
set total 0
\fBfcopy\fR $in $out -size $chunk \e
    -command [list CopyMore $in $out $chunk]
vwait done
.CE
.PP
The fourth example starts an asynchronous, bidirectional fcopy between
two sockets. Those could also be pipes from two [open "|hal 9000" r+]
(though their conversation would remain secret to the script, since
all four fileevent slots are busy).
.PP
.CS
set flows 2
proc Done {dir args} {
     global flows done
     puts "$dir is over."
     incr flows -1
     if {$flows<=0} {set done 1}
}
\fBfcopy\fR $sok1 $sok2 -command [list Done UP]









\fBfcopy\fR $sok2 $sok1 -command [list Done DOWN]
vwait done
.CE
.RE
.TP
\fBchan create \fImode cmdPrefix\fR
.
Creates a new channel, called a \fBreflected\fR channel, with \fIcmdPrefix\fR
as its handler, and returns the name of the channel.  \fBcmdPrefix\fR is the
first words of a command that provides the interface for a \fBrefchan\fR.

Changes to doc/fcopy.n.

8
9
10
11
12
13
14
15
16
17
18
19
20

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54



55
56
57
58
59
60
61
62

63
64
65

66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
.TH fcopy n 8.0 Tcl "Tcl Built-In Commands"
.so man.macros
.BS
'\" Note:  do not modify the .SH NAME line immediately below!
.SH NAME
fcopy \- Copy data from one channel to another
.SH SYNOPSIS
\fBfcopy \fIinchan\fR \fIoutchan\fR ?\fB\-size \fIsize\fR? ?\fB\-command \fIcallback\fR?
.BE

.SH DESCRIPTION
.PP
The \fBfcopy\fR command copies data from one I/O channel, \fIinchan\fR to another I/O channel, \fIoutchan\fR.

The \fBfcopy\fR command leverages the buffering in the Tcl I/O system to
avoid extra copies and to avoid buffering too much data in
main memory when copying large files to slow destinations like
network sockets.
.PP
The \fBfcopy\fR
command transfers data from \fIinchan\fR until end of file
or \fIsize\fR bytes or characters have been
transferred; \fIsize\fR is in bytes if the input channel is in binary mode,
and is in characters otherwise.
If no \fB\-size\fR argument is given,
then the copy goes until end of file.
All the data read from \fIinchan\fR is copied to \fIoutchan\fR.
Without the \fB\-command\fR option, \fBfcopy\fR blocks until the copy is complete
and returns the number of bytes or characters (using the same rules as
for the \fB\-size\fR option) written to \fIoutchan\fR.
.PP
The \fB\-command\fR argument makes \fBfcopy\fR work in the background.
In this case it returns immediately and the \fIcallback\fR is invoked
later when the copy completes.
The \fIcallback\fR is called with
one or two additional
arguments that indicates how many bytes were written to \fIoutchan\fR.
If an error occurred during the background copy, the second argument is the
error string associated with the error.
With a background copy,
it is not necessary to put \fIinchan\fR or \fIoutchan\fR into
non-blocking mode; the \fBfcopy\fR command takes care of that automatically.
However, it is necessary to enter the event loop by using
the \fBvwait\fR command or by using Tk.
.PP
You are not allowed to do other input operations with \fIinchan\fR, or
output operations with \fIoutchan\fR, during a background
\fBfcopy\fR. The converse is entirely legitimate, as exhibited by the



bidirectional fcopy example below.
.PP
If either \fIinchan\fR or \fIoutchan\fR get closed
while the copy is in progress, the current copy is stopped
and the command callback is \fInot\fR made.
If \fIinchan\fR is closed,
then all data already queued for \fIoutchan\fR is written out.
.PP

Note that \fIinchan\fR can become readable during a background copy.
You should turn off any \fBfileevent\fR handlers during a background
copy so those handlers do not interfere with the copy.

Any wrong-sided I/O attempted (by a \fBfileevent\fR handler or otherwise) will get a

.QW "channel busy"
error.
.PP
\fBFcopy\fR translates end-of-line sequences in \fIinchan\fR and \fIoutchan\fR
according to the \fB\-translation\fR option
for these channels.
See the manual entry for \fBfconfigure\fR for details on the
\fB\-translation\fR option.
The translations mean that the number of bytes read from \fIinchan\fR
can be different than the number of bytes written to \fIoutchan\fR.
Only the number of bytes written to \fIoutchan\fR is reported,
either as the return value of a synchronous \fBfcopy\fR or
as the argument to the callback for an asynchronous \fBfcopy\fR.
.PP
\fBFcopy\fR obeys the encodings and character translations configured
for the channels. This
means that the incoming characters are converted internally first
UTF-8 and then into the encoding of the channel \fBfcopy\fR writes
to. See the manual entry for \fBfconfigure\fR for details on the
\fB\-encoding\fR and \fB\-translation\fR options. No conversion is
done if both channels are
set to encoding
.QW binary
and have matching translations. If only the output channel is set to encoding
.QW binary
the system will write the internal UTF-8 representation of the incoming
characters. If only the input channel is set to encoding
.QW binary
the system will assume that the incoming
bytes are valid UTF-8 characters and convert them according to the
output encoding. The behaviour of the system for bytes which are not
valid UTF-8 characters is undefined in this case.
.SH EXAMPLES
.PP
The first example transfers the contents of one channel exactly to
another. Note that when copying one file to another, it is better to
use \fBfile copy\fR which also copies file metadata (e.g. the file
access permissions) where possible.
.PP







|




|
>
|
|
|
<

<
<
<
<
<
|
<
<
<
<
<

|
<
|
<
<
|
|
|
|
<
<
<
|

<
|
<
>
>
>
|

<
|
|
<
|

>
|
<
<
>
|
>


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25





26





27
28

29


30
31
32
33



34
35

36

37
38
39
40
41

42
43

44
45
46
47


48
49
50
51
52






























53
54
55
56
57
58
59
.TH fcopy n 8.0 Tcl "Tcl Built-In Commands"
.so man.macros
.BS
'\" Note:  do not modify the .SH NAME line immediately below!
.SH NAME
fcopy \- Copy data from one channel to another
.SH SYNOPSIS
\fBfcopy \fIinputChan\fR \fIoutputChan\fR ?\fB\-size \fIsize\fR? ?\fB\-command \fIcallback\fR?
.BE

.SH DESCRIPTION
.PP
Reads characters from \fIinputChan\fR and writes them to \fIoutputChan\fR until
all characters are copied, blocking until the copy is complete and returning
the number of characters copied.  Leverages internal buffers to avoid extra
copies and to avoid buffering too much data in main memory when copying large
files to slow destinations like network sockets.

.PP





\fB\-size\fR limits the number of characters copied.





.PP
\fB\-command\fR makes \fBfcopy\fR return immediately, work in the background,

and call \fIcallback\fR when the copy completes, providing as an additional


argument the number of characters written to \fIoutputChan\fR.  If an error
occurres during the background copy, another argument provides the message for
the error.  \fIinputChan\fR and \fIoutputChan\fR are automatically configured
for non-blocking mode if needed.  Background copying only works correctly if



events are being processed e.g. via \fBvwait\fR or Tk.
.PP

During a background copy no other read operation may be performed on

\fIinputChan\fR, and no other write operation may be performed on
\fIoutputChan\fR.  However, write operations may by performed on
\fIinputChan\fR and read operations may be performed on \fIoutputChan\fR, as
exhibited by the bidirectional copy example below.
.PP

If either \fIinputChan\fR or \fIoutputChan\fR is closed while the copy is in
progress, copying ceases and \fBno\fR callback is made.  If \fIinputChan\fR is

closed all data already queued is written to \fIoutputChan\fR.
.PP
There should be no event handler established for \fIinputChan\fR  because it
may become readable during a background copy.  An attempt to read or write from


within an event handler results result in the error,  "channel busy".  Any
wrong-sided I/O attempted (by a \fBfileevent\fR handler or otherwise) results
in a
.QW "channel busy"
error.






























.SH EXAMPLES
.PP
The first example transfers the contents of one channel exactly to
another. Note that when copying one file to another, it is better to
use \fBfile copy\fR which also copies file metadata (e.g. the file
access permissions) where possible.
.PP
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    incr total $bytes
    if {([string length $error] != 0) || [eof $in]} {
        set done $total
        close $in
        close $out
    } else {
        \fBfcopy\fR $in $out -size $chunk \e
                -command [list CopyMore $in $out $chunk]
    }
}
set in [open $file1]
set out [socket $server $port]
set chunk 1024
set total 0
\fBfcopy\fR $in $out -size $chunk \e
        -command [list CopyMore $in $out $chunk]
vwait done
.CE
.PP
The fourth example starts an asynchronous, bidirectional fcopy between
two sockets. Those could also be pipes from two [open "|hal 9000" r+]
(though their conversation would remain secret to the script, since
all four fileevent slots are busy).







|







|







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    incr total $bytes
    if {([string length $error] != 0) || [eof $in]} {
        set done $total
        close $in
        close $out
    } else {
        \fBfcopy\fR $in $out -size $chunk \e
            -command [list CopyMore $in $out $chunk]
    }
}
set in [open $file1]
set out [socket $server $port]
set chunk 1024
set total 0
\fBfcopy\fR $in $out -size $chunk \e
    -command [list CopyMore $in $out $chunk]
vwait done
.CE
.PP
The fourth example starts an asynchronous, bidirectional fcopy between
two sockets. Those could also be pipes from two [open "|hal 9000" r+]
(though their conversation would remain secret to the script, since
all four fileevent slots are busy).

Changes to generic/tclEncoding.c.

2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
		} else {
		   /* PROFILE_STRICT */
		   result = TCL_CONVERT_SYNTAX;
		   break;
		}
	    } else {
		/*
		 * Convert 0xC080 to real nulls when we are in output mode,
		 * irrespective of the profile.
		 */
		*dst++ = 0;
		src += 2;
	    }

	} else if (!Tcl_UtfCharComplete(src, srcEnd - src)) {
	    /*







|
<







2566
2567
2568
2569
2570
2571
2572
2573

2574
2575
2576
2577
2578
2579
2580
		} else {
		   /* PROFILE_STRICT */
		   result = TCL_CONVERT_SYNTAX;
		   break;
		}
	    } else {
		/*
		 * For output convert 0xC080 to a real null.

		 */
		*dst++ = 0;
		src += 2;
	    }

	} else if (!Tcl_UtfCharComplete(src, srcEnd - src)) {
	    /*

Changes to generic/tclIO.c.

170
171
172
173
174
175
176


177
178
179
180
181
182
183
			    int errorCode);
static int		CloseChannelPart(Tcl_Interp *interp, Channel *chanPtr,
			    int errorCode, int flags);
static int		CloseWrite(Tcl_Interp *interp, Channel *chanPtr);
static void		CommonGetsCleanup(Channel *chanPtr);
static int		CopyData(CopyState *csPtr, int mask);
static void		DeleteTimerHandler(ChannelState *statePtr);


static int		MoveBytes(CopyState *csPtr);

static void		MBCallback(CopyState *csPtr, Tcl_Obj *errObj);
static void		MBError(CopyState *csPtr, int mask, int errorCode);
static int		MBRead(CopyState *csPtr);
static int		MBWrite(CopyState *csPtr);
static void		MBEvent(void *clientData, int mask);







>
>







170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
			    int errorCode);
static int		CloseChannelPart(Tcl_Interp *interp, Channel *chanPtr,
			    int errorCode, int flags);
static int		CloseWrite(Tcl_Interp *interp, Channel *chanPtr);
static void		CommonGetsCleanup(Channel *chanPtr);
static int		CopyData(CopyState *csPtr, int mask);
static void		DeleteTimerHandler(ChannelState *statePtr);
int				Lossless(ChannelState *inStatePtr,
			    ChannelState *outStatePtr, long long toRead);
static int		MoveBytes(CopyState *csPtr);

static void		MBCallback(CopyState *csPtr, Tcl_Obj *errObj);
static void		MBError(CopyState *csPtr, int mask, int errorCode);
static int		MBRead(CopyState *csPtr);
static int		MBWrite(CopyState *csPtr);
static void		MBEvent(void *clientData, int mask);
334
335
336
337
338
339
340



341
342
343
344
345
346
347
    FreeChannelInternalRep,		/* freeIntRepProc */
    DupChannelInternalRep,		/* dupIntRepProc */
    NULL,			/* updateStringProc */
    NULL,			/* setFromAnyProc */
    TCL_OBJTYPE_V0
};




#define ChanSetInternalRep(objPtr, resPtr)					\
    do {								\
	Tcl_ObjInternalRep ir;						\
	(resPtr)->refCount++;						\
	ir.twoPtrValue.ptr1 = (resPtr);					\
	ir.twoPtrValue.ptr2 = NULL;					\
	Tcl_StoreInternalRep((objPtr), &chanObjType, &ir);			\







>
>
>







336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
    FreeChannelInternalRep,		/* freeIntRepProc */
    DupChannelInternalRep,		/* dupIntRepProc */
    NULL,			/* updateStringProc */
    NULL,			/* setFromAnyProc */
    TCL_OBJTYPE_V0
};

#define GetIso88591() \
    (binaryEncoding ? Tcl_GetEncoding(NULL, "iso8859-1") : binaryEncoding)

#define ChanSetInternalRep(objPtr, resPtr)					\
    do {								\
	Tcl_ObjInternalRep ir;						\
	(resPtr)->refCount++;						\
	ir.twoPtrValue.ptr1 = (resPtr);					\
	ir.twoPtrValue.ptr2 = NULL;					\
	Tcl_StoreInternalRep((objPtr), &chanObjType, &ir);			\
9360
9361
9362
9363
9364
9365
9366
9367
9368
9369
9370
9371
9372
9373
9374
9375
9376
9377
9378
9379
9380
9381
9382
9383
9384
9385
    /*
     * Make sure the output side is unbuffered.
     */

    ResetFlag(outStatePtr, CHANNEL_LINEBUFFERED);
    SetFlag(outStatePtr, CHANNEL_UNBUFFERED);

    /*
     * Test for conditions where we know we can just move bytes from input
     * channel to output channel with no transformation or even examination
     * of the bytes themselves.
     */

    moveBytes = inStatePtr->inEofChar == '\0'	/* No eofChar to stop input */
	    && inStatePtr->inputTranslation == TCL_TRANSLATE_LF
	    && outStatePtr->outputTranslation == TCL_TRANSLATE_LF
	    && inStatePtr->encoding == outStatePtr->encoding
	    && CHANNEL_PROFILE_GET(inStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8
	    && CHANNEL_PROFILE_GET(outStatePtr->outputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8;

    /*
     * Allocate a new CopyState to maintain info about the current copy in
     * progress. This structure will be deallocated when the copy is
     * completed.
     */








<
<
<
<
<
|
<
<
<
<
<
<







9365
9366
9367
9368
9369
9370
9371





9372






9373
9374
9375
9376
9377
9378
9379
    /*
     * Make sure the output side is unbuffered.
     */

    ResetFlag(outStatePtr, CHANNEL_LINEBUFFERED);
    SetFlag(outStatePtr, CHANNEL_UNBUFFERED);






    moveBytes = Lossless(inStatePtr, outStatePtr, toRead);







    /*
     * Allocate a new CopyState to maintain info about the current copy in
     * progress. This structure will be deallocated when the copy is
     * completed.
     */

9678
9679
9680
9681
9682
9683
9684
9685
9686
9687
9688
9689
9690
9691
9692
9693
9694
9695
9696
9697
9698
9699
9700
9701
9702
9703
9704
9705
9706
9707
9708
9709
9710
9711
9712
9713
9714
9715
9716
9717
    Tcl_Channel inChan, outChan;
    ChannelState *inStatePtr, *outStatePtr;
    int result = TCL_OK;
    Tcl_Size sizeb;
    Tcl_WideInt total;
    Tcl_WideInt size; /* TODO - be careful if total and size are made unsigned  */
    const char *buffer;
    int inBinary, outBinary, sameEncoding;
				/* Encoding control */
    int underflow;		/* Input underflow */

    inChan	= (Tcl_Channel) csPtr->readPtr;
    outChan	= (Tcl_Channel) csPtr->writePtr;
    inStatePtr	= csPtr->readPtr->state;
    outStatePtr	= csPtr->writePtr->state;
    interp	= csPtr->interp;
    cmdPtr	= csPtr->cmdPtr;

    /*
     * Copy the data the slow way, using the translation mechanism.
     *
     * Note: We have make sure that we use the topmost channel in a stack for
     * the copying. The caller uses Tcl_GetChannel to access it, and thus gets
     * the bottom of the stack.
     */

    inBinary = (inStatePtr->encoding == NULL);
    outBinary = (outStatePtr->encoding == NULL);
    sameEncoding = inStatePtr->encoding == outStatePtr->encoding
	    && CHANNEL_PROFILE_GET(inStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8
	    && CHANNEL_PROFILE_GET(outStatePtr->outputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8;

    if (!(inBinary || sameEncoding)) {
	TclNewObj(bufObj);
	Tcl_IncrRefCount(bufObj);
    }

    while (csPtr->toRead != (Tcl_WideInt) 0) {
	/*
	 * Check for unreported background errors.







|
<

















|
<
<
<
<

|







9672
9673
9674
9675
9676
9677
9678
9679

9680
9681
9682
9683
9684
9685
9686
9687
9688
9689
9690
9691
9692
9693
9694
9695
9696
9697




9698
9699
9700
9701
9702
9703
9704
9705
9706
    Tcl_Channel inChan, outChan;
    ChannelState *inStatePtr, *outStatePtr;
    int result = TCL_OK;
    Tcl_Size sizeb;
    Tcl_WideInt total;
    Tcl_WideInt size; /* TODO - be careful if total and size are made unsigned  */
    const char *buffer;
    int moveBytes;

    int underflow;		/* Input underflow */

    inChan	= (Tcl_Channel) csPtr->readPtr;
    outChan	= (Tcl_Channel) csPtr->writePtr;
    inStatePtr	= csPtr->readPtr->state;
    outStatePtr	= csPtr->writePtr->state;
    interp	= csPtr->interp;
    cmdPtr	= csPtr->cmdPtr;

    /*
     * Copy the data the slow way, using the translation mechanism.
     *
     * Note: We have make sure that we use the topmost channel in a stack for
     * the copying. The caller uses Tcl_GetChannel to access it, and thus gets
     * the bottom of the stack.
     */

    moveBytes = Lossless(inStatePtr, outStatePtr, csPtr->toRead);





    if (!moveBytes) {
	TclNewObj(bufObj);
	Tcl_IncrRefCount(bufObj);
    }

    while (csPtr->toRead != (Tcl_WideInt) 0) {
	/*
	 * Check for unreported background errors.
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
9756
9757
9758
9759
9760
9761
9762
9763
9764
9765
9766
9767
9768
	     * underflow instead to prime the readable fileevent.
	     */

	    size = 0;
	    underflow = 1;
	} else {
	    /*
	     * Read up to bufSize bytes.
	     */

	    if ((csPtr->toRead == (Tcl_WideInt) -1)
                    || (csPtr->toRead > (Tcl_WideInt) csPtr->bufSize)) {
		sizeb = csPtr->bufSize;
	    } else {
		sizeb = csPtr->toRead;
	    }

	    if (inBinary || sameEncoding) {
		size = DoRead(inStatePtr->topChanPtr, csPtr->buffer, sizeb,
                              !GotFlag(inStatePtr, CHANNEL_NONBLOCKING));
	    } else {
		size = DoReadChars(inStatePtr->topChanPtr, bufObj, sizeb,
			!GotFlag(inStatePtr, CHANNEL_NONBLOCKING)
			,0 /* No append */);
	    }







|









|







9733
9734
9735
9736
9737
9738
9739
9740
9741
9742
9743
9744
9745
9746
9747
9748
9749
9750
9751
9752
9753
9754
9755
9756
9757
	     * underflow instead to prime the readable fileevent.
	     */

	    size = 0;
	    underflow = 1;
	} else {
	    /*
	     * Read up to bufSize characters.
	     */

	    if ((csPtr->toRead == (Tcl_WideInt) -1)
                    || (csPtr->toRead > (Tcl_WideInt) csPtr->bufSize)) {
		sizeb = csPtr->bufSize;
	    } else {
		sizeb = csPtr->toRead;
	    }

	    if (moveBytes) {
		size = DoRead(inStatePtr->topChanPtr, csPtr->buffer, sizeb,
                              !GotFlag(inStatePtr, CHANNEL_NONBLOCKING));
	    } else {
		size = DoReadChars(inStatePtr->topChanPtr, bufObj, sizeb,
			!GotFlag(inStatePtr, CHANNEL_NONBLOCKING)
			,0 /* No append */);
	    }
9821
9822
9823
9824
9825
9826
9827
9828
9829
9830
9831
9832
9833
9834
9835
9836
9837
9838
9839
9840
9841
9842
9843
9844
9845
9846
9847
9848
9849
9850
9851
9852
9853
	    }
	}

	/*
	 * Now write the buffer out.
	 */

	if (inBinary || sameEncoding) {
	    buffer = csPtr->buffer;
	    sizeb = size;
	} else {
	    buffer = Tcl_GetStringFromObj(bufObj, &sizeb);
	}

	if (outBinary || sameEncoding) {
	    sizeb = WriteBytes(outStatePtr->topChanPtr, buffer, sizeb);
	} else {
	    sizeb = WriteChars(outStatePtr->topChanPtr, buffer, sizeb);
	}

	/*
	 * [Bug 2895565]. At this point 'size' still contains the number of
	 * bytes or characters which have been read. We keep this to later to
	 * update the totals and toRead information, see marker (UP) below. We
	 * must not overwrite it with 'sizeb', which is the number of written
	 * bytes or characters, and both EOL translation and encoding
	 * conversion may have changed this number unpredictably in relation
	 * to 'size' (It can be smaller or larger, in the latter case able to
	 * drive toRead below -1, causing infinite looping). Completely
	 * unsuitable for updating totals and toRead.
	 */

	if (sizeb == TCL_INDEX_NONE) {







|

|


<
<
<
<
<





|


|







9810
9811
9812
9813
9814
9815
9816
9817
9818
9819
9820
9821





9822
9823
9824
9825
9826
9827
9828
9829
9830
9831
9832
9833
9834
9835
9836
9837
	    }
	}

	/*
	 * Now write the buffer out.
	 */

	if (moveBytes) {
	    buffer = csPtr->buffer;
	    sizeb = WriteBytes(outStatePtr->topChanPtr, buffer, size);
	} else {
	    buffer = Tcl_GetStringFromObj(bufObj, &sizeb);





	    sizeb = WriteChars(outStatePtr->topChanPtr, buffer, sizeb);
	}

	/*
	 * [Bug 2895565]. At this point 'size' still contains the number of
	 * characters which have been read. We keep this to later to
	 * update the totals and toRead information, see marker (UP) below. We
	 * must not overwrite it with 'sizeb', which is the number of written
	 * characters, and both EOL translation and encoding
	 * conversion may have changed this number unpredictably in relation
	 * to 'size' (It can be smaller or larger, in the latter case able to
	 * drive toRead below -1, causing infinite looping). Completely
	 * unsuitable for updating totals and toRead.
	 */

	if (sizeb == TCL_INDEX_NONE) {
9866
9867
9868
9869
9870
9871
9872
9873
9874
9875
9876
9877
9878
9879
9880
9881
9882
9883
	    if (msg != NULL) {
		Tcl_DecrRefCount(msg);
	    }
	    break;
	}

	/*
	 * Update the current byte count. Do it now so the count is valid
	 * before a return or break takes us out of the loop. The invariant at
	 * the top of the loop should be that csPtr->toRead holds the number
	 * of bytes left to copy.
	 */

	if (csPtr->toRead != -1) {
	    csPtr->toRead -= size;
	}
	csPtr->total += size;








|


|







9850
9851
9852
9853
9854
9855
9856
9857
9858
9859
9860
9861
9862
9863
9864
9865
9866
9867
	    if (msg != NULL) {
		Tcl_DecrRefCount(msg);
	    }
	    break;
	}

	/*
	 * Update the current character count. Do it now so the count is valid
	 * before a return or break takes us out of the loop. The invariant at
	 * the top of the loop should be that csPtr->toRead holds the number
	 * of characters left to copy.
	 */

	if (csPtr->toRead != -1) {
	    csPtr->toRead -= size;
	}
	csPtr->total += size;

9936
9937
9938
9939
9940
9941
9942
9943
9944
9945
9946
9947
9948
9949
9950
9951

    if (bufObj != NULL) {
	TclDecrRefCount(bufObj);
	bufObj = NULL;
    }

    /*
     * Make the callback or return the number of bytes transferred. The local
     * total is used because StopCopy frees csPtr.
     */

    total = csPtr->total;
    if (cmdPtr && interp) {
	int code;

	/*







|
|







9920
9921
9922
9923
9924
9925
9926
9927
9928
9929
9930
9931
9932
9933
9934
9935

    if (bufObj != NULL) {
	TclDecrRefCount(bufObj);
	bufObj = NULL;
    }

    /*
     * Make the callback or return the number of characters transferred. The
     * local total is used because StopCopy frees csPtr.
     */

    total = csPtr->total;
    if (cmdPtr && interp) {
	int code;

	/*
10256
10257
10258
10259
10260
10261
10262












































10263
10264
10265
10266
10267
10268
10269
static void
CopyEventProc(
    void *clientData,
    int mask)
{
    (void) CopyData((CopyState *)clientData, mask);
}













































/*
 *----------------------------------------------------------------------
 *
 * StopCopy --
 *
 *	This routine halts a copy that is in progress.







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







10240
10241
10242
10243
10244
10245
10246
10247
10248
10249
10250
10251
10252
10253
10254
10255
10256
10257
10258
10259
10260
10261
10262
10263
10264
10265
10266
10267
10268
10269
10270
10271
10272
10273
10274
10275
10276
10277
10278
10279
10280
10281
10282
10283
10284
10285
10286
10287
10288
10289
10290
10291
10292
10293
10294
10295
10296
10297
static void
CopyEventProc(
    void *clientData,
    int mask)
{
    (void) CopyData((CopyState *)clientData, mask);
}

/*
 *----------------------------------------------------------------------
 *
 * Lossless --
 *
 *	Determines whether copying characters between two channel states would
 *	be lossless, i.e. whether one byte corresponds to one character, every
 *	character appears in the Unicode character set, there are no
 *	translations to be performed, and no inline signals to respond to.
 *
 * Result:
 *	True if copying would be lossless.
 *
 *----------------------------------------------------------------------
 */
int
Lossless(
    ChannelState *inStatePtr,
    ChannelState *outStatePtr,
    long long toRead)
{
    return inStatePtr->inEofChar == '\0'	/* No eofChar to stop input */
	&& inStatePtr->inputTranslation == TCL_TRANSLATE_LF
	&& outStatePtr->outputTranslation == TCL_TRANSLATE_LF
	&& (
	    (
		(inStatePtr->encoding == NULL
		    || inStatePtr->encoding == GetBinaryEncoding()
		)
		&&
		(outStatePtr->encoding == NULL
		    || outStatePtr->encoding == GetBinaryEncoding()
		)
	    )
	    ||
	    (
		toRead == -1
		&& inStatePtr->encoding == outStatePtr->encoding
		&& CHANNEL_PROFILE_GET(inStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8
		&& CHANNEL_PROFILE_GET(outStatePtr->inputEncodingFlags) == TCL_ENCODING_PROFILE_TCL8
	    )
	);
}

/*
 *----------------------------------------------------------------------
 *
 * StopCopy --
 *
 *	This routine halts a copy that is in progress.

Changes to tests/chanio.test.

6882
6883
6884
6885
6886
6887
6888
6889
6890
6891
6892
6893
6894
6895
6896



6897


6898
6899

6900


6901
6902
6903
6904
6905
6906
6907
} 5
test chan-io-52.11 {TclCopyChannel & encodings} -setup {
    set f [open $path(utf8-fcopy.txt) w]
    fconfigure $f -encoding utf-8 -translation lf
    puts $f АА
    close $f
} -constraints {fcopy} -body {
    # binary to encoding => the input has to be in utf-8 to make sense to the
    # encoder
    set in  [open $path(utf8-fcopy.txt) r]
    set out [open $path(kyrillic.txt) w]
    # -translation binary is also -encoding binary
    chan configure $in  -translation binary
    chan configure $out -encoding koi8-r -translation lf
    chan copy $in $out



    chan close $in


    chan close $out
    file size $path(kyrillic.txt)

} -result 3



test chan-io-53.1 {CopyData} -setup {
    file delete $path(test1)
} -constraints {fcopy} -body {
    set f1 [open $thisScript]
    set f2 [open $path(test1) w]
    chan configure $f1 -translation lf -blocking 0







<
<




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







6882
6883
6884
6885
6886
6887
6888


6889
6890
6891
6892
6893
6894
6895
6896
6897
6898
6899
6900
6901

6902
6903
6904
6905
6906
6907
6908
6909
6910
6911
6912
} 5
test chan-io-52.11 {TclCopyChannel & encodings} -setup {
    set f [open $path(utf8-fcopy.txt) w]
    fconfigure $f -encoding utf-8 -translation lf
    puts $f АА
    close $f
} -constraints {fcopy} -body {


    set in  [open $path(utf8-fcopy.txt) r]
    set out [open $path(kyrillic.txt) w]
    # -translation binary is also -encoding binary
    chan configure $in  -translation binary
    chan configure $out -encoding koi8-r -translation lf -profile strict
    catch {chan copy $in $out} cres copts
    return $cres
} -cleanup {
    if {$in in [chan names]} {
	close $in
    }
    if {$out in [chan names]} {
	close $out

    }
    catch {unset cres}
} -match glob -result  {error writing "*": invalid or incomplete\
	multibyte or wide character}

test chan-io-53.1 {CopyData} -setup {
    file delete $path(test1)
} -constraints {fcopy} -body {
    set f1 [open $thisScript]
    set f2 [open $path(test1) w]
    chan configure $f1 -translation lf -blocking 0

Changes to tests/io.test.

7515
7516
7517
7518
7519
7520
7521
7522
7523
7524
7525
7526
7527
7528
7529
7530
7531
7532
7533
7534
7535
7536



7537


7538
7539
7540
7541

7542
7543
7544
7545
7546
7547
7548
    close $in
    close $out

    file size $path(utf8-fcopy.txt)
} 5
test io-52.11 {TclCopyChannel & encodings} -setup {
    set out [open $path(utf8-fcopy.txt) w]
    fconfigure $out -encoding utf-8 -translation lf
    puts $out "АА"
    close $out
} -constraints {fcopy} -body {
    # binary to encoding => the input has to be
    # in utf-8 to make sense to the encoder

    set in  [open $path(utf8-fcopy.txt) r]
    set out [open $path(kyrillic.txt) w]

    # -translation binary is also -encoding binary
    fconfigure $in  -translation binary
    fconfigure $out -encoding koi8-r -translation lf

    fcopy $in $out



    close $in


    close $out

    file size $path(kyrillic.txt)
} -result 3


test io-52.12 {coverage of -translation auto} {
    file delete $path(test1) $path(test2)
    set out [open $path(test1) wb]
    chan configure $out -translation lf
    puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz
    close $out







|
|


<
<
<


<


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







7515
7516
7517
7518
7519
7520
7521
7522
7523
7524
7525



7526
7527

7528
7529
7530

7531
7532
7533
7534
7535
7536
7537
7538
7539
7540
7541
7542
7543
7544
7545
7546
7547
7548
7549
    close $in
    close $out

    file size $path(utf8-fcopy.txt)
} 5
test io-52.11 {TclCopyChannel & encodings} -setup {
    set out [open $path(utf8-fcopy.txt) w]
    fconfigure $out -encoding utf-8 -translation lf -profile strict
    puts $out АА
    close $out
} -constraints {fcopy} -body {



    set in  [open $path(utf8-fcopy.txt) r]
    set out [open $path(kyrillic.txt) w]

    # -translation binary is also -encoding binary
    fconfigure $in  -translation binary
    fconfigure $out -encoding koi8-r -translation lf -profile strict

    catch {fcopy $in $out} cres copts
	return $cres
} -cleanup {
	if {$in in [chan names]} {
		close $in
	}
	if {$out in [chan names]} {
		close $out
	}
    catch {unset cres}
} -match glob -result  {error writing "*": invalid or incomplete\
	multibyte or wide character}

test io-52.12 {coverage of -translation auto} {
    file delete $path(test1) $path(test2)
    set out [open $path(test1) wb]
    chan configure $out -translation lf
    puts -nonewline $out abcdefg\rhijklmn\nopqrstu\r\nvwxyz
    close $out
7775
7776
7777
7778
7779
7780
7781























7782
7783
7784
7785
7786
7787
7788
    vwait ::s0
    set ::s0
} -cleanup {
    close $in
    close $out
    unset ::s0
} -match glob -result {0 {error writing "file*": invalid or incomplete multibyte or wide character}}

























test io-53.1 {CopyData} {fcopy} {
    file delete $path(test1)
    set f1 [open $thisScript]
    set f2 [open $path(test1) w]
    fconfigure $f1 -translation lf -blocking 0







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







7776
7777
7778
7779
7780
7781
7782
7783
7784
7785
7786
7787
7788
7789
7790
7791
7792
7793
7794
7795
7796
7797
7798
7799
7800
7801
7802
7803
7804
7805
7806
7807
7808
7809
7810
7811
7812
    vwait ::s0
    set ::s0
} -cleanup {
    close $in
    close $out
    unset ::s0
} -match glob -result {0 {error writing "file*": invalid or incomplete multibyte or wide character}}

test io-52.24 {fcopy -size should always be characters} -setup {
    set out [open utf8-fcopy-52.24.txt w]
    fconfigure $out -encoding utf-8 -translation lf
    puts $out "Á"
    close $out
} -constraints {fcopy} -body {
    set in  [open utf8-fcopy-52.24.txt r]
    set out [open utf8-fcopy-52.24.out.txt w+]

    fconfigure $in  -encoding utf-8 -profile tcl8
    fconfigure $out -encoding utf-8 -profile tcl8
    fcopy $in $out -size 1
	seek $out 0
	# a result of \xc3 means that only the first byte of the utf-8 encoding of
	# Á made it into to the output file.
	read $out
} -cleanup {
    close $in
    close $out
	catch {file delete utf8-fcopy-52.24.txt}
    catch {file delete utf8-fcopy-52.24.out.txt}
} -result Á


test io-53.1 {CopyData} {fcopy} {
    file delete $path(test1)
    set f1 [open $thisScript]
    set f2 [open $path(test1) w]
    fconfigure $f1 -translation lf -blocking 0