recoll/src/windows/execmd_w.cpp
Jean-Francois Dockes 693d22a896 first non-compiling and incomplete draft of the windows execmd module
--HG--
branch : WINDOWSPORT
2015-09-08 10:24:19 +02:00

492 lines
16 KiB
C++

#include "execmd.h"
#include <windows.h>
#include <iostream>
#include <string>
using namespace std;
class ExecCmd::Internal {
public:
Internal()
: m_advise(0), m_provide(0), m_timeoutMs(1000),
m_pid(-1), m_hOutputRead(NULL), m_hInputWrite(NULL) {
}
std::vector<std::string> m_env;
ExecCmdAdvise *m_advise;
ExecCmdProvide *m_provide;
bool m_killRequest;
int m_timeoutMs;
string m_stderrFile;
// Subprocess id
pid_t m_pid;
HANDLE m_hOutputRead;
HANDLE m_hInputWrite;
OVERLAPPED m_oOutputRead; // Do these need resource control?
OVERLAPPED m_oInputWrite;
PROCESS_INFORMATION m_piProcInfo;
// Reset internal state indicators. Any resources should have been
// previously freed
void reset() {
m_killRequest = false;
m_hOutputRead = NULL;
m_hInputWrite = NULL;
memset(&m_oOutputRead, 0, sizeof(m_oOutputRead));
memset(&m_oInputWrite, 0, sizeof(m_oInputWrite));
m_pid = -1;
}
};
ExecCmd::ExecCmd()
{
m = new Internal();
if (m) {
m->reset();
}
}
void ExecCmd::setAdvise(ExecCmdAdvise *adv)
{
m->m_advise = adv;
}
void ExecCmd::setProvide(ExecCmdProvide *p)
{
m->m_provide = p;
}
void ExecCmd::setTimeout(int mS)
{
if (mS > 30) {
m->m_timeoutMs = mS;
}
}
void ExecCmd::setStderr(const std::string& stderrFile)
{
m->m_stderrFile = stderrFile;
}
pid_t ExecCmd::getChildPid()
{
return m->m_pid;
}
void ExecCmd::setKill()
{
m->m_killRequest = true;
}
void ExecCmd::zapChild()
{
setKill();
(void)wait();
}
bool ExecCmd::Internal::PreparePipes(bool has_input,HANDLE *hChildInput,
bool has_output, HANDLE *hChildOutput,
HANDLE *hChildError)
{
HANDLE hInputWriteTmp = NULL;
HANDLE hOutputReadTmp = NULL;
HANDLE hOutputWrite = NULL;
HANDLE hErrorWrite = NULL;
HANDLE hInputRead = NULL;
m_hOutputRead = NULL;
m_hInputWrite = NULL;
memset(&m_oOutputRead, 0, sizeof(m_oOutputRead));
memset(&m_oInputWrite, 0, sizeof(m_oInputWrite));
// manual reset event
m_oOutputRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_oOutputRead.hEvent == INVALID_HANDLE_VALUE) {
goto errout;
}
// manual reset event
m_oInputWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_oInputWrite.hEvent == INVALID_HANDLE_VALUE) {
goto errout;
}
if (has_output) {
// src for this code: https://www.daniweb.com/software-development/cpp/threads/295780/using-named-pipes-with-asynchronous-io-redirection-to-winapi
// ONLY IMPORTANT CHANGE
// set inheritance flag to TRUE in CreateProcess
// you need this for the client to inherit the handles
SECURITY_ATTRIBUTES sa;
// Set up the security attributes struct.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE; // this is the critical bit
sa.lpSecurityDescriptor = NULL;
// Create the child output named pipe.
// This creates a inheritable, one-way handle for the server to read
hOutputReadTmp = CreateNamedPipe(
TEXT("\\\\.\\pipe\\outstreamPipe"),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1, 4096, 4096, 0, &sa);
if (hOutputReadTmp == INVALID_HANDLE_VALUE) {
goto errout;
}
// However, the client can not use an inbound server handle.
// Client needs a write-only, outgoing handle.
// So, you create another handle to the same named pipe, only
// write-only. Again, must be created with the inheritable
// attribute, and the options are important.
// use CreateFile to open a new handle to the existing pipe...
hOutputWrite = CreateFile(
TEXT("\\\\.\\pipe\\outstreamPipe"),
FILE_WRITE_DATA | SYNCHRONIZE,
0, &sa, OPEN_EXISTING, // very important flag!
FILE_ATTRIBUTE_NORMAL, 0 // no template file for OPEN_EXISTING
);
// All is well? not quite. Our main server-side handle was
// created shareable.
// That means the client will receive it, and we have a
// problem because pipe termination conditions rely on knowing
// when the last handle closes.
// So, the only answer is to create another one, just for the server...
// Create new output read handle and the input write
// handles. Set the Inheritance property to FALSE. Otherwise,
// the child inherits the properties and, as a result,
// non-closeable handles to the pipes are created.
if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
GetCurrentProcess(), &m_hOutputRead,
0, FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS)) {
goto errout;
}
// now we kill the original, inheritable server-side handle. That
// will keep the pipe open, but keep the client program from
// getting it. Child-proofing. Close inheritable copies of the
// handles you do not want to be inherited.
if (!CloseHandle(hOutputReadTmp)) {
goto errout;
}
hOutputReadTmp = NULL;
}
#if 0 // Nope, can't do this, we certainly don't want to mix stdout and stderr
// Have to take the give stderr output file instead
if (!m->m_stderrFile.empty()) {
// Open the file and make the handle inheritable
} else {
// Duplicate our own stderr. Does passing NULL work for this ?
}
// we're going to give the same pipe for stderr, so duplicate for that:
// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
GetCurrentProcess(), &hErrorWrite,
0, TRUE, DUPLICATE_SAME_ACCESS)) {
goto errout;
}
#endif
if (has_input) {
// now same procedure for input pipe
HANDLE hInputWriteTmp = CreateNamedPipe(
TEXT("\\\\.\\pipe\\instreamPipe"),
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1, 4096, 4096, 0, &sa);
if (hInputWriteTmp == INVALID_HANDLE_VALUE) {
goto errout;
}
hInputRead = CreateFile(
TEXT("\\\\.\\pipe\\instreamPipe"),
FILE_READ_DATA | SYNCHRONIZE,
0, &sa, OPEN_EXISTING, // very important flag!
FILE_ATTRIBUTE_NORMAL, 0 // no template file for OPEN_EXISTING
);
if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
GetCurrentProcess(), &m_hInputWrite,
0, FALSE, // Make it uninheritable.
DUPLICATE_SAME_ACCESS)) {
goto errout;
}
if (!CloseHandle(hInputWriteTmp)) {
goto errout;
}
hInputWriteTmp = NULL;
}
*hChildInput = hInputRead;
*hChildOutput = hOutputWrite;
*hChildError = hErrorWrite;
return true;
errout:
if (hInputWriteTmp)
CloseHandle(hInputWriteTmp);
if (hOutputReadTmp)
CloseHandle(hOutputReadTmp);
if (m_hOutputRead)
CloseHandle(m_hOutputRead);
if (m_hInputWrite)
CloseHandle(m_hInputWrite);
if (m_oOutputRead.hEvent)
CloseHandle(m_oOutputRead.hEvent);
if (m_oInputWrite.hEvent)
CloseHandle(m_oInputWrite.hEvent);
if (hOutputWrite)
CloseHandle(hOutputWrite);
if (hInputRead)
CloseHandle(hInputRead);
if (hErrorWrite)
CloseHandle(hErrorWrite);
}
/**
This routine appends the given argument to a command line such
that CommandLineToArgvW will return the argument string unchanged.
Arguments in a command line should be separated by spaces; this
function does not add these spaces. The caller must append spaces
between calls.
@param arg Supplies the argument to encode.
@param cmdLine Supplies the command line to which we append
the encoded argument string.
@force Supplies an indication of whether we should quote
the argument even if it does not contain any characters that
would ordinarily require quoting.
*/
static void argQuote(const string& arg,
string& cmdLine,
bool force = false)
{
// Don't quote unless we actually need to do so
if (!force && !arg.empty() &&
arg.find_first_of(" \t\n\v\"") == arg.npos) {
cmdLine.append(arg);
} else {
cmdLine.push_back ('"');
for (auto It = arg.begin () ; ; ++It) {
unsigned NumberBackslashes = 0;
while (It != arg.end () && *It == '\\') {
++It;
++NumberBackslashes;
}
if (It == arg.end()) {
// Escape all backslashes, but let the terminating
// double quotation mark we add below be interpreted
// as a metacharacter.
cmdLine.append (NumberBackslashes * 2, '\\');
break;
} else if (*It == L'"') {
// Escape all backslashes and the following
// double quotation mark.
cmdLine.append (NumberBackslashes * 2 + 1, '\\');
cmdLine.push_back (*It);
} else {
// Backslashes aren't special here.
cmdLine.append (NumberBackslashes, '\\');
cmdLine.push_back (*It);
}
}
cmdLine.push_back ('"');
}
}
static string argvToCmdLine(const string& cmd, const vector<string>& args)
{
string cmdline;
argQuote(cmd, cmdline);
for (auto it = args.begin(); it != args.end(); it++) {
cmdline.append(" ");
argQuote(cmd, cmdline);
}
return cmdline;
}
// Create a child process
int ExecCmd::startExec(const string &cmd, const vector<string>& args,
bool has_input, bool has_output)
{
{ // Debug and logging
string command = cmd + " ";
for (vector<string>::const_iterator it = args.begin();
it != args.end(); it++) {
command += "{" + *it + "} ";
}
LOGDEB(("ExecCmd::startExec: (%d|%d) %s\n",
has_input, has_output, command.c_str()));
}
string cmdline = argvToCmdLine(cmd, args);
// The resource manager ensures resources are freed if we return early
//ExecCmdRsrc e(this->m);
HANDLE hInputRead;
HANDLE hOutputWrite;
HANDLE hErrorWrite;
if (!m->PreparePipes(has_input, &hInputRead, has_output,
&hOutputWrite, &hErrorWrite)) {
return false;
}
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
siStartInfo.hStdOutput = hOutputWrite;
siStartInfo.hStdInput = hInputRead;
siStartInfo.hStdError = hErrorWrite;
// Create the child process.
bSuccess = CreateProcess(NULL,
cmdline.c_str(), // command line
NULL, // process security attributes
NULL, // primary thread security attrs
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
if (!bSuccess) {
return false;
}
m->pid = piProcInfo.dwProcessId;
return true;
}
// Read from a file and write its contents to the pipe for the child's
// STDIN. Stop when there is no more data.
int ExecCmd::send(const string& data)
{
DWORD dwRead, dwWritten;
BOOL bSuccess = false;
bSuccess = WriteFile(m->m_hInputWrite, data.c_str(), data.size(),
NULL, &m->m_oInputWrite);
DWORD err = GetLastError();
// TODO: some more decision, either the operation completes immediately
// and we get success, or it is started (which is indicated by no success)
// and ERROR_IO_PENDING
// in the first case bytes read/written parameter can be used directly
if (!bSuccess && err != ERROR_IO_PENDING) {
return;
}
WaitResult waitRes = Wait(oInputWrite.hEvent, 5000);
if (waitRes == Ok) {
if (!GetOverlappedResult(m->m_hInputWrite,
&m->m_oInputWrite, &dwWritten, TRUE)) {
ErrorExit("GetOverlappedResult");
}
} else if (waitRes == Quit) {
if (!CancelIo(hInputWrite))
ErrorExit("CancelIo");
return;
} else if (waitRes == Timeout) {
if (!CancelIo(hInputWrite))
ErrorExit("CancelIo");
return;
}
}
// Read output from the child process's pipe for STDOUT
// and write to cout in this programme
// Stop when there is no more data.
// @arg cnt count to read, -1 means read to end of data.
int ExecCmd::receive(const string& data, int cnt)
{
DWORD dwRead;
const int BUFSIZE = 8192;
CHAR chBuf[BUFSIZE];
BOOL bSuccess = FALSE;
int totread = 0;
while (true) {
int toread = cnt > 0 ? MIN(cnt - totread, BUFSIZE) : BUFSIZE;
bSuccess = ReadFile(m->m_hOutputRead, chBuf, toread,
NULL, &m->m_oOutputRead);
DWORD err = GetLastError();
if (!bSuccess && err != ERROR_IO_PENDING)
break;
WaitResult waitRes = Wait(oOutputRead.hEvent, 5000);
if (waitRes == Ok) {
if (!GetOverlappedResult(hOutputRead, &oOutputRead, &dwRead, TRUE)) {
ErrorExit("GetOverlappedResult");
}
totread += dwRead;
data.append(buf, dwRead);
} else if (waitRes == Quit) {
if (!CancelIo(hOutputRead))
ErrorExit("CancelIo");
break;
} else if (waitRes == Timeout) {
if (!CancelIo(hOutputRead))
ErrorExit("CancelIo");
break;
}
}
return totread;
}
int ExecCmd::wait()
{
// Wait until child process exits.
WaitForSingleObject(m->m_piProcInfo.hProcess, INFINITE);
// Get exit code
DWORD exit_code = 0;
GetExitCodeProcess(m->m_piProcInfo.hProcess, &exit_code);
// Close handles to the child process and its primary thread.
CloseHandle(m->m_piProcInfo.hProcess);
CloseHandle(m->m_piProcInfo.hThread);
return (int)exit_code;
}
int ExecCmd::doexec(const string &cmd, const vector<string>& args,
const string *input, string *output)
{
if (input && output) {
LOGERR(("ExecCmd::doexec: can't do both input and output\n"));
return -1;
}
if (startExec(cmd, args, input != 0, output != 0) < 0) {
return -1;
}
if (input) {
receive(*input);
} else if (output) {
send(*output);
}
return wait();
}