classification
Title: sys.stdout.isatty() returns True even if redirected to NUL
Type: behavior Stage: needs patch
Components: IO, Windows Versions: Python 3.10, Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: eryksun, kmeyer, paul.moore, socketpair, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2016-11-10 08:18 by kmeyer, last changed 2021-02-25 14:05 by eryksun.

Messages (6)
msg280494 - (view) Author: Kuno Meyer (kmeyer) Date: 2016-11-10 08:18
[Python 3.5.2 on Windows]

>py -c "import sys; print(sys.stdout.isatty(), file=sys.stderr)" > NUL
True

NUL is the Windows equivalent of /dev/nul, so the result should probably be False.

Under Cygwin, the result is as expected:

$ python3 -c "import sys; print(sys.stdout.isatty(), file=sys.stderr)" > /dev/nul
False
msg280498 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2016-11-10 09:00
Windows doesn't have terminal devices, so the C runtime's isatty() function just checks for a character device, which includes NUL among others. 

This can lead to hanging the REPL, albeit with a contrived example such as redirecting stdin to COM3:

    C:\>python < COM3
    Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) 
    [MSC v.1900 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> ^C

Ctrl+C doesn't work because the main thread is blocked (we'd have to call CancelSynchronousIo in the C signal handler). I killed the process using the default Ctrl+Break handler. "^C" was printed by the cmd shell's Ctrl+Break handler.

I don't think the CRT's check for a character device is generally useful. It could be replaced with a check that specifically looks for a console handle (e.g. by calling GetConsoleMode), which is what someone calling isatty() generally wants to know.
msg280504 - (view) Author: Kuno Meyer (kmeyer) Date: 2016-11-10 12:57
http://stackoverflow.com/questions/3648711/detect-nul-file-descriptor-isatty-is-bogus, although a bit convoluted, might also help. It mentions GetConsoleMode() for stdin and GetConsoleScreenBufferInfo() for stdout.
msg280508 - (view) Author: Марк Коренберг (socketpair) * Date: 2016-11-10 14:25
1. I think, that is not a bug.
2. It seems, that for Cygwin, `> /dev/nul` is a bug, it should be `/dev/null` does not it ?
msg280564 - (view) Author: Kuno Meyer (kmeyer) Date: 2016-11-11 07:02
2) Yes, it should be `> /dev/null` instead of `> /dev/nul` (my bad). The output remains the same.
msg387673 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2021-02-25 14:05
Here's an example of how to change the isatty() method in Modules/_io/fileio.c to check for a console in Windows:

    _io_FileIO_isatty_impl(fileio *self)
    {
        long res;

        if (self->fd < 0)
            return err_closed();
        Py_BEGIN_ALLOW_THREADS
        _Py_BEGIN_SUPPRESS_IPH
        res = isatty(self->fd);
    #ifdef MS_WINDOWS
        if (res) {
            DWORD mode;
            HANDLE h = (HANDLE)_get_osfhandle(self->fd);
            // It's a console if GetConsoleMode succeeds or if the last error
            // isn't ERROR_INVALID_HANDLE. e.g., if 'con' is opened with 'w'
            // mode, the error is ERROR_ACCESS_DENIED.
            res = GetConsoleMode(h, &mode) ||
                    GetLastError() != ERROR_INVALID_HANDLE;
        }
    #endif
        _Py_END_SUPPRESS_IPH
        Py_END_ALLOW_THREADS
        return PyBool_FromLong(res);
    }
History
Date User Action Args
2021-02-25 14:05:07eryksunsettype: behavior
stage: needs patch
messages: + msg387673
versions: + Python 3.9, Python 3.10, - Python 3.5, Python 3.6, Python 3.7
2016-11-11 07:02:40kmeyersetmessages: + msg280564
2016-11-10 14:25:23socketpairsetnosy: + socketpair
messages: + msg280508
2016-11-10 12:57:45kmeyersetmessages: + msg280504
2016-11-10 09:00:23eryksunsetversions: + Python 3.6, Python 3.7
nosy: + eryksun

messages: + msg280498

components: + IO
2016-11-10 08:18:38kmeyercreate