]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/windows/fixups.py
eaf5d5eb9115b95b99935d48c3e0a131fb169ec8
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / windows / fixups.py
1
2 done = False
3
4 def initialize():
5     global done
6     import sys
7     if sys.platform != "win32" or done:
8         return True
9     done = True
10
11     import codecs, re
12     from ctypes import WINFUNCTYPE, windll, POINTER, byref, c_int
13     from ctypes.wintypes import BOOL, HANDLE, DWORD, UINT, LPWSTR, LPCWSTR, LPVOID
14     from allmydata.util import log
15     from allmydata.util.encodingutil import canonical_encoding
16
17     # <https://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx>
18     SetErrorMode = WINFUNCTYPE(UINT, UINT)(("SetErrorMode", windll.kernel32))
19     SEM_FAILCRITICALERRORS = 0x0001
20     SEM_NOOPENFILEERRORBOX = 0x8000
21
22     SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX)
23
24     original_stderr = sys.stderr
25
26     # If any exception occurs in this code, we'll probably try to print it on stderr,
27     # which makes for frustrating debugging if stderr is directed to our wrapper.
28     # So be paranoid about catching errors and reporting them to original_stderr,
29     # so that we can at least see them.
30     def _complain(message):
31         print >>original_stderr, isinstance(message, str) and message or repr(message)
32         log.msg(message, level=log.WEIRD)
33
34     # Work around <http://bugs.python.org/issue6058>.
35     codecs.register(lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
36
37     # Make Unicode console output work independently of the current code page.
38     # This also fixes <http://bugs.python.org/issue1602>.
39     # Credit to Michael Kaplan <https://blogs.msdn.com/b/michkap/archive/2010/04/07/9989346.aspx>
40     # and TZOmegaTZIOY
41     # <http://stackoverflow.com/questions/878972/windows-cmd-encoding-change-causes-python-crash/1432462#1432462>.
42     try:
43         # <https://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx>
44         # HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
45         # returns INVALID_HANDLE_VALUE, NULL, or a valid handle
46         #
47         # <https://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx>
48         # DWORD WINAPI GetFileType(DWORD hFile);
49         #
50         # <https://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx>
51         # BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode);
52
53         GetStdHandle = WINFUNCTYPE(HANDLE, DWORD)(("GetStdHandle", windll.kernel32))
54         STD_OUTPUT_HANDLE = DWORD(-11)
55         STD_ERROR_HANDLE  = DWORD(-12)
56         GetFileType = WINFUNCTYPE(DWORD, DWORD)(("GetFileType", windll.kernel32))
57         FILE_TYPE_CHAR   = 0x0002
58         FILE_TYPE_REMOTE = 0x8000
59         GetConsoleMode = WINFUNCTYPE(BOOL, HANDLE, POINTER(DWORD))(("GetConsoleMode", windll.kernel32))
60         INVALID_HANDLE_VALUE = DWORD(-1).value
61
62         def not_a_console(handle):
63             if handle == INVALID_HANDLE_VALUE or handle is None:
64                 return True
65             return ((GetFileType(handle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR
66                     or GetConsoleMode(handle, byref(DWORD())) == 0)
67
68         old_stdout_fileno = None
69         old_stderr_fileno = None
70         if hasattr(sys.stdout, 'fileno'):
71             old_stdout_fileno = sys.stdout.fileno()
72         if hasattr(sys.stderr, 'fileno'):
73             old_stderr_fileno = sys.stderr.fileno()
74
75         STDOUT_FILENO = 1
76         STDERR_FILENO = 2
77         real_stdout = (old_stdout_fileno == STDOUT_FILENO)
78         real_stderr = (old_stderr_fileno == STDERR_FILENO)
79
80         if real_stdout:
81             hStdout = GetStdHandle(STD_OUTPUT_HANDLE)
82             if not_a_console(hStdout):
83                 real_stdout = False
84
85         if real_stderr:
86             hStderr = GetStdHandle(STD_ERROR_HANDLE)
87             if not_a_console(hStderr):
88                 real_stderr = False
89
90         if real_stdout or real_stderr:
91             # BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars,
92             #                           LPDWORD lpCharsWritten, LPVOID lpReserved);
93
94             WriteConsoleW = WINFUNCTYPE(BOOL, HANDLE, LPWSTR, DWORD, POINTER(DWORD), LPVOID) \
95                                 (("WriteConsoleW", windll.kernel32))
96
97             class UnicodeOutput:
98                 def __init__(self, hConsole, stream, fileno, name):
99                     self._hConsole = hConsole
100                     self._stream = stream
101                     self._fileno = fileno
102                     self.closed = False
103                     self.softspace = False
104                     self.mode = 'w'
105                     self.encoding = 'utf-8'
106                     self.name = name
107                     if hasattr(stream, 'encoding') and canonical_encoding(stream.encoding) != 'utf-8':
108                         log.msg("%s: %r had encoding %r, but we're going to write UTF-8 to it" %
109                                 (name, stream, stream.encoding), level=log.CURIOUS)
110                     self.flush()
111
112                 def isatty(self):
113                     return False
114                 def close(self):
115                     # don't really close the handle, that would only cause problems
116                     self.closed = True
117                 def fileno(self):
118                     return self._fileno
119                 def flush(self):
120                     if self._hConsole is None:
121                         try:
122                             self._stream.flush()
123                         except Exception, e:
124                             _complain("%s.flush: %r from %r" % (self.name, e, self._stream))
125                             raise
126
127                 def write(self, text):
128                     try:
129                         if self._hConsole is None:
130                             if isinstance(text, unicode):
131                                 text = text.encode('utf-8')
132                             self._stream.write(text)
133                         else:
134                             if not isinstance(text, unicode):
135                                 text = str(text).decode('utf-8')
136                             remaining = len(text)
137                             while remaining > 0:
138                                 n = DWORD(0)
139                                 # There is a shorter-than-documented limitation on the length of the string
140                                 # passed to WriteConsoleW (see #1232).
141                                 retval = WriteConsoleW(self._hConsole, text, min(remaining, 10000), byref(n), None)
142                                 if retval == 0 or n.value == 0:
143                                     raise IOError("WriteConsoleW returned %r, n.value = %r" % (retval, n.value))
144                                 remaining -= n.value
145                                 if remaining == 0: break
146                                 text = text[n.value:]
147                     except Exception, e:
148                         _complain("%s.write: %r" % (self.name, e))
149                         raise
150
151                 def writelines(self, lines):
152                     try:
153                         for line in lines:
154                             self.write(line)
155                     except Exception, e:
156                         _complain("%s.writelines: %r" % (self.name, e))
157                         raise
158
159             if real_stdout:
160                 sys.stdout = UnicodeOutput(hStdout, None, STDOUT_FILENO, '<Unicode console stdout>')
161             else:
162                 sys.stdout = UnicodeOutput(None, sys.stdout, old_stdout_fileno, '<Unicode redirected stdout>')
163
164             if real_stderr:
165                 sys.stderr = UnicodeOutput(hStderr, None, STDERR_FILENO, '<Unicode console stderr>')
166             else:
167                 sys.stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno, '<Unicode redirected stderr>')
168     except Exception, e:
169         _complain("exception %r while fixing up sys.stdout and sys.stderr" % (e,))
170
171     # This works around <http://bugs.python.org/issue2128>.
172     GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
173     CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int)) \
174                             (("CommandLineToArgvW", windll.shell32))
175
176     argc = c_int(0)
177     argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
178
179     # Because of <http://bugs.python.org/issue8775> (and similar limitations in
180     # twisted), the 'bin/tahoe' script cannot invoke us with the actual Unicode arguments.
181     # Instead it "mangles" or escapes them using \x7F as an escape character, which we
182     # unescape here.
183     def unmangle(s):
184         return re.sub(ur'\x7F[0-9a-fA-F]*\;', lambda m: unichr(int(m.group(0)[1:-1], 16)), s)
185
186     try:
187         argv = [unmangle(argv_unicode[i]).encode('utf-8') for i in xrange(0, argc.value)]
188     except Exception, e:
189         _complain("%s:  could not unmangle Unicode arguments.\n%r"
190                   % (sys.argv[0], [argv_unicode[i] for i in xrange(0, argc.value)]))
191         raise
192
193     # Take only the suffix with the same number of arguments as sys.argv.
194     # This accounts for anything that can cause initial arguments to be stripped,
195     # for example, the Python interpreter or any options passed to it, or runner
196     # scripts such as 'coverage run'. It works even if there are no such arguments,
197     # as in the case of a frozen executable created by bb-freeze or similar.
198
199     sys.argv = argv[-len(sys.argv):]
200     if sys.argv[0].endswith('.pyscript'):
201         sys.argv[0] = sys.argv[0][:-9]