1 |
/* PCSX2 - PS2 Emulator for PCs |
2 |
* Copyright (C) 2002-2010 PCSX2 Dev Team |
3 |
* |
4 |
* PCSX2 is free software: you can redistribute it and/or modify it under the terms |
5 |
* of the GNU Lesser General Public License as published by the Free Software Found- |
6 |
* ation, either version 3 of the License, or (at your option) any later version. |
7 |
* |
8 |
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
9 |
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
10 |
* PURPOSE. See the GNU General Public License for more details. |
11 |
* |
12 |
* You should have received a copy of the GNU General Public License along with PCSX2. |
13 |
* If not, see <http://www.gnu.org/licenses/>. |
14 |
*/ |
15 |
|
16 |
#include "PrecompiledHeader.h" |
17 |
#include "Win32.h" |
18 |
|
19 |
#include "App.h" |
20 |
#include "ConsoleLogger.h" |
21 |
|
22 |
// -------------------------------------------------------------------------------------- |
23 |
// Win32 Console Pipes |
24 |
// As a courtesy and convenience, we redirect stdout/stderr to the console and logfile. |
25 |
// -------------------------------------------------------------------------------------- |
26 |
|
27 |
using namespace Threading; |
28 |
|
29 |
// -------------------------------------------------------------------------------------- |
30 |
// WinPipeThread |
31 |
// -------------------------------------------------------------------------------------- |
32 |
class WinPipeThread : public pxThread |
33 |
{ |
34 |
typedef pxThread _parent; |
35 |
|
36 |
protected: |
37 |
const HANDLE& m_outpipe; |
38 |
const ConsoleColors m_color; |
39 |
|
40 |
public: |
41 |
WinPipeThread( const HANDLE& outpipe, ConsoleColors color ) |
42 |
: m_outpipe( outpipe ) |
43 |
, m_color( color ) |
44 |
{ |
45 |
m_name = (m_color == Color_Red) ? L"Redirect_Stderr" : L"Redirect_Stdout"; |
46 |
} |
47 |
|
48 |
virtual ~WinPipeThread() throw() |
49 |
{ |
50 |
_parent::Cancel(); |
51 |
} |
52 |
|
53 |
protected: |
54 |
void ExecuteTaskInThread() |
55 |
{ |
56 |
::SetThreadPriority( ::GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL ); |
57 |
if( m_outpipe == INVALID_HANDLE_VALUE ) return; |
58 |
|
59 |
try |
60 |
{ |
61 |
char s8_Buf[2049]; |
62 |
DWORD u32_Read = 0; |
63 |
|
64 |
while( true ) |
65 |
{ |
66 |
if( !ReadFile(m_outpipe, s8_Buf, sizeof(s8_Buf)-1, &u32_Read, NULL) ) |
67 |
{ |
68 |
DWORD result = GetLastError(); |
69 |
if( result == ERROR_HANDLE_EOF || result == ERROR_BROKEN_PIPE ) break; |
70 |
if( result == ERROR_IO_PENDING ) |
71 |
{ |
72 |
Yield( 10 ); |
73 |
continue; |
74 |
} |
75 |
|
76 |
throw Exception::WinApiError().SetDiagMsg(L"ReadFile from pipe failed."); |
77 |
} |
78 |
|
79 |
if( u32_Read <= 3 ) |
80 |
{ |
81 |
// Windows has a habit of sending 1 or 2 characters of every message, and then sending |
82 |
// the rest in a second message. This is "ok" really, except our Console class is hardly |
83 |
// free of overhead, so it's helpful if we can concatenate the couple of messages together. |
84 |
// But we don't want to break the ability to print progressive status bars, like '....' |
85 |
// so I use a clever Yield/Peek loop combo that keeps reading as long as there's new data |
86 |
// immediately being fed to our pipe. :) --air |
87 |
|
88 |
DWORD u32_avail = 0; |
89 |
|
90 |
do |
91 |
{ |
92 |
Yield(); |
93 |
if( !PeekNamedPipe(m_outpipe, 0, 0, 0, &u32_avail, 0) ) |
94 |
throw Exception::WinApiError().SetDiagMsg(L"Error peeking Pipe."); |
95 |
|
96 |
if( u32_avail == 0 ) break; |
97 |
|
98 |
DWORD loopread; |
99 |
if( !ReadFile(m_outpipe, &s8_Buf[u32_Read], sizeof(s8_Buf)-u32_Read-1, &loopread, NULL) ) break; |
100 |
u32_Read += loopread; |
101 |
|
102 |
} while( u32_Read < sizeof(s8_Buf)-32 ); |
103 |
} |
104 |
|
105 |
// ATTENTION: The Console always prints ANSI to the pipe independent if compiled as UNICODE or MBCS! |
106 |
s8_Buf[u32_Read] = 0; |
107 |
|
108 |
ConsoleColorScope cs(m_color); |
109 |
Console.DoWriteFromStdout( fromUTF8(s8_Buf) ); |
110 |
|
111 |
TestCancel(); |
112 |
} |
113 |
} |
114 |
catch( Exception::RuntimeError& ex ) |
115 |
{ |
116 |
// Log error, and fail silently. It's not really important if the |
117 |
// pipe fails. PCSX2 will run fine without it in any case. |
118 |
Console.Error( ex.FormatDiagnosticMessage() ); |
119 |
} |
120 |
} |
121 |
}; |
122 |
|
123 |
// -------------------------------------------------------------------------------------- |
124 |
// WinPipeRedirection |
125 |
// -------------------------------------------------------------------------------------- |
126 |
class WinPipeRedirection : public PipeRedirectionBase |
127 |
{ |
128 |
DeclareNoncopyableObject( WinPipeRedirection ); |
129 |
|
130 |
protected: |
131 |
DWORD m_stdhandle; |
132 |
FILE* m_stdfp; |
133 |
FILE m_stdfp_copy; |
134 |
|
135 |
HANDLE m_readpipe; |
136 |
HANDLE m_writepipe; |
137 |
int m_crtFile; |
138 |
FILE* m_fp; |
139 |
|
140 |
WinPipeThread m_Thread; |
141 |
|
142 |
public: |
143 |
WinPipeRedirection( FILE* stdstream ); |
144 |
virtual ~WinPipeRedirection() throw(); |
145 |
|
146 |
void Cleanup() throw(); |
147 |
}; |
148 |
|
149 |
WinPipeRedirection::WinPipeRedirection( FILE* stdstream ) |
150 |
: m_Thread( m_readpipe, (stdstream == stderr) ? Color_Red : Color_Black ) |
151 |
{ |
152 |
m_stdhandle = ( stdstream == stderr ) ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE; |
153 |
m_stdfp = stdstream; |
154 |
m_stdfp_copy = *stdstream; |
155 |
|
156 |
m_readpipe = INVALID_HANDLE_VALUE; |
157 |
m_writepipe = INVALID_HANDLE_VALUE; |
158 |
m_crtFile = -1; |
159 |
m_fp = NULL; |
160 |
|
161 |
pxAssume( (stdstream == stderr) || (stdstream == stdout) ); |
162 |
|
163 |
try |
164 |
{ |
165 |
if( 0 == CreatePipe( &m_readpipe, &m_writepipe, NULL, 0 ) ) |
166 |
throw Exception::WinApiError().SetDiagMsg(L"CreatePipe failed."); |
167 |
|
168 |
if( 0 == SetStdHandle( m_stdhandle, m_writepipe ) ) |
169 |
throw Exception::WinApiError().SetDiagMsg(L"SetStdHandle failed."); |
170 |
|
171 |
// Note: Don't use GetStdHandle to "confirm" the handle. |
172 |
// |
173 |
// Under Windows7, and possibly Vista, GetStdHandle for STDOUT will return NULL |
174 |
// after it's been assigned a custom write pipe (this differs from XP, which |
175 |
// returns the assigned handle). Amusingly, the GetStdHandle succeeds for STDERR |
176 |
// and also tends to succeed when the app is run from the MSVC debugger. |
177 |
// |
178 |
// Fortunately, there's no need to use GetStdHandle anyway, so long as SetStdHandle |
179 |
// didn't error. |
180 |
|
181 |
m_crtFile = _open_osfhandle( (intptr_t)m_writepipe, _O_TEXT ); |
182 |
if( m_crtFile == -1 ) |
183 |
throw Exception::RuntimeError().SetDiagMsg( L"_open_osfhandle returned -1." ); |
184 |
|
185 |
m_fp = _fdopen( m_crtFile, "w" ); |
186 |
if( m_fp == NULL ) |
187 |
throw Exception::RuntimeError().SetDiagMsg( L"_fdopen returned NULL." ); |
188 |
|
189 |
*m_stdfp = *m_fp; // omg hack. but it works >_< |
190 |
setvbuf( stdstream, NULL, _IONBF, 0 ); |
191 |
|
192 |
m_Thread.Start(); |
193 |
} |
194 |
catch( Exception::BaseThreadError& ex ) |
195 |
{ |
196 |
// thread object will become invalid because of scoping after we leave |
197 |
// the constructor, so re-pack a new exception: |
198 |
|
199 |
Cleanup(); |
200 |
throw Exception::RuntimeError().SetDiagMsg( ex.FormatDiagnosticMessage() ).SetUserMsg( ex.FormatDisplayMessage() ); |
201 |
} |
202 |
catch( BaseException& ex ) |
203 |
{ |
204 |
Cleanup(); |
205 |
ex.DiagMsg() = (wxString)((stdstream==stdout) ? L"STDOUT" : L"STDERR") + L" Redirection Init failed: " + ex.DiagMsg(); |
206 |
throw; |
207 |
} |
208 |
catch( ... ) |
209 |
{ |
210 |
// C++ doesn't execute the object destructor automatically, because it's fail++ |
211 |
// (and I'm *not* encapsulating each handle into its own object >_<) |
212 |
|
213 |
Cleanup(); |
214 |
throw; |
215 |
} |
216 |
} |
217 |
|
218 |
WinPipeRedirection::~WinPipeRedirection() |
219 |
{ |
220 |
Cleanup(); |
221 |
} |
222 |
|
223 |
void WinPipeRedirection::Cleanup() throw() |
224 |
{ |
225 |
// restore the old handle we so graciously hacked earlier ;) |
226 |
// (or don't and suffer CRT crashes! ahaha!) |
227 |
|
228 |
if( m_stdfp != NULL ) |
229 |
*m_stdfp = m_stdfp_copy; |
230 |
|
231 |
// Cleanup Order Notes: |
232 |
// * The redirection thread is most likely blocking on ReadFile(), so we can't Cancel yet, lest we deadlock -- |
233 |
// Closing the writepipe (either directly or through the fp/crt handles) issues an EOF to the thread, |
234 |
// so it's safe to Cancel afterward. |
235 |
// |
236 |
// * The seemingly redundant series of checks here are designed to handle cases where the pipe init fails |
237 |
// mid-init (in which case the writepipe might be allocated while the fp/crtFile are still invalid, etc). |
238 |
|
239 |
if( m_fp != NULL ) |
240 |
{ |
241 |
fclose( m_fp ); |
242 |
m_fp = NULL; |
243 |
|
244 |
m_crtFile = -1; // crtFile is closed implicitly when closing m_fp |
245 |
m_writepipe = INVALID_HANDLE_VALUE; // same for the write end of the pipe |
246 |
} |
247 |
|
248 |
if( m_crtFile != -1 ) |
249 |
{ |
250 |
_close( m_crtFile ); |
251 |
m_crtFile = -1; // m_file is closed implicitly when closing crtFile |
252 |
m_writepipe = INVALID_HANDLE_VALUE; // same for the write end of the pipe (I assume) |
253 |
} |
254 |
|
255 |
if( m_writepipe != INVALID_HANDLE_VALUE ) |
256 |
{ |
257 |
CloseHandle( m_writepipe ); |
258 |
m_writepipe = INVALID_HANDLE_VALUE; |
259 |
} |
260 |
|
261 |
m_Thread.Cancel(); |
262 |
|
263 |
if( m_readpipe != INVALID_HANDLE_VALUE ) |
264 |
{ |
265 |
CloseHandle( m_readpipe ); |
266 |
m_readpipe = INVALID_HANDLE_VALUE; |
267 |
} |
268 |
} |
269 |
|
270 |
// The win32 specific implementation of PipeRedirection. |
271 |
PipeRedirectionBase* NewPipeRedir( FILE* stdstream ) |
272 |
{ |
273 |
try |
274 |
{ |
275 |
return new WinPipeRedirection( stdstream ); |
276 |
} |
277 |
catch( Exception::RuntimeError& ex ) |
278 |
{ |
279 |
// Entirely non-critical errors. Log 'em and move along. |
280 |
Console.Error( ex.FormatDiagnosticMessage() ); |
281 |
} |
282 |
|
283 |
return NULL; |
284 |
} |