Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect OSError raised when redirecting to NUL on Windows #1065

Closed
uranusjr opened this issue Jul 10, 2018 · 2 comments
Closed

Incorrect OSError raised when redirecting to NUL on Windows #1065

uranusjr opened this issue Jul 10, 2018 · 2 comments
Labels
windows Issues pertaining to the Windows environment
Milestone

Comments

@uranusjr
Copy link

uranusjr commented Jul 10, 2018

Reproducible for both Python 3.6 and 3.7. I did not test against older versions.

Tested on the Click 6.x series. Windows 10, Build 17134.

Steps to reproduce:

# t.py
import click

@click.command()
def main():
    click.echo('run')

if __name__ == '__main__':
    main()
> python t.py
run

> python t.py >NUL
Traceback (most recent call last):
  File "t.py", line 10, in <module>
    main()
  File ".venv\lib\site-packages\click\core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File ".venv\lib\site-packages\click\core.py", line 697, in main
    rv = self.invoke(ctx)
  File ".venv\lib\site-packages\click\core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File ".venv\lib\site-packages\click\core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "t.py", line 6, in main
    click.echo('run')
  File ".venv\lib\site-packages\click\utils.py", line 259, in echo
    file.write(message)
  File ".venv\lib\site-packages\click\_winconsole.py", line 180, in write
    return self._text_stream.write(x)
  File ".venv\lib\site-packages\click\_winconsole.py", line 164, in write
    raise OSError(self._get_error_message(GetLastError()))
OSError: Windows error 1

I believe the problem is that Click tries to be helpful, and raises an error when there are not bytes written when there should be. This is, however, exactly what is expected to happen when you redirect things to NUL on Windows.

Curiously, however, Windows seems to act a little differently for stderr. 2>NUL works as expected.

@uranusjr
Copy link
Author

I added an additional check based on MSDN’s suggestion:

--- a/click/_winconsole.py
+++ b/click/_winconsole.py
@@ -33,6 +33,8 @@ kernel32 = windll.kernel32
 GetStdHandle = kernel32.GetStdHandle
 ReadConsoleW = kernel32.ReadConsoleW
 WriteConsoleW = kernel32.WriteConsoleW
+WriteFile = kernel32.WriteFile
+GetConsoleMode = kernel32.GetConsoleMode
 GetLastError = kernel32.GetLastError
 GetCommandLineW = WINFUNCTYPE(LPWSTR)(
     ('GetCommandLineW', windll.kernel32))
@@ -156,7 +158,11 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
                                        MAX_BYTES_WRITTEN) // 2
         code_units_written = c_ulong()

-        WriteConsoleW(self.handle, buf, code_units_to_be_written,
+        if GetConsoleMode(self.handle, byref(c_ulong())):
+            WriteConsoleW(self.handle, buf, code_units_to_be_written,
+                          byref(code_units_written), None)
+        else:
+            WriteFile(self.handle, buf, code_units_to_be_written,
                       byref(code_units_written), None)
         bytes_written = 2 * code_units_written.value

This checks if the handle is actually a console with GetConsoleMode. If that succeeds (no matter what the mode is). it is a console, and we can safely use WriteConsoleW. Otherwise we should use WriteFile instead. This seems to work, according to my simple tests.

I am not very familiar with Windows API, and would like some input whether this is actually a good idea.

@jcrotts jcrotts added the windows Issues pertaining to the Windows environment label Aug 3, 2018
@segevfiner
Copy link
Contributor

We shouldn't even be using this code if stdio is redirected, but Windows, being Windows, catches us off guard here: https://bugs.python.org/issue28654.

In essence, isatty returns True for the NUL device on Windows!

CPython itself uses GetConsoleMode to test if the HANDLE/fd is a console, so the right fix will be to use GetConsoleMode instead of isatty when checking if we should use our Unicode console hack implementation.

P.S. @davidism Why is this code used on CPython 3.6+ where the Windows Unicode console hack is implemented by CPython itself?

segevfiner added a commit to segevfiner/click that referenced this issue Oct 7, 2018
isatty returns True for the NUL device on Windows. So use GetConsoleMode instead to detect
a console handle.

See https://bugs.python.org/issue28654

Fixes pallets#1065
segevfiner added a commit to segevfiner/click that referenced this issue Oct 7, 2018
isatty returns True for the NUL device on Windows. So use GetConsoleMode instead to detect
a console handle.

See https://bugs.python.org/issue28654

Fixes pallets#1065
davidism pushed a commit to segevfiner/click that referenced this issue Feb 16, 2020
isatty returns True for the NUL device on Windows. So use GetConsoleMode instead to detect
a console handle.

See https://bugs.python.org/issue28654

Fixes pallets#1065
@davidism davidism added this to the 7.1 milestone Feb 16, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
windows Issues pertaining to the Windows environment
Projects
None yet
Development

No branches or pull requests

4 participants