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:
parent
1fad717aaa
commit
54bbc5325f
|
@ -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):
|
||||||
|
try:
|
||||||
|
if self._hConsole is None:
|
||||||
|
if isinstance(text, unicode):
|
||||||
|
text = text.encode('utf-8')
|
||||||
|
self._stream.write(text)
|
||||||
|
else:
|
||||||
if not isinstance(text, unicode):
|
if not isinstance(text, unicode):
|
||||||
text = str(text).decode('utf-8')
|
text = str(text).decode('utf-8')
|
||||||
for i in xrange(0, len(text), buffer_chars):
|
remaining = len(text)
|
||||||
fputws(text[i:(i+buffer_chars)], self._stream)
|
while remaining > 0:
|
||||||
fflush(self._stream)
|
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):
|
||||||
|
try:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
self.write(line)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue