windows/fixups.py: Don't rely on buggy MSVCRT library for Unicode output, use the Win32 API instead. This should make it work on XP. Also, change how we handle the case where sys.stdout and sys.stderr are redirected, since the .encoding attribute isn't necessarily writeable.

This commit is contained in:
david-sarah 2010-07-25 21:50:19 -07:00
parent 1fad717aaa
commit 54bbc5325f
1 changed files with 105 additions and 57 deletions

View File

@ -8,9 +8,10 @@ def initialize():
return True return True
done = True done = True
original_stderr = sys.stderr
import codecs, re import codecs, re
from ctypes import WINFUNCTYPE, windll, CFUNCTYPE, cdll, POINTER, byref, \ from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
c_wchar_p, c_char_p, c_void_p, c_int, c_size_t from ctypes.wintypes import BOOL, HANDLE, DWORD, LPWSTR, LPCWSTR, LPVOID
from allmydata.util import log from allmydata.util import log
from allmydata.util.encodingutil import canonical_encoding from allmydata.util.encodingutil import canonical_encoding
@ -19,99 +20,146 @@ def initialize():
# Make Unicode console output work independently of the current code page. # Make Unicode console output work independently of the current code page.
# This also fixes <http://bugs.python.org/issue1602>. # This also fixes <http://bugs.python.org/issue1602>.
# Credit to Michael Kaplan <http://blogs.msdn.com/b/michkap/archive/2008/03/18/8306597.aspx> # Credit to Michael Kaplan <http://blogs.msdn.com/b/michkap/archive/2010/04/07/9989346.aspx>
# and TZOmegaTZIOY # and TZOmegaTZIOY
# <http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>. # <http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>.
try: try:
# <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx>
# HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
# returns INVALID_HANDLE_VALUE, NULL, or a valid handle
#
# <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx>
# DWORD WINAPI GetFileType(DWORD hFile);
#
# <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx>
# BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode);
GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32))
STD_OUTPUT_HANDLE = DWORD(-11)
STD_ERROR_HANDLE = DWORD(-12)
GetFileType = WINFUNCTYPE(DWORD, DWORD)(("GetFileType", windll.kernel32))
FILE_TYPE_CHAR = 0x0002
FILE_TYPE_REMOTE = 0x8000
GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(("GetConsoleMode", windll.kernel32))
INVALID_HANDLE_VALUE = DWORD(-1).value
def not_a_console(handle):
if handle == INVALID_HANDLE_VALUE or handle is None:
return True
return ((GetFileType(handle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR
or GetConsoleMode(handle, byref(DWORD())) == 0)
old_stdout_fileno = None
old_stderr_fileno = None
if hasattr(sys.stdout, 'fileno'):
old_stdout_fileno = sys.stdout.fileno()
if hasattr(sys.stderr, 'fileno'):
old_stderr_fileno = sys.stderr.fileno()
STDOUT_FILENO = 1 STDOUT_FILENO = 1
STDERR_FILENO = 2 STDERR_FILENO = 2
real_stdout = hasattr(sys.stdout, 'fileno') and sys.stdout.fileno() == STDOUT_FILENO real_stdout = (old_stdout_fileno == STDOUT_FILENO)
real_stderr = hasattr(sys.stderr, 'fileno') and sys.stderr.fileno() == STDERR_FILENO real_stderr = (old_stderr_fileno == STDERR_FILENO)
def force_utf8(stream, name): if real_stdout:
if hasattr(stream, 'encoding') and canonical_encoding(stream.encoding) != 'utf-8': hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
log.msg("%s (%r) had encoding %r, but we're going to write UTF-8 to it" % if not_a_console(hStdout):
(name, stream, stream.encoding), level=log.CURIOUS) real_stdout = False
stream.encoding = 'utf-8'
if not real_stdout: if real_stderr:
force_utf8(sys.stdout, "sys.stdout") hStderr = GetStdHandle(STD_ERROR_HANDLE)
if not_a_console(hStderr):
if not real_stderr: real_stderr = False
force_utf8(sys.stderr, "sys.stderr")
if real_stdout or real_stderr: if real_stdout or real_stderr:
# FILE * _fdopen(int fd, const char *mode); # BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars,
# #define _IOLBF 0x0040 # LPDWORD lpCharsWritten, LPVOID lpReserved);
# int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
# #define _O_U8TEXT 0x40000
# int _setmode(int fd, int mode);
# int fputws(const wchar_t *ws, FILE *stream);
# int fflush(FILE *stream);
c_runtime = cdll.msvcrt WriteConsoleW = WINFUNCTYPE(BOOL, HANDLE, LPWSTR, DWORD, POINTER(DWORD), LPVOID) \
NULL = None (("WriteConsoleW", windll.kernel32))
_fdopen = CFUNCTYPE(c_void_p, c_int, c_char_p)(("_fdopen", c_runtime))
_IOLBF = 0x0040
setvbuf = CFUNCTYPE(c_int, c_void_p, c_char_p, c_int, c_size_t)(("setvbuf", c_runtime))
_O_U8TEXT = 0x40000
_setmode = CFUNCTYPE(c_int, c_int, c_int)(("_setmode", c_runtime))
fputws = CFUNCTYPE(c_int, c_wchar_p, c_void_p)(("fputws", c_runtime));
fflush = CFUNCTYPE(c_int, c_void_p)(("fflush", c_runtime));
buffer_chars = 1024 # If any exception occurs in this code, we'll probably try to print it on stderr,
# which makes for frustrating debugging if stderr is directed to this code.
# So be paranoid about catching errors and reporting them to original_stderr,
# so that we can at least see them.
class UnicodeOutput: class UnicodeOutput:
def __init__(self, fileno, name): def __init__(self, hConsole, stream, fileno, name):
self._stream = _fdopen(fileno, "w") self._hConsole = hConsole
assert self._stream is not NULL self._stream = stream
# Deep magic. MSVCRT supports writing wide-oriented output to stdout/stderr
# to the console using the Unicode APIs, but it does the conversion in the
# stdio buffer, so you need that buffer to be as large as the maximum amount
# you're going to write in a single call (in bytes, not characters).
setvbuf(self._stream, NULL, _IOLBF, buffer_chars*4 + 100)
_setmode(fileno, _O_U8TEXT)
self._fileno = fileno self._fileno = fileno
self.closed = False self.closed = False
self.softspace = False self.softspace = False
self.mode = 'w' self.mode = 'w'
self.encoding = 'utf-8' self.encoding = 'utf-8'
self.name = name self.name = name
if hasattr(stream, 'encoding') and canonical_encoding(stream.encoding) != 'utf-8':
log.msg("%s (%r) had encoding %r, but we're going to write UTF-8 to it" %
(name, stream, stream.encoding), level=log.CURIOUS)
self.flush()
def isatty(self): def isatty(self):
return False return False
def close(self): def close(self):
# don't really close the handle, that would only cause problems
self.closed = True self.closed = True
self.flush()
def fileno(self): def fileno(self):
return self._fileno return self._fileno
def flush(self): def flush(self):
fflush(self._stream) if self._hConsole is None:
try:
self._stream.flush()
except Exception, e:
print >>original_stderr, repr(e)
raise
def write(self, text): def write(self, text):
if not isinstance(text, unicode): try:
text = str(text).decode('utf-8') if self._hConsole is None:
for i in xrange(0, len(text), buffer_chars): if isinstance(text, unicode):
fputws(text[i:(i+buffer_chars)], self._stream) text = text.encode('utf-8')
fflush(self._stream) self._stream.write(text)
else:
if not isinstance(text, unicode):
text = str(text).decode('utf-8')
remaining = len(text)
while remaining > 0:
n = DWORD(0)
retval = WriteConsoleW(self._hConsole, text, remaining, byref(n), None)
if retval == 0 or n.value == 0:
raise IOError("could not write to %s [WriteConsoleW returned %r, n.value = %r]"
% (self.name, retval, n.value))
remaining -= n.value
if remaining == 0: break
text = text[n.value:]
except Exception, e:
print >>original_stderr, repr(e)
raise
def writelines(self, lines): def writelines(self, lines):
for line in lines: try:
self.write(line) for line in lines:
self.write(line)
except Exception, e:
print >>original_stderr, repr(e)
raise
if real_stdout: if real_stdout:
sys.stdout = UnicodeOutput(STDOUT_FILENO, '<Unicode stdout>') sys.stdout = UnicodeOutput(hStdout, None, STDOUT_FILENO, '<Unicode console stdout>')
else:
sys.stdout = UnicodeOutput(None, sys.stdout, old_stdout_fileno, '<Unicode redirected stdout>')
if real_stderr: if real_stderr:
sys.stderr = UnicodeOutput(STDERR_FILENO, '<Unicode stderr>') sys.stderr = UnicodeOutput(hStderr, None, STDERR_FILENO, '<Unicode console stderr>')
else:
sys.stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno, '<Unicode redirected stdout>')
except Exception, e: except Exception, e:
log.msg("exception %r while fixing up sys.stdout and sys.stderr" % (e,), level=log.WEIRD) print >>original_stderr, "exception %r while fixing up sys.stdout and sys.stderr" % (e,)
log.msg("exception %r while fixing up sys.stdout and sys.stderr" % (e,), log.WEIRD)
# Unmangle command-line arguments. # Unmangle command-line arguments.
GetCommandLineW = WINFUNCTYPE(c_wchar_p)(("GetCommandLineW", windll.kernel32)) GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
CommandLineToArgvW = WINFUNCTYPE(POINTER(c_wchar_p), c_wchar_p, POINTER(c_int)) \ CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int)) \
(("CommandLineToArgvW", windll.shell32)) (("CommandLineToArgvW", windll.shell32))
argc = c_int(0) argc = c_int(0)