280 lines
8.2 KiB
C++
280 lines
8.2 KiB
C++
#pragma once
|
|
|
|
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
|
|
|
#include <libPPUI/win32_op.h>
|
|
|
|
namespace ProcessUtils {
|
|
class PipeIO : public stream_reader, public stream_writer {
|
|
public:
|
|
PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout");
|
|
PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) {
|
|
}
|
|
~PipeIO() {
|
|
}
|
|
|
|
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
if (p_bytes == 0) return;
|
|
OVERLAPPED ol = {};
|
|
ol.hEvent = m_event;
|
|
ResetEvent(m_event);
|
|
DWORD bytesWritten;
|
|
SetLastError(NO_ERROR);
|
|
if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesWritten, &ol)) {
|
|
// succeeded already?
|
|
if (bytesWritten != p_bytes) throw exception_io();
|
|
return;
|
|
}
|
|
|
|
{
|
|
const DWORD code = GetLastError();
|
|
if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
|
|
}
|
|
const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
|
SetLastError(NO_ERROR);
|
|
DWORD state = myWait(_countof(handles), handles);
|
|
if (state == WAIT_OBJECT_0) {
|
|
try {
|
|
WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) );
|
|
} catch(...) {
|
|
_cancel(ol);
|
|
throw;
|
|
}
|
|
if (bytesWritten != p_bytes) throw exception_io();
|
|
return;
|
|
}
|
|
_cancel(ol);
|
|
abort.check();
|
|
throw timeout();
|
|
}
|
|
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
uint8_t * ptr = (uint8_t*) p_buffer;
|
|
size_t done = 0;
|
|
while(done < p_bytes) {
|
|
abort.check();
|
|
size_t delta = readPass(ptr + done, p_bytes - done, abort);
|
|
if (delta == 0) break;
|
|
done += delta;
|
|
}
|
|
return done;
|
|
}
|
|
size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
if (p_bytes == 0) return 0;
|
|
OVERLAPPED ol = {};
|
|
ol.hEvent = m_event;
|
|
ResetEvent(m_event);
|
|
DWORD bytesDone;
|
|
SetLastError(NO_ERROR);
|
|
if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesDone, &ol)) {
|
|
// succeeded already?
|
|
return bytesDone;
|
|
}
|
|
|
|
{
|
|
const DWORD code = GetLastError();
|
|
switch(code) {
|
|
case ERROR_HANDLE_EOF:
|
|
return 0;
|
|
case ERROR_IO_PENDING:
|
|
break; // continue
|
|
default:
|
|
exception_io_from_win32(code);
|
|
};
|
|
}
|
|
|
|
const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
|
SetLastError(NO_ERROR);
|
|
DWORD state = myWait(_countof(handles), handles);
|
|
if (state == WAIT_OBJECT_0) {
|
|
SetLastError(NO_ERROR);
|
|
if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) {
|
|
const DWORD code = GetLastError();
|
|
if (code == ERROR_HANDLE_EOF) bytesDone = 0;
|
|
else {
|
|
_cancel(ol);
|
|
exception_io_from_win32(code);
|
|
}
|
|
}
|
|
return bytesDone;
|
|
}
|
|
_cancel(ol);
|
|
abort.check();
|
|
throw timeout();
|
|
}
|
|
private:
|
|
DWORD myWait(DWORD count, const HANDLE * handles) {
|
|
if (m_processMessages) {
|
|
for(;;) {
|
|
DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT);
|
|
if (state == WAIT_OBJECT_0 + count) {
|
|
ProcessPendingMessages();
|
|
} else {
|
|
return state;
|
|
}
|
|
}
|
|
} else {
|
|
return WaitForMultipleObjects(count, handles, FALSE, m_timeOut);
|
|
}
|
|
}
|
|
void _cancel(OVERLAPPED & ol) {
|
|
#if _WIN32_WINNT >= 0x600
|
|
CancelIoEx(m_handle,&ol);
|
|
#else
|
|
CancelIo(m_handle);
|
|
#endif
|
|
}
|
|
static void ProcessPendingMessages() {
|
|
MSG msg = {};
|
|
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
|
|
}
|
|
|
|
HANDLE m_handle;
|
|
HANDLE m_event;
|
|
const DWORD m_timeOut;
|
|
const bool m_processMessages;
|
|
};
|
|
|
|
class SubProcess : public stream_reader, public stream_writer {
|
|
public:
|
|
PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure");
|
|
|
|
SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) {
|
|
HANDLE ev;
|
|
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
|
hEventRead = ev;
|
|
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
|
hEventWrite = ev;
|
|
Restart();
|
|
}
|
|
void Restart() {
|
|
CleanUp();
|
|
STARTUPINFO si = {};
|
|
try {
|
|
si.cb = sizeof(si);
|
|
si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;
|
|
//si.wShowWindow = SW_HIDE;
|
|
|
|
myCreatePipeOut(si.hStdInput, hStdIn);
|
|
myCreatePipeIn(hStdOut, si.hStdOutput);
|
|
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
PROCESS_INFORMATION pi = {};
|
|
try {
|
|
WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) );
|
|
} catch(std::exception const & e) {
|
|
throw failure(PFC_string_formatter() << "Could not start the worker process - " << e);
|
|
}
|
|
hProcess = pi.hProcess; _Close(pi.hThread);
|
|
} catch(...) {
|
|
_Close(si.hStdInput);
|
|
_Close(si.hStdOutput);
|
|
CleanUp(); throw;
|
|
}
|
|
_Close(si.hStdInput);
|
|
_Close(si.hStdOutput);
|
|
}
|
|
~SubProcess() {
|
|
CleanUp();
|
|
CloseHandle(hEventRead);
|
|
CloseHandle(hEventWrite);
|
|
}
|
|
|
|
bool IsRunning() const {
|
|
return hProcess != NULL;
|
|
}
|
|
void Detach() {
|
|
CleanUp(true);
|
|
}
|
|
bool ProcessMessages;
|
|
DWORD TimeOutMS;
|
|
|
|
|
|
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS);
|
|
writer.write(p_buffer, p_bytes, abort);
|
|
}
|
|
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
|
PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS);
|
|
return reader.read(p_buffer, p_bytes, abort);
|
|
}
|
|
void SetPriority(DWORD val) {
|
|
SetPriorityClass(hProcess, val);
|
|
}
|
|
protected:
|
|
HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite;
|
|
const pfc::string8 ExePath;
|
|
|
|
void CleanUp(bool bDetach = false) {
|
|
_Close(hStdIn); _Close(hStdOut);
|
|
if (hProcess != NULL) {
|
|
if (!bDetach) {
|
|
if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) {
|
|
//PFC_ASSERT( !"Should not get here - worker stuck" );
|
|
FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating";
|
|
TerminateProcess(hProcess, -1);
|
|
}
|
|
}
|
|
_Close(hProcess);
|
|
}
|
|
}
|
|
private:
|
|
static void _Close(HANDLE & h) {
|
|
if (h != NULL) {CloseHandle(h); h = NULL;}
|
|
}
|
|
|
|
static void myCreatePipe(HANDLE & in, HANDLE & out) {
|
|
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) );
|
|
}
|
|
|
|
static pfc::string_formatter makePipeName() {
|
|
GUID id;
|
|
CoCreateGuid (&id);
|
|
return PFC_string_formatter() << "\\\\.\\pipe\\" << pfc::print_guid(id);
|
|
}
|
|
|
|
static void myCreatePipeOut(HANDLE & in, HANDLE & out) {
|
|
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
|
SetLastError(NO_ERROR);
|
|
HANDLE pipe = CreateNamedPipe(
|
|
pipeName,
|
|
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
1,
|
|
1024*64,
|
|
1024*64,
|
|
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
|
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
|
|
|
in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
|
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
|
CloseHandle(pipe);
|
|
}
|
|
|
|
static void myCreatePipeIn(HANDLE & in, HANDLE & out) {
|
|
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
|
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
|
SetLastError(NO_ERROR);
|
|
HANDLE pipe = CreateNamedPipe(
|
|
pipeName,
|
|
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
|
1,
|
|
1024*64,
|
|
1024*64,
|
|
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
|
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
|
|
|
out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
|
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
|
CloseHandle(pipe);
|
|
}
|
|
|
|
PFC_CLASS_NOT_COPYABLE_EX(SubProcess)
|
|
};
|
|
}
|
|
|
|
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
|
|