blob: 3979c410d4d1ec5d566be60530397533feccd0ba [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Nokia and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nokia - Initial API and implementation
*******************************************************************************/
#include "ProcessService.h"
#include <algorithm>
#include <string>
#include <set>
#include <Tlhelp32.h>
#include "TCFHeaders.h"
#include "TCFChannel.h"
#include "WinDebugMonitor.h"
#include "WinProcess.h"
#include "AgentUtils.h"
#include "ContextManager.h"
#include "EventClientNotifier.h"
#include "Logger.h"
#include "DetachProcessAction.h"
static const char * sServiceName = "Processes";
static std::string quoteIfNeeded(const char* source);
ProcessService::ProcessService(Protocol * proto) :
TCFService(proto) {
AddCommand("getContext", command_get_context);
AddCommand("getChildren", command_get_children);
AddCommand("attach", command_attach);
AddCommand("detach", command_detach);
AddCommand("terminate", command_terminate);
AddCommand("signal", command_signal);
AddCommand("getEnvironment", command_get_environment);
AddCommand("start", command_start);
}
ProcessService::~ProcessService(void) {
}
const char* ProcessService::GetName() {
return sServiceName;
}
/*
* This will return a context, running or debugged one.
*/
void ProcessService::command_get_context(const char * token, Channel * c) {
LogTrace("ProcessService::command_get_context", "token: %s", token);
TCFChannel channel(c);
std::string id = channel.readString();
channel.readZero();
channel.readComplete();
Context* context = ContextManager::findContext(id);
if (context == NULL) {
// Return an invalid context ID error.
channel.writeCompleteReply(token, ERR_INV_CONTEXT, 1);
return;
}
channel.writeReplyHeader(token);
channel.writeError(0);
EventClientNotifier::WriteContext(*context, channel);
channel.writeZero();
channel.writeComplete();
}
/** Filter to get choose Contexts which are the top-level RunControlContext that were from the
* previous Processes::getChildren call. Ignore those that are being debugged. */
static bool IsNotRunningProcessContext(const ContextID& contextID) {
Context* context = ContextManager::findContext(contextID);
ProcessContext* rcContext = dynamic_cast<ProcessContext*>(context);
trace(LOG_ALWAYS, "Context %s: isRC=%d, isDebugging=%d", contextID.c_str(), rcContext!=0, rcContext && rcContext->IsDebugging());
return !rcContext || rcContext->IsDebugging();
}
/** Remove and delete a context by value. */
static void RemoveAndDeleteContext(const ContextID& contextID) {
trace(LOG_ALWAYS, "Removing context %s", contextID.c_str());
delete ContextManager::removeContext(contextID);
}
void ProcessService::command_get_children(const char * token, Channel * c) {
char id[256];
int attached_only;
json_read_string(&c->inp, id, sizeof(id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
attached_only = json_read_boolean(&c->inp);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
if (id[0] != 0) {
write_errno(&c->out, 0);
write_stringz(&c->out, "null");
}
else {
DWORD err = 0;
HANDLE snapshot;
PROCESSENTRY32 pe32;
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) err = set_win32_errno(GetLastError());
memset(&pe32, 0, sizeof(pe32));
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!err && !Process32First(snapshot, &pe32)) {
err = set_win32_errno(GetLastError());
CloseHandle(snapshot);
}
write_errno(&c->out, err);
if (err) {
write_stringz(&c->out, "null");
}
else {
// Get all the known contexts
std::list<ContextID> runContexts = ContextManager::getContexts();
// Shuffle to the end the ones we want to remove
std::list<ContextID>::iterator removeEnd = std::remove_if(runContexts.begin(), runContexts.end(),
IsNotRunningProcessContext);
// Remove these entries from the manager
std::for_each(runContexts.begin(), removeEnd, RemoveAndDeleteContext);
int cnt = 0;
write_stream(&c->out, '[');
do {
if (!attached_only || WinProcess::GetProcessByID(pe32.th32ProcessID) != NULL) {
if (cnt > 0) write_stream(&c->out, ',');
// We just pass the OS process ID.
// If context still exists, it is being debugged, so leave it be.
WinProcess* process = dynamic_cast<WinProcess*>(ContextManager::findContext(
WinProcess::CreateInternalID(pe32.th32ProcessID)));
if (!process) {
process = new WinProcess(pe32.th32ProcessID, pe32.szExeFile);
ContextManager::addContext(process);
}
// Tell host the unique internal ID.
json_write_string(&c->out, process->GetID().c_str());
cnt++;
}
} while (Process32Next(snapshot, &pe32));
write_stream(&c->out, ']');
write_stream(&c->out, 0);
}
if (snapshot != INVALID_HANDLE_VALUE)
CloseHandle(snapshot);
}
write_stream(&c->out, MARKER_EOM);
}
void ProcessService::command_attach(const char * token, Channel * c) {
LogTrace("ProcessService::command_attach", "token: %s", token);
TCFChannel channel(c);
std::string id = channel.readString();
channel.readZero();
channel.readComplete();
RunControlContext* context = dynamic_cast<RunControlContext*>(ContextManager::findContext(id));
// TODO: or not running
if (context == NULL) {
// Return an invalid context ID error.
channel.writeCompleteReply(token, ERR_INV_CONTEXT);
return;
}
// This function will report result to host debugger.
AttachToProcessParams params(token, c, context->GetOSID(), true);
WinDebugMonitor::AttachToProcess(params);
}
void ProcessService::command_detach(const char * token, Channel * c) {
LogTrace("ProcessService::command_detach", "token: %s", token);
TCFChannel channel(c);
std::string id = channel.readString();
channel.readZero();
channel.readComplete();
WinProcess* context = dynamic_cast<WinProcess*>(ContextManager::findContext(id));
if (context == NULL || !context->IsDebugging()) {
// Return an invalid context ID error.
channel.writeCompleteReply(token, ERR_INV_CONTEXT);
return;
}
context->GetMonitor()->PostAction(new DetachProcessAction(
AgentActionParams(token, c), context->GetOSID()));
}
void ProcessService::command_terminate(const char * token, Channel * c) {
}
void ProcessService::command_signal(const char * token, Channel * c) {
}
void ProcessService::command_get_environment(const char * token, Channel * c) {
char ** p = environ;
TCFChannel channel(c);
channel.readComplete();
channel.writeReplyHeader(token);
channel.writeError(0);
channel.writeCharacter('[');
if (p != NULL) {
while (*p != NULL) {
if (p != environ)
channel.writeCharacter(',');
json_write_string(&c->out, *p++);
}
}
channel.writeCharacter(']');
channel.writeZero();
channel.writeComplete();
}
std::wstring json_read_string(Channel * c) {
char stringbuffer[4096];
json_read_string(&c->inp, stringbuffer, sizeof(stringbuffer));
if (c->inp.read(&c->inp) != 0)
exception(ERR_JSON_SYNTAX);
int wideSize = MultiByteToWideChar(CP_UTF8, 0, stringbuffer, strlen(
stringbuffer) + 1, NULL, 0);
wchar_t* wideChars = new wchar_t[wideSize];
MultiByteToWideChar(CP_UTF8, 0, stringbuffer, strlen(stringbuffer) + 1,
wideChars, wideSize);
std::wstring result = wideChars;
delete[] wideChars;
return result;
}
void ProcessService::command_start(const char * token, Channel * c) {
TCFChannel channel(c);
std::string directory = channel.readString();
channel.readZero();
std::string executable = channel.readString();
channel.readZero();
char ** args = NULL;
char ** envp = NULL;
int args_len = 0;
int envp_len = 0;
args = json_read_alloc_string_array(&c->inp, &args_len);
if (c->inp.read(&c->inp) != 0)
exception(ERR_JSON_SYNTAX);
envp = json_read_alloc_string_array(&c->inp, &envp_len);
if (c->inp.read(&c->inp) != 0)
exception(ERR_JSON_SYNTAX);
std::vector<std::string> environment;
for (int i = 0; i < envp_len; i++)
{
environment.push_back(envp[i]);
}
// this is the "attach" parameter, but note that in this case
// it's really if you want to stay attached to the process we're
// launching in order to debug it, vs. just launching it in order
// to run it (without debugging). for attaching to an existing
// process, command_attach will be called.
// TODO currently ignored, but we need to honor it if/when we add
// support for running vs. debugging.
/* bool debug = */ json_read_boolean(&c->inp); // attach
if (read_stream(&c->inp) != 0)
exception(ERR_JSON_SYNTAX);
channel.readComplete();
std::string wargs;
wargs += quoteIfNeeded(executable.c_str());
for (int i = 0; i < args_len; i++) {
wargs += ' ';
wargs += quoteIfNeeded(args[i]);
}
loc_free(args);
loc_free(envp);
LaunchProcessParams params(token, c, executable, directory, wargs, environment, true);
WinDebugMonitor::LaunchProcess(params);
}
/**
* Escape a string for the Win32 command line.
*
* Adapted from Win32ProcessEx.c in spawner:
*
* Copyright (c) 2002, 2010 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX Software Systems - initial API and implementation
* Wind River Systems, Inc.
*******************************************************************************/
static std::string quoteIfNeeded(const char* source) {
std::string target;
int cpyLength = strlen(source);
BOOL bSlash = FALSE;
int i = 0;
#define QUOTATION_DO 0
#define QUOTATION_DONE 1
#define QUOTATION_NONE 2
#undef _T
#define _T(x) x
int nQuotationMode = 0;
if((_T('\"') == *source) && (_T('\"') == *(source + cpyLength - 1)))
{
nQuotationMode = QUOTATION_DONE;
}
else
if(strchr(source, _T(' ')) == NULL)
{
// No reason to quotate term becase it doesn't have embedded spaces
nQuotationMode = QUOTATION_NONE;
}
else
{
// Needs to be quotated
nQuotationMode = QUOTATION_DO;
target += _T('\"');
}
for(; i < cpyLength; ++i)
{
if(source[i] == _T('\\'))
bSlash = TRUE;
else
{
// Don't quote embracing quotation marks
if((source[i] == _T('\"')) && !((nQuotationMode == QUOTATION_DONE) && ((i == 0) || (i == (cpyLength - 1))) ) )
{
if(!bSlash) // If still not escaped
{
target += _T('\\');
}
}
bSlash = FALSE;
}
target += source[i];
}
if(nQuotationMode == QUOTATION_DO)
{
target += _T('\"');
}
return target;
}