• Russ Cox's avatar
    os: fix handling of Windows Unicode console input and ^Z · 610d5221
    Russ Cox authored
    Go 1.5 worked with Unicode console input but not ^Z.
    Go 1.6 did not work with Unicode console input but did handle one ^Z case.
    Go 1.7 did not work with Unicode console input but did handle one ^Z case.
    
    The intent of this CL is for Go 1.8 to work with Unicode console input
    and also handle all ^Z cases.
    
    Here's a simple test program for reading from the console.
    It prints a "> " prompt, calls read, prints what it gets, and repeats.
    
    	package main
    
    	import (
    	    "fmt"
    	    "os"
    	)
    
    	func main() {
    	    p := make([]byte, 100)
    	    fmt.Printf("> ")
    	    for {
    	        n, err := os.Stdin.Read(p)
    	        fmt.Printf("[%d %q %v]\n> ", n, p[:n], err)
    	    }
    	}
    
    On Unix, typing a ^D produces a break in the input stream.
    If the ^D is at the beginning of a line, then the 0 bytes returned
    appear as an io.EOF:
    
    	$ go run /tmp/x.go
    	> hello
    	[6 "hello\n" <nil>]
    	> hello^D[5 "hello" <nil>]
    	> ^D[0 "" EOF]
    	> ^D[0 "" EOF]
    	> hello^Dworld
    	[5 "hello" <nil>]
    	> [6 "world\n" <nil>]
    	>
    
    On Windows, the EOF character is ^Z, not ^D, and there has
    been a long-standing problem that in Go programs, ^Z on Windows
    does not behave in the expected way, namely like ^D on Unix.
    Instead, the ^Z come through as literal ^Z characters:
    
    	C:\>c:\go1.5.4\bin\go run x.go
    	> ^Z
    	[3 "\x1a\r\n" <nil>]
    	> hello^Zworld
    	[13 "hello\x1aworld\r\n" <nil>]
    	>
    
    CL 4310 attempted to fix this bug, then known as #6303,
    by changing the use of ReadConsole to ReadFile.
    This CL was released as part of Go 1.6 and did fix the case
    of a ^Z by itself, but not as part of a larger input:
    
    	C:\>c:\go1.6.3\bin\go run x.go
    	> ^Z
    	[0 "" EOF]
    	> hello^Zworld
    	[13 "hello\x1aworld\r\n" <nil>]
    	>
    
    So the fix was incomplete.
    Worse, the fix broke Unicode console input.
    
    ReadFile does not handle Unicode console input correctly.
    To handle Unicode correctly, programs must use ReadConsole.
    Early versions of Go used ReadFile to read the console,
    leading to incorrect Unicode handling, which was filed as #4760
    and fixed in CL 7312053, which switched to ReadConsole
    and was released as part of Go 1.1 and still worked as of Go 1.5:
    
    	C:\>c:\go1.5.4\bin\go run x.go
    	> hello
    	[7 "hello\r\n" <nil>]
    	> hello world
    	[16 "hello world\r\n" <nil>]
    	>
    
    But in Go 1.6:
    
    	C:\>c:\go1.6.3\bin\go run x.go
    	> hello
    	[7 "hello\r\n" <nil>]
    	> hello world
    	[0 "" EOF]
    	>
    
    That is, changing back to ReadFile in Go 1.6 reintroduced #4760,
    which has been refiled as #17097. (We have no automated test
    for this because we don't know how to simulate console input
    in a test: it appears that one must actually type at a keyboard
    to use the real APIs. This CL at least adds a comment warning
    not to reintroduce ReadFile again.)
    
    CL 29493 attempted to fix #17097, but it was not a complete fix:
    the hello world example above still fails, as does Shift-JIS input,
    which was filed as #17939.
    
    CL 29493 also broke ^Z handling, which was filed as #17427.
    
    This CL attempts the never before successfully performed trick
    of simultaneously fixing Unicode console input and ^Z handling.
    It changes the console input to use ReadConsole again,
    as in Go 1.5, which seemed to work for all known Unicode input.
    Then it adds explicit handling of ^Z in the input stream.
    (In the case where standard input is a redirected file, ^Z processing
    should not happen, and it does not, because this code path is only
    invoked when standard input is the console.)
    
    With this CL:
    
    	C:\>go run x.go
    	> hello
    	[7 "hello\r\n" <nil>]
    	> hello world
    	[16 "hello world\r\n" <nil>]
    	> ^Z
    	[0 "" EOF]
    	> [2 "\r\n" <nil>]
    	> hello^Zworld
    	[5 "hello" <nil>]
    	> [0 "" EOF]
    	> [7 "world\r\n" <nil>]
    
    This almost matches Unix:
    
    	$ go run /tmp/x.go
    	> hello
    	[6 "hello\n" <nil>]
    	> hello world
    	[15 "hello world\n" <nil>]
    	> ^D
    	[0 "" EOF]
    	> [1 "\n" <nil>]
    	> hello^Dworld
    	[5 "hello" <nil>]
    	> [6 "world\n" <nil>]
    	>
    
    The difference is in the handling of hello^Dworld / hello^Zworld.
    On Unix, hello^Dworld terminates the read of hello but does not
    result in a zero-length read between reading hello and world.
    This is dictated by the tty driver, not any special Go code.
    
    On Windows, in this CL, hello^Zworld inserts a zero length read
    result between hello and world, which is treated as an interior EOF.
    This is implemented by the Go code in this CL, but it matches the
    handling of ^Z on the console in other programs:
    
    	C:\>copy con x.txt
    	hello^Zworld
    	        1 file(s) copied.
    
    	C:\>type x.txt
    	hello
    	C:\>
    
    A natural question is how to test all this. As noted above, we don't
    know how to write automated tests using the actual Windows console.
    CL 29493 introduced the idea of substituting a different syscall.ReadFile
    implementation for testing; this CL continues that idea but substituting
    for syscall.ReadConsole instead. To avoid the regression of putting
    ReadFile back, this CL adds a comment warning against that.
    
    Fixes #17427.
    Fixes #17939.
    
    Change-Id: Ibaabd0ceb2d7af501d44ac66d53f64aba3944142
    Reviewed-on: https://go-review.googlesource.com/33451
    Run-TryBot: Russ Cox <rsc@golang.org>
    Reviewed-by: 's avatarQuentin Smith <quentin@golang.org>
    Reviewed-by: 's avatarAlex Brainman <alex.brainman@gmail.com>
    TryBot-Result: Gobot Gobot <gobot@golang.org>
    610d5221
