|
1
|
1 #pragma once
|
|
|
2
|
|
|
3 #ifdef FOOBAR2000_DESKTOP_WINDOWS
|
|
|
4
|
|
|
5 #include <libPPUI/win32_op.h>
|
|
|
6
|
|
|
7 namespace ProcessUtils {
|
|
|
8 class PipeIO : public stream_reader, public stream_writer {
|
|
|
9 public:
|
|
|
10 PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout");
|
|
|
11 PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) {
|
|
|
12 }
|
|
|
13 ~PipeIO() {
|
|
|
14 }
|
|
|
15
|
|
|
16 void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
|
17 if (p_bytes == 0) return;
|
|
|
18 OVERLAPPED ol = {};
|
|
|
19 ol.hEvent = m_event;
|
|
|
20 ResetEvent(m_event);
|
|
|
21 DWORD bytesWritten;
|
|
|
22 SetLastError(NO_ERROR);
|
|
|
23 if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesWritten, &ol)) {
|
|
|
24 // succeeded already?
|
|
|
25 if (bytesWritten != p_bytes) throw exception_io();
|
|
|
26 return;
|
|
|
27 }
|
|
|
28
|
|
|
29 {
|
|
|
30 const DWORD code = GetLastError();
|
|
|
31 if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
|
|
|
32 }
|
|
|
33 const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
|
|
34 SetLastError(NO_ERROR);
|
|
|
35 DWORD state = myWait(_countof(handles), handles);
|
|
|
36 if (state == WAIT_OBJECT_0) {
|
|
|
37 try {
|
|
|
38 WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) );
|
|
|
39 } catch(...) {
|
|
|
40 _cancel(ol);
|
|
|
41 throw;
|
|
|
42 }
|
|
|
43 if (bytesWritten != p_bytes) throw exception_io();
|
|
|
44 return;
|
|
|
45 }
|
|
|
46 _cancel(ol);
|
|
|
47 abort.check();
|
|
|
48 throw timeout();
|
|
|
49 }
|
|
|
50 size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
|
51 uint8_t * ptr = (uint8_t*) p_buffer;
|
|
|
52 size_t done = 0;
|
|
|
53 while(done < p_bytes) {
|
|
|
54 abort.check();
|
|
|
55 size_t delta = readPass(ptr + done, p_bytes - done, abort);
|
|
|
56 if (delta == 0) break;
|
|
|
57 done += delta;
|
|
|
58 }
|
|
|
59 return done;
|
|
|
60 }
|
|
|
61 size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
|
62 if (p_bytes == 0) return 0;
|
|
|
63 OVERLAPPED ol = {};
|
|
|
64 ol.hEvent = m_event;
|
|
|
65 ResetEvent(m_event);
|
|
|
66 DWORD bytesDone;
|
|
|
67 SetLastError(NO_ERROR);
|
|
|
68 if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesDone, &ol)) {
|
|
|
69 // succeeded already?
|
|
|
70 return bytesDone;
|
|
|
71 }
|
|
|
72
|
|
|
73 {
|
|
|
74 const DWORD code = GetLastError();
|
|
|
75 switch(code) {
|
|
|
76 case ERROR_HANDLE_EOF:
|
|
|
77 return 0;
|
|
|
78 case ERROR_IO_PENDING:
|
|
|
79 break; // continue
|
|
|
80 default:
|
|
|
81 exception_io_from_win32(code);
|
|
|
82 };
|
|
|
83 }
|
|
|
84
|
|
|
85 const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
|
|
86 SetLastError(NO_ERROR);
|
|
|
87 DWORD state = myWait(_countof(handles), handles);
|
|
|
88 if (state == WAIT_OBJECT_0) {
|
|
|
89 SetLastError(NO_ERROR);
|
|
|
90 if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) {
|
|
|
91 const DWORD code = GetLastError();
|
|
|
92 if (code == ERROR_HANDLE_EOF) bytesDone = 0;
|
|
|
93 else {
|
|
|
94 _cancel(ol);
|
|
|
95 exception_io_from_win32(code);
|
|
|
96 }
|
|
|
97 }
|
|
|
98 return bytesDone;
|
|
|
99 }
|
|
|
100 _cancel(ol);
|
|
|
101 abort.check();
|
|
|
102 throw timeout();
|
|
|
103 }
|
|
|
104 private:
|
|
|
105 DWORD myWait(DWORD count, const HANDLE * handles) {
|
|
|
106 if (m_processMessages) {
|
|
|
107 for(;;) {
|
|
|
108 DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT);
|
|
|
109 if (state == WAIT_OBJECT_0 + count) {
|
|
|
110 ProcessPendingMessages();
|
|
|
111 } else {
|
|
|
112 return state;
|
|
|
113 }
|
|
|
114 }
|
|
|
115 } else {
|
|
|
116 return WaitForMultipleObjects(count, handles, FALSE, m_timeOut);
|
|
|
117 }
|
|
|
118 }
|
|
|
119 void _cancel(OVERLAPPED & ol) {
|
|
|
120 #if _WIN32_WINNT >= 0x600
|
|
|
121 CancelIoEx(m_handle,&ol);
|
|
|
122 #else
|
|
|
123 CancelIo(m_handle);
|
|
|
124 #endif
|
|
|
125 }
|
|
|
126 static void ProcessPendingMessages() {
|
|
|
127 MSG msg = {};
|
|
|
128 while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
|
|
|
129 }
|
|
|
130
|
|
|
131 HANDLE m_handle;
|
|
|
132 HANDLE m_event;
|
|
|
133 const DWORD m_timeOut;
|
|
|
134 const bool m_processMessages;
|
|
|
135 };
|
|
|
136
|
|
|
137 class SubProcess : public stream_reader, public stream_writer {
|
|
|
138 public:
|
|
|
139 PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure");
|
|
|
140
|
|
|
141 SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) {
|
|
|
142 HANDLE ev;
|
|
|
143 WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
|
|
144 hEventRead = ev;
|
|
|
145 WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
|
|
146 hEventWrite = ev;
|
|
|
147 Restart();
|
|
|
148 }
|
|
|
149 void Restart() {
|
|
|
150 CleanUp();
|
|
|
151 STARTUPINFO si = {};
|
|
|
152 try {
|
|
|
153 si.cb = sizeof(si);
|
|
|
154 si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;
|
|
|
155 //si.wShowWindow = SW_HIDE;
|
|
|
156
|
|
|
157 myCreatePipeOut(si.hStdInput, hStdIn);
|
|
|
158 myCreatePipeIn(hStdOut, si.hStdOutput);
|
|
|
159 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
160
|
|
|
161 PROCESS_INFORMATION pi = {};
|
|
|
162 try {
|
|
|
163 WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) );
|
|
|
164 } catch(std::exception const & e) {
|
|
|
165 throw failure(PFC_string_formatter() << "Could not start the worker process - " << e);
|
|
|
166 }
|
|
|
167 hProcess = pi.hProcess; _Close(pi.hThread);
|
|
|
168 } catch(...) {
|
|
|
169 _Close(si.hStdInput);
|
|
|
170 _Close(si.hStdOutput);
|
|
|
171 CleanUp(); throw;
|
|
|
172 }
|
|
|
173 _Close(si.hStdInput);
|
|
|
174 _Close(si.hStdOutput);
|
|
|
175 }
|
|
|
176 ~SubProcess() {
|
|
|
177 CleanUp();
|
|
|
178 CloseHandle(hEventRead);
|
|
|
179 CloseHandle(hEventWrite);
|
|
|
180 }
|
|
|
181
|
|
|
182 bool IsRunning() const {
|
|
|
183 return hProcess != NULL;
|
|
|
184 }
|
|
|
185 void Detach() {
|
|
|
186 CleanUp(true);
|
|
|
187 }
|
|
|
188 bool ProcessMessages;
|
|
|
189 DWORD TimeOutMS;
|
|
|
190
|
|
|
191
|
|
|
192 void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
|
193 PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS);
|
|
|
194 writer.write(p_buffer, p_bytes, abort);
|
|
|
195 }
|
|
|
196 size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
|
197 PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS);
|
|
|
198 return reader.read(p_buffer, p_bytes, abort);
|
|
|
199 }
|
|
|
200 void SetPriority(DWORD val) {
|
|
|
201 SetPriorityClass(hProcess, val);
|
|
|
202 }
|
|
|
203 protected:
|
|
|
204 HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite;
|
|
|
205 const pfc::string8 ExePath;
|
|
|
206
|
|
|
207 void CleanUp(bool bDetach = false) {
|
|
|
208 _Close(hStdIn); _Close(hStdOut);
|
|
|
209 if (hProcess != NULL) {
|
|
|
210 if (!bDetach) {
|
|
|
211 if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) {
|
|
|
212 //PFC_ASSERT( !"Should not get here - worker stuck" );
|
|
|
213 FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating";
|
|
|
214 TerminateProcess(hProcess, -1);
|
|
|
215 }
|
|
|
216 }
|
|
|
217 _Close(hProcess);
|
|
|
218 }
|
|
|
219 }
|
|
|
220 private:
|
|
|
221 static void _Close(HANDLE & h) {
|
|
|
222 if (h != NULL) {CloseHandle(h); h = NULL;}
|
|
|
223 }
|
|
|
224
|
|
|
225 static void myCreatePipe(HANDLE & in, HANDLE & out) {
|
|
|
226 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
|
227 WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) );
|
|
|
228 }
|
|
|
229
|
|
|
230 static pfc::string_formatter makePipeName() {
|
|
|
231 GUID id;
|
|
|
232 CoCreateGuid (&id);
|
|
|
233 return pfc::format( "\\\\.\\pipe\\", pfc::print_guid(id));
|
|
|
234 }
|
|
|
235
|
|
|
236 static void myCreatePipeOut(HANDLE & in, HANDLE & out) {
|
|
|
237 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
|
238 const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
|
|
239 SetLastError(NO_ERROR);
|
|
|
240 HANDLE pipe = CreateNamedPipe(
|
|
|
241 pipeName,
|
|
|
242 FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
|
|
|
243 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
|
244 1,
|
|
|
245 1024*64,
|
|
|
246 1024*64,
|
|
|
247 NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
|
|
248 if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
|
|
249
|
|
|
250 in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
|
|
251 DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
|
|
252 CloseHandle(pipe);
|
|
|
253 }
|
|
|
254
|
|
|
255 static void myCreatePipeIn(HANDLE & in, HANDLE & out) {
|
|
|
256 SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
|
257 const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
|
|
258 SetLastError(NO_ERROR);
|
|
|
259 HANDLE pipe = CreateNamedPipe(
|
|
|
260 pipeName,
|
|
|
261 FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
|
|
|
262 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
|
263 1,
|
|
|
264 1024*64,
|
|
|
265 1024*64,
|
|
|
266 NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
|
|
267 if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
|
|
268
|
|
|
269 out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
|
|
270 DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
|
|
271 CloseHandle(pipe);
|
|
|
272 }
|
|
|
273
|
|
|
274 PFC_CLASS_NOT_COPYABLE_EX(SubProcess)
|
|
|
275 };
|
|
|
276 }
|
|
|
277
|
|
|
278 #endif // FOOBAR2000_DESKTOP_WINDOWS
|
|
|
279
|