0

I am aware that sys.stdout is a Python object that wraps the output file handle but I am wondering if those file handles are "synced" and always the same?

For example, say sys.stdout.isatty() is True. I call GetStdHandle(-11) (-11 is STDOUT on Windows) and then some Windows Console API that fails and find that the error's errno is 6 (The handle is invalid). AFAIK, this means that the handle is not a valid console handle. In that case, they are not "synced". In other words, is it possible to redirect sys.stdout while the STDOUT handle returned by GetStdHandle is not redirected? My code uses GetStdHandle so ultimately I should test for errno 6 but it would be nice if I could just rely on sys.stdout.isatty.

Here is an example (I don't have access to a windows machine at the moment but hopefully the code is correct). Run with and without redirection (or normally and within a call to subprocess.check_output.

import sys
from ctypes import WinError, wintypes

STDOUT = -11
ERROR_INVALID_HANDLE = 6
kernel32 = ctypes.WinDLL('kernel32', use_errno=True, use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)

# Assume we set argtypes/restype for all win api functions here

if handle == wintypes.HANDLE(-1).value:
    raise WinError()

console_mode = wintypes.DWORD(0)

# We use GetConsoleMode here but it could be any function that expects a
# valid console handle
retval = kernel32.GetConsoleMode(handle, ctypes.byref(console_mode))

# Are the following assertions always true?
if retval == 0:
    errno = ctypes.get_last_error()

    if errno == ERROR_INVALID_HANDLE:
        print('Invalid handle')
        assert not sys.stdout.isatty()
    else:
        # Another error happened
        raise WinError()
else:
    assert sys.stdout.isatty()

I tried to scour the CPython source code but could not find anything that could confirm or deny this. Perhaps someone more experienced with the codebase could point me in the right direction?

EDIT: I know about the CONOUT$ + CreateFile API. I am not interested in getting the input or output handle under redirection but in understanding the relationship between the Windows console handle APIs and sys.stdout.

NordCoder
  • 403
  • 1
  • 7
  • 21
  • `this means that the handle is not a valid console handle.` Do you mean that the handle returned by `GetStdHandle` is invalid in some console APIs? Can you be more specific? For example, add some codes. – Strive Sun Jan 18 '21 at 07:32
  • As I understand it, the handle returned by `GetStdHandle` is only invalid for the console APIs if stdout (or stderr) has been redirected since the output stream is no longer to a console which causes `The handle is invalid` errors (errno = 6). I updated the question with some example code. – NordCoder Jan 19 '21 at 09:02

1 Answers1

1

Yes, I can reproduce this problem in C++.

You can use CreateFile to get the output handle of the console,and then use the handle as a parameter when calling the windows console apis .

The CreateFile function enables a process to get a handle to its console's input buffer and active screen buffer, even if STDIN and STDOUT have been redirected. To open a handle to a console's input buffer, specify the CONIN$ value in a call to CreateFile. Specify the CONOUT$ value in a call to CreateFile to open a handle to a console's active screen buffer. CreateFile enables you to specify the read/write access of the handle that it returns.

Refer: Console Handles

In C++ it looks like this,

 HANDLE hConsole = CreateFile("CONOUT$",
        GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

It works well, and you can convert it to python code as needed.

Updated:

import sys
import ctypes
from ctypes import WinError, wintypes

STDOUT = -11
sys.stdout = open('test.txt', 'w')
kernel32 = ctypes.WinDLL('kernel32', use_errno=True, use_last_error=True)
handle = kernel32.GetStdHandle(STDOUT)
if handle == wintypes.HANDLE(-1).value:
    raise WinError()

console_mode = wintypes.DWORD(0)
retval = kernel32.GetConsoleMode(handle, ctypes.byref(console_mode))
print(retval)

if sys.stdout.isatty():
    print('You are running in a real terminal')
else:
    print('You are being piped or redirected')

retval returns 1. They will all be printed in test.txt.

enter image description here

When you delete sys.stdout = open('test.txt', 'w').

enter image description here

Strive Sun
  • 5,988
  • 1
  • 9
  • 26
  • Thanks for your answer. Are you saying you can verify my example on Windows? I know about "CONOUT$" and "CONIN$" but I am not trying to get a valid console handle in the presence of redirection. I am trying to verify if a redirected program will have both `sys.stdout.isatty() == False` and return the "The handle is invalid" when calling console API functions with a handle returned from `GetStdHandle` (because of redirection). Also, is the inverse situation true if the program is not redirected? – NordCoder Jan 21 '21 at 20:58
  • @NordCoder If the `stdout` is redirected, `sys.stdout.isatty()` will return false. As [the link](https://stackoverflow.com/a/14382806/11128312) you provided pointed out, `sys.stdout` is not actually the standard output file handle, it wraps that file handle. In other words, in python, `GetStdHandle(-11)` and `sys.stdout` are not the same thing. In C++, if the stdout is redirectd, `GetConsoleMode` will return 0 which means invailed handle. But there is no `sys.stdout` in C++. I added a test sample in answer. We can simply verify. – Strive Sun Jan 22 '21 at 02:55
  • 1
    Thanks for the additional example. That is a surprising result. It seems that it is better then to rely on `sys.stdout.isatty()` for detecting redirection than on calling any console API function and checking that the error number is 6. – NordCoder Jan 31 '21 at 16:31