Name
Last commit
Last update
..
exec Loading commit data...
signal Loading commit data...
user Loading commit data...
dir.go Loading commit data...
dir_plan9.go Loading commit data...
dir_unix.go Loading commit data...
dir_windows.go Loading commit data...
env.go Loading commit data...
env_test.go Loading commit data...
env_unix_test.go Loading commit data...
error.go Loading commit data...
error_plan9.go Loading commit data...
error_test.go Loading commit data...
error_unix.go Loading commit data...
error_unix_test.go Loading commit data...
error_windows.go Loading commit data...
error_windows_test.go Loading commit data...
example_test.go Loading commit data...
exec.go Loading commit data...
exec_plan9.go Loading commit data...
exec_posix.go Loading commit data...
exec_unix.go Loading commit data...
exec_windows.go Loading commit data...
executable.go Loading commit data...
executable_darwin.go Loading commit data...
executable_freebsd.go Loading commit data...
executable_plan9.go Loading commit data...
executable_procfs.go Loading commit data...
executable_solaris.go Loading commit data...
executable_test.go Loading commit data...
executable_windows.go Loading commit data...
export_test.go Loading commit data...
export_windows_test.go Loading commit data...
file.go Loading commit data...
file_plan9.go Loading commit data...
file_posix.go Loading commit data...
file_unix.go Loading commit data...
file_windows.go Loading commit data...
getwd.go Loading commit data...
getwd_darwin.go Loading commit data...
os_test.go Loading commit data...
os_unix_test.go Loading commit data...
os_windows_test.go Loading commit data...
path.go Loading commit data...
path_plan9.go Loading commit data...
path_test.go Loading commit data...
path_unix.go Loading commit data...
path_windows.go Loading commit data...
path_windows_test.go Loading commit data...
pipe_bsd.go Loading commit data...
pipe_linux.go Loading commit data...
pipe_test.go Loading commit data...
proc.go Loading commit data...
stat_darwin.go Loading commit data...
stat_dragonfly.go Loading commit data...
stat_freebsd.go Loading commit data...
stat_linux.go Loading commit data...
stat_nacl.go Loading commit data...
stat_netbsd.go Loading commit data...
stat_openbsd.go Loading commit data...
stat_plan9.go Loading commit data...
stat_solaris.go Loading commit data...
stat_unix.go Loading commit data...
stat_windows.go Loading commit data...
sticky_bsd.go Loading commit data...
sticky_notbsd.go Loading commit data...
str.go Loading commit data...
sys.go Loading commit data...
sys_bsd.go Loading commit data...
sys_darwin.go Loading commit data...
sys_freebsd.go Loading commit data...
sys_linux.go Loading commit data...
sys_nacl.go Loading commit data...
sys_plan9.go Loading commit data...
sys_solaris.go Loading commit data...
sys_unix.go Loading commit data...
sys_windows.go Loading commit data...
types.go Loading commit data...
types_plan9.go Loading commit data...
types_unix.go Loading commit data...
types_windows.go Loading commit data...
wait_unimp.go Loading commit data...
wait_wait6.go Loading commit data...
wait_waitid.go Loading commit data...