Windows execmd: use a wide char environment

This commit is contained in:
Jean-Francois Dockes 2020-06-02 08:53:11 +01:00
parent 84c0098f23
commit 7ccde15839

View File

@ -32,7 +32,6 @@
#include <psapi.h> #include <psapi.h>
#include "smallut.h" #include "smallut.h"
#include "pathut.h" #include "pathut.h"
#include "transcode.h"
using namespace std; using namespace std;
@ -42,7 +41,7 @@ using namespace std;
static void printError(const string& text) static void printError(const string& text)
{ {
DWORD err = GetLastError(); DWORD err = GetLastError();
LOGERR(text << " : err: " << err << "\n"); LOGERR(text << " : err: " << err << "\n");
} }
/** /**
@ -108,37 +107,30 @@ static string argvToCmdLine(const string& cmd, const vector<string>& args)
return cmdline; return cmdline;
} }
// Because we build with UNICODE defined, GetEnvironmentStrings is
// defined as GetEnvironmentStringsW. Because of a Windows problem teh
// GetEnvironmentStrings function is really a GetEnvironmentStringsA,
// which is what we want.
// See: https://devblogs.microsoft.com/oldnewthing/20130117-00/?p=5533
#undef GetEnvironmentStrings
// Merge the father environment with the variable specified in m_env // Merge the father environment with the variable specified in m_env
static char *mergeEnvironment(const std::unordered_map<string, string>& addenv) static wchar_t *mergeEnvironment(
const std::unordered_map<string, string>& addenv)
{ {
// Parse existing environment. // Retrieve existing environment.
char *envir = GetEnvironmentStrings(); wchar_t *wenvir = GetEnvironmentStringsW();
char *cp0 = envir;
std::unordered_map<string, string> envirmap;
const wchar_t *cp0 = wenvir;
std::unordered_map<string, string> envirmap;
string name, value; string name, value;
for (char *cp1 = cp0;;cp1++) { for (const wchar_t *cp1 = cp0;;cp1++) {
if (*cp1 == '=') { if (*cp1 == L'=') {
name = string(cp0, cp1 - cp0); wchartoutf8(cp0, name, cp1 - cp0);
cp0 = cp1 + 1; cp0 = cp1 + 1;
} else if (*cp1 == 0) { } else if (*cp1 == 0) {
value = string(cp0, cp1 - cp0); wchartoutf8(cp0, value, cp1 - cp0);
envirmap[name] = value; envirmap[name] = value;
LOGDEB1("mergeEnvir: [" << (name) << "] = [" << (value) << "]\n" ); LOGDEB1("mergeEnvir: [" << name << "] = [" << value << "]\n" );
cp0 = cp1 + 1; cp0 = cp1 + 1;
if (*cp0 == 0) if (*cp0 == 0)
break; break;
} }
} }
FreeEnvironmentStringsW(wenvir);
FreeEnvironmentStringsA(envir);
// Merge our values // Merge our values
for (auto it = addenv.begin(); it != addenv.end(); it++) { for (auto it = addenv.begin(); it != addenv.end(); it++) {
@ -146,21 +138,27 @@ static char *mergeEnvironment(const std::unordered_map<string, string>& addenv)
} }
// Create environment block // Create environment block
// Size computation. We could do an exact computation by
// converting the strings, but do worst case instead. one utf-8
// byte can't convert to more than one wchar
size_t sz = 0; size_t sz = 0;
for (auto it = envirmap.begin(); it != envirmap.end(); it++) { for (auto it = envirmap.begin(); it != envirmap.end(); it++) {
sz += it->first.size() + it->second.size() + 2; // =, 0 // the +2 is for '=' and '\0'
sz += sizeof(wchar_t) * (it->first.size() + it->second.size() + 2);
} }
sz++; // final 0 sz += sizeof(wchar_t); // final 0
char *nenvir = (char *)malloc(sz);
if (nenvir == 0) wchar_t *nenvir = (wchar_t *)malloc(sz+2);
if (nullptr == nenvir)
return nenvir; return nenvir;
char *cp = nenvir; wchar_t *cp = nenvir;
for (auto it = envirmap.begin(); it != envirmap.end(); it++) { for (const auto& entry : envirmap) {
memcpy(cp, it->first.c_str(), it->first.size()); utf8towchar(entry.first, cp, sz);
cp += it->first.size(); cp += wcslen(cp);
*cp++ = '='; *cp++ = L'=';
memcpy(cp, it->second.c_str(), it->second.size()); utf8towchar(entry.second, cp, sz);
cp += it->second.size(); cp += wcslen(cp);
*cp++ = 0; *cp++ = 0;
} }
// Final double-zero // Final double-zero
@ -267,8 +265,10 @@ static int getVMMBytes(HANDLE hProcess)
PROCESS_MEMORY_COUNTERS pmc; PROCESS_MEMORY_COUNTERS pmc;
const int MB = 1024 * 1024; const int MB = 1024 * 1024;
if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) { if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
LOGDEB2("ExecCmd: getVMMBytes paged Kbs " << (int(pmc.QuotaPagedPoolUsage/1024)) << " non paged " << (int(pmc.QuotaNonPagedPoolUsage/1024)) << " Kbs\n" ); LOGDEB2("ExecCmd: getVMMBytes paged Kbs " <<
return int(pmc.QuotaPagedPoolUsage /MB + pmc.QuotaPagedPoolUsage/1024 << " non paged " <<
pmc.QuotaNonPagedPoolUsage/1024 << " Kbs\n");
return int(pmc.QuotaPagedPoolUsage / MB +
pmc.QuotaNonPagedPoolUsage / MB); pmc.QuotaNonPagedPoolUsage / MB);
} }
return -1; return -1;
@ -335,7 +335,7 @@ static bool sendIntr(int pid)
LOGDEB("execmd_w: sendIntr attaching console\n" ); LOGDEB("execmd_w: sendIntr attaching console\n" );
if (!AttachConsole((unsigned int) pid)) { if (!AttachConsole((unsigned int) pid)) {
int err = GetLastError(); int err = GetLastError();
LOGERR("execmd_w: sendIntr: AttachConsole failed: " << (err) << "\n" ); LOGERR("execmd_w: sendIntr: AttachConsole failed: " << err << "\n");
return false; return false;
} }
} }
@ -345,7 +345,8 @@ static bool sendIntr(int pid)
// Disable Ctrl-C handling for our program // Disable Ctrl-C handling for our program
if (!SetConsoleCtrlHandler(NULL, true)) { if (!SetConsoleCtrlHandler(NULL, true)) {
int err = GetLastError(); int err = GetLastError();
LOGERR("execmd_w:sendIntr:SetCons.Ctl.Hndlr.(NULL, true) failed: " << (err) << "\n" ); LOGERR("execmd_w:sendIntr:SetCons.Ctl.Hndlr.(NULL, true) failed: " <<
err << "\n");
return false; return false;
} }
#endif #endif
@ -355,7 +356,7 @@ static bool sendIntr(int pid)
bool ret = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid); bool ret = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid);
if (!ret) { if (!ret) {
int err = GetLastError(); int err = GetLastError();
LOGERR("execmd_w:sendIntr:Gen.Cons.CtrlEvent failed: " << (err) << "\n" ); LOGERR("execmd_w:sendIntr:Gen.Cons.CtrlEvent failed: " << err << "\n");
} }
#if 0 #if 0
@ -367,7 +368,7 @@ static bool sendIntr(int pid)
LOGDEB("execmd_w: sendIntr detaching console\n" ); LOGDEB("execmd_w: sendIntr detaching console\n" );
if (!FreeConsole()) { if (!FreeConsole()) {
int err = GetLastError(); int err = GetLastError();
LOGERR("execmd_w: sendIntr: FreeConsole failed: " << (err) << "\n" ); LOGERR("execmd_w: sendIntr: FreeConsole failed: " << err << "\n");
} }
} }
@ -388,7 +389,7 @@ public:
~ExecCmdRsrc() { ~ExecCmdRsrc() {
if (!m_active || !m_parent) if (!m_active || !m_parent)
return; return;
LOGDEB1("~ExecCmdRsrc: working. mypid: " << ((int)getpid()) << "\n" ); LOGDEB1("~ExecCmdRsrc: working. mypid: " << ((int)getpid()) << "\n" );
if (m_parent->m_hOutputRead) if (m_parent->m_hOutputRead)
CloseHandle(m_parent->m_hOutputRead); CloseHandle(m_parent->m_hOutputRead);
if (m_parent->m_hInputWrite) if (m_parent->m_hInputWrite)
@ -556,7 +557,8 @@ bool ExecCmd::Internal::tooBig()
return false; return false;
int mbytes = getVMMBytes(m_piProcInfo.hProcess); int mbytes = getVMMBytes(m_piProcInfo.hProcess);
if (mbytes > m_rlimit_as_mbytes) { if (mbytes > m_rlimit_as_mbytes) {
LOGINFO("ExecCmd:: process mbytes " << (mbytes) << " > set limit " << (m_rlimit_as_mbytes) << "\n" ); LOGINFO("ExecCmd:: process mbytes " << mbytes << " > set limit " <<
m_rlimit_as_mbytes << "\n");
m_killRequest = true; m_killRequest = true;
return true; return true;
} }
@ -738,7 +740,8 @@ int ExecCmd::startExec(const string &cmd, const vector<string>& args,
it != args.end(); it++) { it != args.end(); it++) {
command += "{" + *it + "} "; command += "{" + *it + "} ";
} }
LOGDEB("ExecCmd::startExec: (" << (has_input) << "|" << (has_output) << ") " << (command) << "\n" ); LOGDEB("ExecCmd::startExec: (" << has_input << "|" << has_output <<
") " << command << "\n");
} }
// What if we're called twice ? First make sure we're clean // What if we're called twice ? First make sure we're clean
@ -756,7 +759,7 @@ int ExecCmd::startExec(const string &cmd, const vector<string>& args,
HANDLE hErrorWrite; HANDLE hErrorWrite;
if (!m->preparePipes(has_input, &hInputRead, has_output, if (!m->preparePipes(has_input, &hInputRead, has_output,
&hOutputWrite, &hErrorWrite)) { &hOutputWrite, &hErrorWrite)) {
LOGERR("ExecCmd::startExec: preparePipes failed\n" ); LOGERR("ExecCmd::startExec: preparePipes failed\n");
return false; return false;
} }
@ -786,21 +789,21 @@ int ExecCmd::startExec(const string &cmd, const vector<string>& args,
siStartInfo.hStdInput = hInputRead; siStartInfo.hStdInput = hInputRead;
siStartInfo.hStdError = hErrorWrite; siStartInfo.hStdError = hErrorWrite;
char *envir = mergeEnvironment(m->m_env); wchar_t *envir = mergeEnvironment(m->m_env);
int flags = CREATE_NEW_PROCESS_GROUP | CREATE_UNICODE_ENVIRONMENT;
// Create the child process. // Create the child process.
LOGDEB("ExecCmd:startExec: cmdline [" << cmdline << "]\n"); LOGDEB("ExecCmd:startExec: cmdline [" << cmdline << "]\n");
SYSPATH(cmdline, wcmdline); SYSPATH(cmdline, wcmdline);
bSuccess = CreateProcessW(NULL, bSuccess = CreateProcessW(NULL, // app name
wcmdline, // command line wcmdline, // command line
NULL, // process security attributes NULL, // process security attributes
NULL, // primary thread security attrs NULL, // primary thread security attrs
TRUE, // handles are inherited TRUE, // handles are inherited
CREATE_NEW_PROCESS_GROUP, // creation flags flags, // creation flags
envir, // Merged environment envir, // Merged environment
NULL, // use parent's current directory NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer &siStartInfo, // STARTUPINFO pointer
&m->m_piProcInfo); // PROCESS_INFORMATION &m->m_piProcInfo); // PROCESS_INFORMATION
if (!bSuccess) { if (!bSuccess) {
printError("ExecCmd::doexec: CreateProcess"); printError("ExecCmd::doexec: CreateProcess");
} }
@ -823,7 +826,7 @@ int ExecCmd::startExec(const string &cmd, const vector<string>& args,
// Send data to the child. // Send data to the child.
int ExecCmd::send(const string& data) int ExecCmd::send(const string& data)
{ {
LOGDEB2("ExecCmd::send: cnt " << (int(data.size())) << "\n" ); LOGDEB2("ExecCmd::send: cnt " << data.size() << "\n");
BOOL bSuccess = WriteFile(m->m_hInputWrite, data.c_str(), BOOL bSuccess = WriteFile(m->m_hInputWrite, data.c_str(),
(DWORD)data.size(), NULL, &m->m_oInputWrite); (DWORD)data.size(), NULL, &m->m_oInputWrite);
DWORD err = GetLastError(); DWORD err = GetLastError();
@ -833,7 +836,7 @@ int ExecCmd::send(const string& data)
// and ERROR_IO_PENDING // and ERROR_IO_PENDING
// in the first case bytes read/written parameter can be used directly // in the first case bytes read/written parameter can be used directly
if (!bSuccess && err != ERROR_IO_PENDING) { if (!bSuccess && err != ERROR_IO_PENDING) {
LOGERR("ExecCmd::send: WriteFile: got err " << (err) << "\n" ); LOGERR("ExecCmd::send: WriteFile: got err " << err << "\n");
return -1; return -1;
} }
@ -843,7 +846,7 @@ int ExecCmd::send(const string& data)
if (!GetOverlappedResult(m->m_hInputWrite, if (!GetOverlappedResult(m->m_hInputWrite,
&m->m_oInputWrite, &dwWritten, TRUE)) { &m->m_oInputWrite, &dwWritten, TRUE)) {
err = GetLastError(); err = GetLastError();
LOGERR("ExecCmd::send: GetOverLappedResult: err " << (err) << "\n" ); LOGERR("ExecCmd::send: GetOverLappedResult: err " << err << "\n");
return -1; return -1;
} }
} else if (waitRes == Quit) { } else if (waitRes == Quit) {
@ -859,7 +862,7 @@ int ExecCmd::send(const string& data)
} }
return -1; return -1;
} }
LOGDEB2("ExecCmd::send: returning " << (int(dwWritten)) << "\n" ); LOGDEB2("ExecCmd::send: returning " << (int(dwWritten)) << "\n");
return dwWritten; return dwWritten;
} }
@ -874,7 +877,7 @@ int ExecCmd::send(const string& data)
// 0 means read whatever comes back on the first read; // 0 means read whatever comes back on the first read;
int ExecCmd::receive(string& data, int cnt) int ExecCmd::receive(string& data, int cnt)
{ {
LOGDEB1("ExecCmd::receive: cnt " << (cnt) << "\n" ); LOGDEB1("ExecCmd::receive: cnt " << cnt << "\n");
int totread = 0; int totread = 0;
@ -896,10 +899,10 @@ int ExecCmd::receive(string& data, int cnt)
BOOL bSuccess = ReadFile(m->m_hOutputRead, chBuf, toread, BOOL bSuccess = ReadFile(m->m_hOutputRead, chBuf, toread,
NULL, &m->m_oOutputRead); NULL, &m->m_oOutputRead);
DWORD err = GetLastError(); DWORD err = GetLastError();
LOGDEB1("receive: ReadFile: success " << (int(bSuccess)) << " err " << (int(err)) << "\n" ); LOGDEB1("receive: ReadFile: success " <<bSuccess<<" err "<< err << "\n");
if (!bSuccess && err != ERROR_IO_PENDING) { if (!bSuccess && err != ERROR_IO_PENDING) {
if (err != ERROR_BROKEN_PIPE) if (err != ERROR_BROKEN_PIPE)
LOGERR("ExecCmd::receive: ReadFile error: " << (int(err)) << "\n" ); LOGERR("ExecCmd::receive: ReadFile error: " << err << "\n");
break; break;
} }
@ -911,7 +914,8 @@ int ExecCmd::receive(string& data, int cnt)
&dwRead, TRUE)) { &dwRead, TRUE)) {
err = GetLastError(); err = GetLastError();
if (err && err != ERROR_BROKEN_PIPE) { if (err && err != ERROR_BROKEN_PIPE) {
LOGERR("ExecCmd::recv:GetOverlappedResult: err " << (err) << "\n" ); LOGERR("ExecCmd::recv:GetOverlappedResult: err " << err <<
"\n");
return -1; return -1;
} }
} }
@ -920,7 +924,7 @@ int ExecCmd::receive(string& data, int cnt)
data.append(chBuf, dwRead); data.append(chBuf, dwRead);
if (m->m_advise) if (m->m_advise)
m->m_advise->newData(dwRead); m->m_advise->newData(dwRead);
LOGDEB1("ExecCmd::recv: ReadFile: " << (int(dwRead)) << " bytes\n" ); LOGDEB1("ExecCmd::recv: ReadFile: " << dwRead << " bytes\n");
} }
} else if (waitRes == Quit) { } else if (waitRes == Quit) {
if (!CancelIo(m->m_hOutputRead)) { if (!CancelIo(m->m_hOutputRead)) {
@ -928,7 +932,7 @@ int ExecCmd::receive(string& data, int cnt)
} }
break; break;
} else if (waitRes == Timeout) { } else if (waitRes == Timeout) {
LOGDEB0("ExecCmd::receive: timeout (" << (m->m_timeoutMs) << " mS)\n" ); LOGDEB0("ExecCmd::receive: timeout (" << m->m_timeoutMs << " mS)\n");
if (m->tooBig()) { if (m->tooBig()) {
if (!CancelIo(m->m_hOutputRead)) { if (!CancelIo(m->m_hOutputRead)) {
printError("CancelIo"); printError("CancelIo");
@ -947,7 +951,7 @@ int ExecCmd::receive(string& data, int cnt)
} }
} }
if (m->m_killRequest) { if (m->m_killRequest) {
LOGINFO("ExecCmd::doexec: cancel request\n" ); LOGINFO("ExecCmd::doexec: cancel request\n");
if (!CancelIo(m->m_hOutputRead)) { if (!CancelIo(m->m_hOutputRead)) {
printError("CancelIo"); printError("CancelIo");
} }
@ -958,13 +962,13 @@ int ExecCmd::receive(string& data, int cnt)
if ((cnt == 0 && totread > 0) || (cnt > 0 && totread == cnt)) if ((cnt == 0 && totread > 0) || (cnt > 0 && totread == cnt))
break; break;
} }
LOGDEB1("ExecCmd::receive: returning " << (totread) << " bytes\n" ); LOGDEB1("ExecCmd::receive: returning " << totread << " bytes\n");
return totread; return totread;
} }
int ExecCmd::getline(string& data) int ExecCmd::getline(string& data)
{ {
LOGDEB2("ExecCmd::getline: cnt " << (cnt) << ", timeo " << (timeo) << "\n" ); LOGDEB2("ExecCmd::getline: cnt " << cnt << ", timeo " << timeo << "\n");
data.erase(); data.erase();
if (m->m_buf.empty()) { if (m->m_buf.empty()) {
m->m_buf.reserve(4096); m->m_buf.reserve(4096);
@ -989,7 +993,7 @@ int ExecCmd::getline(string& data)
} }
if (foundnl) { if (foundnl) {
LOGDEB2("ExecCmd::getline: ret: [" << (data) << "]\n" ); LOGDEB2("ExecCmd::getline: ret: [" << data << "]\n");
return int(data.size()); return int(data.size());
} }
@ -999,7 +1003,7 @@ int ExecCmd::getline(string& data)
return -1; return -1;
} }
if (m->m_buf.empty()) { if (m->m_buf.empty()) {
LOGDEB("ExecCmd::getline: eof? ret: [" << (data) << "]\n" ); LOGDEB("ExecCmd::getline: eof? ret: [" << data << "]\n");
return int(data.size()); return int(data.size());
} }
m->m_bufoffs = 0; m->m_bufoffs = 0;
@ -1019,7 +1023,7 @@ int ExecCmd::wait()
// Wait until child process exits. // Wait until child process exits.
while (WaitForSingleObject(m->m_piProcInfo.hProcess, m->m_timeoutMs) while (WaitForSingleObject(m->m_piProcInfo.hProcess, m->m_timeoutMs)
== WAIT_TIMEOUT) { == WAIT_TIMEOUT) {
LOGDEB("ExecCmd::wait: timeout (ok)\n" ); LOGDEB("ExecCmd::wait: timeout (ok)\n");
if (m->m_advise) { if (m->m_advise) {
m->m_advise->newData(0); m->m_advise->newData(0);
} }
@ -1089,7 +1093,7 @@ int ExecCmd::doexec(const string &cmd, const vector<string>& args,
const string *input, string *output) const string *input, string *output)
{ {
if (input && output) { if (input && output) {
LOGERR("ExecCmd::doexec: can't do both input and output\n" ); LOGERR("ExecCmd::doexec: can't do both input and output\n");
return -1; return -1;
} }
@ -1105,7 +1109,7 @@ int ExecCmd::doexec(const string &cmd, const vector<string>& args,
if (input) { if (input) {
if (!input->empty()) { if (!input->empty()) {
if (send(*input) != (int)input->size()) { if (send(*input) != (int)input->size()) {
LOGERR("ExecCmd::doexec: send failed\n" ); LOGERR("ExecCmd::doexec: send failed\n");
CloseHandle(m->m_hInputWrite); CloseHandle(m->m_hInputWrite);
m->m_hInputWrite = NULL; m->m_hInputWrite = NULL;
return -1; return -1;
@ -1120,7 +1124,7 @@ int ExecCmd::doexec(const string &cmd, const vector<string>& args,
break; break;
} }
if (send(*input) != (int)input->size()) { if (send(*input) != (int)input->size()) {
LOGERR("ExecCmd::doexec: send failed\n" ); LOGERR("ExecCmd::doexec: send failed\n");
CloseHandle(m->m_hInputWrite); CloseHandle(m->m_hInputWrite);
m->m_hInputWrite = NULL; m->m_hInputWrite = NULL;
break; break;
@ -1133,4 +1137,3 @@ int ExecCmd::doexec(const string &cmd, const vector<string>& args,
cleaner.inactivate(); cleaner.inactivate();
return wait(); return wait();
} }