summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/message_loop.cc4
-rw-r--r--base/message_loop.h3
-rw-r--r--base/message_pump_win.cc51
-rw-r--r--base/message_pump_win.h19
-rw-r--r--remoting/base/stoppable.cc2
-rw-r--r--remoting/host/host_user_interface.cc3
-rw-r--r--remoting/host/win/host_service.cc101
-rw-r--r--remoting/host/win/host_service.h3
-rw-r--r--remoting/host/win/launch_process_with_token.cc12
-rw-r--r--remoting/host/win/launch_process_with_token.h4
-rw-r--r--remoting/host/win/wts_session_process_launcher.cc352
-rw-r--r--remoting/host/win/wts_session_process_launcher.h41
-rw-r--r--remoting/remoting.gyp3
13 files changed, 464 insertions, 134 deletions
diff --git a/base/message_loop.cc b/base/message_loop.cc
index 7cadede..32994bf 100644
--- a/base/message_loop.cc
+++ b/base/message_loop.cc
@@ -751,6 +751,10 @@ void MessageLoopForIO::RegisterIOHandler(HANDLE file, IOHandler* handler) {
pump_io()->RegisterIOHandler(file, handler);
}
+bool MessageLoopForIO::RegisterJobObject(HANDLE job, IOHandler* handler) {
+ return pump_io()->RegisterJobObject(job, handler);
+}
+
bool MessageLoopForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
return pump_io()->WaitForIOCompletion(timeout, filter);
}
diff --git a/base/message_loop.h b/base/message_loop.h
index 2ff963f..b54ca85 100644
--- a/base/message_loop.h
+++ b/base/message_loop.h
@@ -631,7 +631,8 @@ class BASE_EXPORT MessageLoopForIO : public MessageLoop {
#if defined(OS_WIN)
// Please see MessagePumpWin for definitions of these methods.
- void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);
+ void RegisterIOHandler(HANDLE file, IOHandler* handler);
+ bool RegisterJobObject(HANDLE job, IOHandler* handler);
bool WaitForIOCompletion(DWORD timeout, IOHandler* filter);
protected:
diff --git a/base/message_pump_win.cc b/base/message_pump_win.cc
index 2b2a10e..40118da 100644
--- a/base/message_pump_win.cc
+++ b/base/message_pump_win.cc
@@ -475,11 +475,26 @@ void MessagePumpForIO::ScheduleDelayedWork(const TimeTicks& delayed_work_time) {
void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle,
IOHandler* handler) {
- ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
+ ULONG_PTR key = HandlerToKey(handler, true);
HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1);
DPCHECK(port);
}
+bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle,
+ IOHandler* handler) {
+ // Job object notifications use the OVERLAPPED pointer to carry the message
+ // data. Mark the completion key correspondingly, so we will not try to
+ // convert OVERLAPPED* to IOContext*.
+ ULONG_PTR key = HandlerToKey(handler, false);
+ JOBOBJECT_ASSOCIATE_COMPLETION_PORT info;
+ info.CompletionKey = reinterpret_cast<void*>(key);
+ info.CompletionPort = port_;
+ return SetInformationJobObject(job_handle,
+ JobObjectAssociateCompletionPortInformation,
+ &info,
+ sizeof(info)) != FALSE;
+}
+
//-----------------------------------------------------------------------------
// MessagePumpForIO private:
@@ -546,12 +561,16 @@ bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
return true;
}
- if (item.context->handler) {
+ // If |item.has_valid_io_context| is false then |item.context| does not point
+ // to a context structure, and so should not be dereferenced, although it may
+ // still hold valid non-pointer data.
+ if (!item.has_valid_io_context || item.context->handler) {
if (filter && item.handler != filter) {
// Save this item for later
completed_io_.push_back(item);
} else {
- DCHECK_EQ(item.context->handler, item.handler);
+ DCHECK(!item.has_valid_io_context ||
+ (item.context->handler == item.handler));
WillProcessIOEvent();
item.handler->OnIOCompleted(item.context, item.bytes_transfered,
item.error);
@@ -577,7 +596,7 @@ bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {
item->bytes_transfered = 0;
}
- item->handler = reinterpret_cast<IOHandler*>(key);
+ item->handler = KeyToHandler(key, &item->has_valid_io_context);
item->context = reinterpret_cast<IOContext*>(overlapped);
return true;
}
@@ -623,4 +642,28 @@ void MessagePumpForIO::DidProcessIOEvent() {
FOR_EACH_OBSERVER(IOObserver, io_observers_, DidProcessIOEvent());
}
+// static
+ULONG_PTR MessagePumpForIO::HandlerToKey(IOHandler* handler,
+ bool has_valid_io_context) {
+ ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
+
+ // |IOHandler| is at least pointer-size aligned, so the lowest two bits are
+ // always cleared. We use the lowest bit to distinguish completion keys with
+ // and without the associated |IOContext|.
+ DCHECK((key & 1) == 0);
+
+ // Mark the completion key as context-less.
+ if (!has_valid_io_context)
+ key = key | 1;
+ return key;
+}
+
+// static
+MessagePumpForIO::IOHandler* MessagePumpForIO::KeyToHandler(
+ ULONG_PTR key,
+ bool* has_valid_io_context) {
+ *has_valid_io_context = ((key & 1) == 0);
+ return reinterpret_cast<IOHandler*>(key & ~static_cast<ULONG_PTR>(1));
+}
+
} // namespace base
diff --git a/base/message_pump_win.h b/base/message_pump_win.h
index e778e01..fd46198 100644
--- a/base/message_pump_win.h
+++ b/base/message_pump_win.h
@@ -296,6 +296,12 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
// |handler| must be valid as long as there is pending IO for the given file.
void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);
+ // Register the handler to be used to process job events. The registration
+ // persists as long as the job object is live, so |handler| must be valid
+ // until the job object is destroyed. Returns true if the registration
+ // succeeded, and false otherwise.
+ bool RegisterJobObject(HANDLE job_handle, IOHandler* handler);
+
// Waits for the next IO completion that should be processed by |filter|, for
// up to |timeout| milliseconds. Return true if any IO operation completed,
// regardless of the involved handler, and false if the timeout expired. If
@@ -316,6 +322,11 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
IOContext* context;
DWORD bytes_transfered;
DWORD error;
+
+ // In some cases |context| can be a non-pointer value casted to a pointer.
+ // |has_valid_io_context| is true if |context| is a valid IOContext
+ // pointer, and false otherwise.
+ bool has_valid_io_context;
};
virtual void DoRunLoop();
@@ -326,6 +337,14 @@ class BASE_EXPORT MessagePumpForIO : public MessagePumpWin {
void WillProcessIOEvent();
void DidProcessIOEvent();
+ // Converts an IOHandler pointer to a completion port key.
+ // |has_valid_io_context| specifies whether completion packets posted to
+ // |handler| will have valid OVERLAPPED pointers.
+ static ULONG_PTR HandlerToKey(IOHandler* handler, bool has_valid_io_context);
+
+ // Converts a completion port key to an IOHandler pointer.
+ static IOHandler* KeyToHandler(ULONG_PTR key, bool* has_valid_io_context);
+
// The completion port associated with this thread.
win::ScopedHandle port_;
// This list will be empty almost always. It stores IO completions that have
diff --git a/remoting/base/stoppable.cc b/remoting/base/stoppable.cc
index 046ac42..99acba4 100644
--- a/remoting/base/stoppable.cc
+++ b/remoting/base/stoppable.cc
@@ -18,7 +18,7 @@ Stoppable::Stoppable(
}
Stoppable::~Stoppable() {
- DCHECK_EQ(state_, kStopped);
+ CHECK_EQ(state_, kStopped);
}
void Stoppable::Stop() {
diff --git a/remoting/host/host_user_interface.cc b/remoting/host/host_user_interface.cc
index 925e106..9172370 100644
--- a/remoting/host/host_user_interface.cc
+++ b/remoting/host/host_user_interface.cc
@@ -32,7 +32,6 @@ void HostUserInterface::Start(ChromotingHost* host,
const base::Closure& disconnect_callback) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
DCHECK(host_ == NULL);
- DCHECK(disconnect_callback_.is_null());
host_ = host;
disconnect_callback_ = disconnect_callback;
@@ -91,7 +90,6 @@ base::SingleThreadTaskRunner* HostUserInterface::ui_task_runner() const {
void HostUserInterface::DisconnectSession() const {
DCHECK(ui_task_runner()->BelongsToCurrentThread());
- DCHECK(!disconnect_callback_.is_null());
disconnect_callback_.Run();
}
@@ -118,7 +116,6 @@ void HostUserInterface::StartForTest(
scoped_ptr<LocalInputMonitor> local_input_monitor) {
DCHECK(network_task_runner()->BelongsToCurrentThread());
DCHECK(host_ == NULL);
- DCHECK(disconnect_callback_.is_null());
host_ = host;
disconnect_callback_ = disconnect_callback;
diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc
index 0c6ccb5..145c930 100644
--- a/remoting/host/win/host_service.cc
+++ b/remoting/host/win/host_service.cc
@@ -8,11 +8,12 @@
#include "remoting/host/win/host_service.h"
#include <windows.h>
+#include <shellapi.h>
#include <wtsapi32.h>
-#include <stdio.h>
#include "base/at_exit.h"
#include "base/base_paths.h"
+#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_path.h"
@@ -53,35 +54,42 @@ const char kIoThreadName[] = "I/O thread";
const wchar_t kSessionNotificationWindowClass[] =
L"Chromoting_SessionNotificationWindow";
-// Command line actions and switches:
-// "run" sumply runs the service as usual.
-const wchar_t kRunActionName[] = L"run";
+// Command line switches:
// "--console" runs the service interactively for debugging purposes.
const char kConsoleSwitchName[] = "console";
+// "--elevate=<binary>" requests <binary> to be launched elevated, presenting
+// a UAC prompt if necessary.
+const char kElevateSwitchName[] = "elevate";
+
// "--help" or "--?" prints the usage message.
const char kHelpSwitchName[] = "help";
const char kQuestionSwitchName[] = "?";
-const char kUsageMessage[] =
- "\n"
- "Usage: %s [action] [options]\n"
- "\n"
- "Actions:\n"
- " run - Run the service (default if no action was specified).\n"
- "\n"
- "Options:\n"
- " --console - Run the service interactively for debugging purposes.\n"
- " --help, --? - Print this message.\n";
+const wchar_t kUsageMessage[] =
+ L"\n"
+ L"Usage: %ls [options]\n"
+ L"\n"
+ L"Options:\n"
+ L" --console - Run the service interactively for debugging purposes.\n"
+ L" --elevate=<...> - Run <...> elevated.\n"
+ L" --help, --? - Print this message.\n";
+
+// The command line parameters that should be copied from the service's command
+// line when launching an elevated child.
+const char* kCopiedSwitchNames[] = {
+ "auth-config", "host-config", "chromoting-ipc", switches::kV,
+ switches::kVModule };
// Exit codes:
const int kSuccessExitCode = 0;
const int kUsageExitCode = 1;
const int kErrorExitCode = 2;
-void usage(const char* program_name) {
- fprintf(stderr, kUsageMessage, program_name);
+void usage(const FilePath& program_name) {
+ LOG(INFO) << StringPrintf(kUsageMessage,
+ UTF16ToWide(program_name.value()).c_str());
}
} // namespace
@@ -164,17 +172,15 @@ HostService* HostService::GetInstance() {
bool HostService::InitWithCommandLine(const CommandLine* command_line) {
CommandLine::StringVector args = command_line->GetArgs();
- // Choose the action to perform.
+ // Check if launch with elevation was requested.
+ if (command_line->HasSwitch(kElevateSwitchName)) {
+ run_routine_ = &HostService::Elevate;
+ return true;
+ }
+
if (!args.empty()) {
- if (args.size() > 1) {
- LOG(ERROR) << "Invalid command line: more than one action requested.";
- return false;
- }
- if (args[0] != kRunActionName) {
- LOG(ERROR) << "Invalid command line: invalid action specified: "
- << args[0];
- return false;
- }
+ LOG(ERROR) << "No positional parameters expected.";
+ return false;
}
// Run interactively if needed.
@@ -225,6 +231,35 @@ void HostService::RunMessageLoop(MessageLoop* message_loop) {
stopped_event_.Signal();
}
+int HostService::Elevate() {
+ // Get the name of the binary to launch.
+ FilePath binary =
+ CommandLine::ForCurrentProcess()->GetSwitchValuePath(kElevateSwitchName);
+
+ // Create the child process command line by copying known switches from our
+ // command line.
+ CommandLine command_line(CommandLine::NO_PROGRAM);
+ command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
+ kCopiedSwitchNames,
+ _countof(kCopiedSwitchNames));
+ CommandLine::StringType parameters = command_line.GetCommandLineString();
+
+ // Launch the child process requesting elevation.
+ SHELLEXECUTEINFO info;
+ memset(&info, 0, sizeof(info));
+ info.cbSize = sizeof(info);
+ info.lpVerb = L"runas";
+ info.lpFile = binary.value().c_str();
+ info.lpParameters = parameters.c_str();
+ info.nShow = SW_SHOWNORMAL;
+
+ if (!ShellExecuteEx(&info)) {
+ return GetLastError();
+ }
+
+ return kSuccessExitCode;
+}
+
int HostService::RunAsService() {
SERVICE_TABLE_ENTRYW dispatch_table[] = {
{ const_cast<LPWSTR>(kWindowsServiceName), &HostService::ServiceMain },
@@ -409,14 +444,19 @@ LRESULT CALLBACK HostService::SessionChangeNotificationProc(HWND hwnd,
} // namespace remoting
-int main(int argc, char** argv) {
+int CALLBACK WinMain(HINSTANCE instance,
+ HINSTANCE previous_instance,
+ LPSTR raw_command_line,
+ int show_command) {
#ifdef OFFICIAL_BUILD
if (remoting::IsUsageStatsAllowed()) {
remoting::InitializeCrashReporting();
}
#endif // OFFICIAL_BUILD
- CommandLine::Init(argc, argv);
+ // CommandLine::Init() ignores the passed |argc| and |argv| on Windows getting
+ // the command line from GetCommandLineW(), so we can safely pass NULL here.
+ CommandLine::Init(0, NULL);
// This object instance is required by Chrome code (for example,
// FilePath, LazyInstance, MessageLoop).
@@ -432,16 +472,15 @@ int main(int argc, char** argv) {
logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
const CommandLine* command_line = CommandLine::ForCurrentProcess();
-
if (command_line->HasSwitch(kHelpSwitchName) ||
command_line->HasSwitch(kQuestionSwitchName)) {
- usage(argv[0]);
+ usage(command_line->GetProgram());
return kSuccessExitCode;
}
remoting::HostService* service = remoting::HostService::GetInstance();
if (!service->InitWithCommandLine(command_line)) {
- usage(argv[0]);
+ usage(command_line->GetProgram());
return kUsageExitCode;
}
diff --git a/remoting/host/win/host_service.h b/remoting/host/win/host_service.h
index 5ff5a79..9533f02 100644
--- a/remoting/host/win/host_service.h
+++ b/remoting/host/win/host_service.h
@@ -61,6 +61,9 @@ class HostService : public WtsConsoleMonitor {
// RunAsService() and RunInConsole().
void RunMessageLoop(MessageLoop* message_loop);
+ // Runs the binary specified by the command line, elevated.
+ int Elevate();
+
// This function handshakes with the service control manager and starts
// the service.
int RunAsService();
diff --git a/remoting/host/win/launch_process_with_token.cc b/remoting/host/win/launch_process_with_token.cc
index ae0e1e6..1320e2a 100644
--- a/remoting/host/win/launch_process_with_token.cc
+++ b/remoting/host/win/launch_process_with_token.cc
@@ -103,6 +103,7 @@ bool CreateRemoteSessionProcess(
uint32 session_id,
const FilePath::StringType& application_name,
const CommandLine::StringType& command_line,
+ DWORD creation_flags,
PROCESS_INFORMATION* process_information_out)
{
DCHECK(base::win::GetVersion() == base::win::VERSION_XP);
@@ -203,8 +204,9 @@ bool CreateRemoteSessionProcess(
CreateProcessRequest* request =
reinterpret_cast<CreateProcessRequest*>(buffer.get());
request->size = size;
- request->use_default_token = TRUE;
request->process_id = GetCurrentProcessId();
+ request->use_default_token = TRUE;
+ request->creation_flags = creation_flags;
request->startup_info.cb = sizeof(request->startup_info);
size_t buffer_offset = sizeof(CreateProcessRequest);
@@ -345,7 +347,9 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
bool LaunchProcessWithToken(const FilePath& binary,
const CommandLine::StringType& command_line,
HANDLE user_token,
- ScopedHandle* process_out) {
+ DWORD creation_flags,
+ ScopedHandle* process_out,
+ ScopedHandle* thread_out) {
FilePath::StringType application_name = binary.value();
base::win::ScopedProcessInformation process_info;
@@ -363,7 +367,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
NULL,
NULL,
FALSE,
- 0,
+ creation_flags,
NULL,
NULL,
&startup_info,
@@ -389,6 +393,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
result = CreateRemoteSessionProcess(session_id,
application_name,
command_line,
+ creation_flags,
process_info.Receive());
} else {
// Restore the error status returned by CreateProcessAsUser().
@@ -405,6 +410,7 @@ bool LaunchProcessWithToken(const FilePath& binary,
CHECK(process_info.IsValid());
process_out->Set(process_info.TakeProcessHandle());
+ thread_out->Set(process_info.TakeThreadHandle());
return true;
}
diff --git a/remoting/host/win/launch_process_with_token.h b/remoting/host/win/launch_process_with_token.h
index 196fdeb..8f46219 100644
--- a/remoting/host/win/launch_process_with_token.h
+++ b/remoting/host/win/launch_process_with_token.h
@@ -23,7 +23,9 @@ bool CreateSessionToken(uint32 session_id, base::win::ScopedHandle* token_out);
bool LaunchProcessWithToken(const FilePath& binary,
const CommandLine::StringType& command_line,
HANDLE user_token,
- base::win::ScopedHandle* process_out);
+ DWORD creation_flags,
+ base::win::ScopedHandle* process_out,
+ base::win::ScopedHandle* thread_out);
} // namespace remoting
diff --git a/remoting/host/win/wts_session_process_launcher.cc b/remoting/host/win/wts_session_process_launcher.cc
index 42cb702..bec5db1 100644
--- a/remoting/host/win/wts_session_process_launcher.cc
+++ b/remoting/host/win/wts_session_process_launcher.cc
@@ -47,9 +47,14 @@ const int kMinLaunchDelaySeconds = 1;
const FilePath::CharType kMe2meHostBinaryName[] =
FILE_PATH_LITERAL("remoting_me2me_host.exe");
+const FilePath::CharType kMe2meServiceBinaryName[] =
+ FILE_PATH_LITERAL("remoting_service.exe");
+
// The IPC channel name is passed to the host in the command line.
const char kChromotingIpcSwitchName[] = "chromoting-ipc";
+const char kElevateSwitchName[] = "elevate";
+
// The command line parameters that should be copied from the service's command
// line to the host process.
const char* kCopiedSwitchNames[] = {
@@ -72,87 +77,57 @@ WtsSessionProcessLauncher::WtsSessionProcessLauncher(
attached_(false),
main_message_loop_(main_message_loop),
ipc_message_loop_(ipc_message_loop),
- monitor_(monitor) {
+ monitor_(monitor),
+ job_state_(kJobUninitialized) {
monitor_->AddWtsConsoleObserver(this);
-}
-WtsSessionProcessLauncher::~WtsSessionProcessLauncher() {
- monitor_->RemoveWtsConsoleObserver(this);
-
- DCHECK(!attached_);
- DCHECK(!timer_.IsRunning());
-}
-
-void WtsSessionProcessLauncher::LaunchProcess() {
- DCHECK(main_message_loop_->BelongsToCurrentThread());
- DCHECK(attached_);
- DCHECK(launcher_.get() == NULL);
- DCHECK(!timer_.IsRunning());
- DCHECK(!worker_process_.IsValid());
+ process_exit_event_.Set(CreateEvent(NULL, TRUE, FALSE, NULL));
+ CHECK(process_exit_event_.IsValid());
- launch_time_ = base::Time::Now();
- launcher_.reset(new WorkerProcessLauncher(
- this,
- base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped,
- base::Unretained(this)),
- main_message_loop_,
- ipc_message_loop_));
- launcher_->Start(kChromotingChannelSecurityDescriptor);
+ // To receive job object notifications it is registered with the completion
+ // port represented by |ipc_message_loop|. The registration has to be done on
+ // the I/O thread because MessageLoopForIO::RegisterJobObject() can only be
+ // called via MessageLoopForIO::current().
+ ipc_message_loop_->PostTask(FROM_HERE, base::Bind(
+ &WtsSessionProcessLauncher::InitializeJob,
+ base::Unretained(this)));
}
-void WtsSessionProcessLauncher::OnLauncherStopped() {
- DCHECK(main_message_loop_->BelongsToCurrentThread());
-
- DWORD exit_code;
- if (!::GetExitCodeProcess(worker_process_, &exit_code)) {
- LOG_GETLASTERROR(INFO)
- << "Failed to query the exit code of the worker process";
- exit_code = CONTROL_C_EXIT;
- }
-
- launcher_.reset(NULL);
- worker_process_.Close();
-
- // Do not relaunch the worker process if the caller has asked us to stop.
- if (stoppable_state() != Stoppable::kRunning) {
- CompleteStopping();
- return;
- }
+WtsSessionProcessLauncher::~WtsSessionProcessLauncher() {
+ // Make sure that the object is completely stopped. The same check exists
+ // in Stoppable::~Stoppable() but this one allows us to examine the state of
+ // the object before destruction.
+ CHECK_EQ(stoppable_state(), Stoppable::kStopped);
- // Stop trying to restart the worker process if its process exited due to
- // misconfiguration.
- if (kMinPermanentErrorExitCode <= exit_code &&
- exit_code <= kMaxPermanentErrorExitCode) {
- Stop();
- return;
- }
+ monitor_->RemoveWtsConsoleObserver(this);
- // Try to restart the worker process if we are still attached to a session.
- if (attached_) {
- // Expand the backoff interval if the process has died quickly or reset it
- // if it was up longer than the maximum backoff delay.
- base::TimeDelta delta = base::Time::Now() - launch_time_;
- if (delta < base::TimeDelta() ||
- delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) {
- launch_backoff_ = base::TimeDelta();
- } else {
- launch_backoff_ = std::max(
- launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds));
- launch_backoff_ = std::min(
- launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds));
- }
+ CHECK(!attached_);
+ CHECK(!timer_.IsRunning());
+}
- // Try to launch the worker process.
- timer_.Start(FROM_HERE, launch_backoff_,
- this, &WtsSessionProcessLauncher::LaunchProcess);
- }
+void WtsSessionProcessLauncher::OnIOCompleted(
+ base::MessagePumpForIO::IOContext* context,
+ DWORD bytes_transferred,
+ DWORD error) {
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread());
+
+ // |bytes_transferred| is used in job object notifications to supply
+ // the message ID; |context| carries process ID.
+ main_message_loop_->PostTask(FROM_HERE, base::Bind(
+ &WtsSessionProcessLauncher::OnJobNotification,
+ base::Unretained(this), bytes_transferred,
+ reinterpret_cast<DWORD>(context)));
}
bool WtsSessionProcessLauncher::DoLaunchProcess(
const std::string& channel_name,
ScopedHandle* process_exit_event_out) {
DCHECK(main_message_loop_->BelongsToCurrentThread());
- DCHECK(!worker_process_.IsValid());
+
+ // The job object is not ready. Retry starting the host process later.
+ if (!job_.IsValid()) {
+ return false;
+ }
// Construct the host binary name.
FilePath dir_path;
@@ -161,28 +136,48 @@ bool WtsSessionProcessLauncher::DoLaunchProcess(
return false;
}
FilePath host_binary = dir_path.Append(kMe2meHostBinaryName);
+ FilePath service_binary = dir_path.Append(kMe2meServiceBinaryName);
// Create the host process command line passing the name of the IPC channel
// to use and copying known switches from the service's command line.
- CommandLine command_line(host_binary);
+ CommandLine command_line(service_binary);
+ command_line.AppendSwitchPath(kElevateSwitchName, host_binary);
command_line.AppendSwitchNative(kChromotingIpcSwitchName,
UTF8ToWide(channel_name));
command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
kCopiedSwitchNames,
_countof(kCopiedSwitchNames));
+ CHECK(ResetEvent(process_exit_event_));
+
// Try to launch the process and attach an object watcher to the returned
// handle so that we get notified when the process terminates.
- if (!LaunchProcessWithToken(host_binary,
+ base::win::ScopedHandle worker_process;
+ base::win::ScopedHandle worker_thread;
+ if (!LaunchProcessWithToken(service_binary,
command_line.GetCommandLineString(),
session_token_,
- &worker_process_)) {
+ CREATE_SUSPENDED,
+ &worker_process,
+ &worker_thread)) {
+ return false;
+ }
+
+ if (!AssignProcessToJobObject(job_, worker_process)) {
+ LOG_GETLASTERROR(ERROR) << "Failed to assign the worker to the job object";
+ TerminateProcess(worker_process, CONTROL_C_EXIT);
+ return false;
+ }
+
+ if (!ResumeThread(worker_thread)) {
+ LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread";
+ DoKillProcess(CONTROL_C_EXIT);
return false;
}
ScopedHandle process_exit_event;
if (!DuplicateHandle(GetCurrentProcess(),
- worker_process_,
+ process_exit_event_,
GetCurrentProcess(),
process_exit_event.Receive(),
SYNCHRONIZE,
@@ -200,8 +195,8 @@ bool WtsSessionProcessLauncher::DoLaunchProcess(
void WtsSessionProcessLauncher::DoKillProcess(DWORD exit_code) {
DCHECK(main_message_loop_->BelongsToCurrentThread());
- if (worker_process_.IsValid()) {
- TerminateProcess(worker_process_, exit_code);
+ if (job_.IsValid()) {
+ TerminateJobObject(job_, exit_code);
}
}
@@ -221,20 +216,6 @@ bool WtsSessionProcessLauncher::OnMessageReceived(const IPC::Message& message) {
return handled;
}
-void WtsSessionProcessLauncher::OnSendSasToConsole() {
- DCHECK(main_message_loop_->BelongsToCurrentThread());
-
- if (attached_) {
- if (sas_injector_.get() == NULL) {
- sas_injector_ = SasInjector::Create();
- }
-
- if (sas_injector_.get() != NULL) {
- sas_injector_->InjectSas();
- }
- }
-}
-
void WtsSessionProcessLauncher::OnSessionAttached(uint32 session_id) {
DCHECK(main_message_loop_->BelongsToCurrentThread());
@@ -276,8 +257,201 @@ void WtsSessionProcessLauncher::DoStop() {
OnSessionDetached();
}
- if (launcher_.get() == NULL) {
- CompleteStopping();
+ job_.Close();
+
+ // Drain the completion queue to make sure all job object notification have
+ // been received.
+ if (job_state_ == kJobRunning) {
+ job_state_ = kJobStopping;
+ ipc_message_loop_->PostTask(FROM_HERE, base::Bind(
+ &WtsSessionProcessLauncher::DrainJobNotifications,
+ base::Unretained(this)));
+ }
+
+ // Don't complete shutdown if |launcher_| is not completely stopped.
+ if (launcher_.get() != NULL) {
+ return;
+ }
+
+ // Don't complete shutdown if the completion queue hasn't been drained.
+ if (job_state_ != kJobUninitialized && job_state_ != kJobStopped) {
+ return;
+ }
+
+ CompleteStopping();
+}
+
+void WtsSessionProcessLauncher::DrainJobNotifications() {
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread());
+
+ // DrainJobNotifications() is posted after the job object is destroyed, so
+ // by this time all notifications from the job object have been processed
+ // already. Let the main thread know that the queue has been drained.
+ main_message_loop_->PostTask(FROM_HERE, base::Bind(
+ &WtsSessionProcessLauncher::DrainJobNotificationsCompleted,
+ base::Unretained(this)));
+}
+
+void WtsSessionProcessLauncher::DrainJobNotificationsCompleted() {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+ DCHECK_EQ(job_state_, kJobStopping);
+
+ job_state_ = kJobStopped;
+ Stop();
+}
+
+void WtsSessionProcessLauncher::InitializeJob() {
+ DCHECK(ipc_message_loop_->BelongsToCurrentThread());
+
+ ScopedHandle job;
+ job.Set(CreateJobObject(NULL, NULL));
+ if (!job.IsValid()) {
+ LOG_GETLASTERROR(ERROR) << "Failed to create a job object";
+ return;
+ }
+
+ // Limit the number of active processes in the job to two (the process
+ // performing elevation and the host) and make sure that all processes will be
+ // killed once the job object is destroyed.
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
+ memset(&info, 0, sizeof(info));
+ info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_ACTIVE_PROCESS |
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ info.BasicLimitInformation.ActiveProcessLimit = 2;
+ if (!SetInformationJobObject(job,
+ JobObjectExtendedLimitInformation,
+ &info,
+ sizeof(info))) {
+ LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object";
+ return;
+ }
+
+ // Register the job object with the completion port in the I/O thread to
+ // receive job notifications.
+ if (!MessageLoopForIO::current()->RegisterJobObject(job, this)) {
+ LOG_GETLASTERROR(ERROR)
+ << "Failed to associate the job object with a completion port";
+ return;
+ }
+
+ // ScopedHandle is not compatible with base::Passed, so we wrap it to a scoped
+ // pointer.
+ scoped_ptr<ScopedHandle> job_wrapper(new ScopedHandle());
+ *job_wrapper = job.Pass();
+
+ // Let the main thread know that initialization is complete.
+ main_message_loop_->PostTask(FROM_HERE, base::Bind(
+ &WtsSessionProcessLauncher::InitializeJobCompleted,
+ base::Unretained(this), base::Passed(&job_wrapper)));
+}
+
+void WtsSessionProcessLauncher::InitializeJobCompleted(
+ scoped_ptr<ScopedHandle> job) {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+ DCHECK(!job_.IsValid());
+ DCHECK_EQ(job_state_, kJobUninitialized);
+
+ if (stoppable_state() == Stoppable::kRunning) {
+ job_ = job->Pass();
+ job_state_ = kJobRunning;
+ }
+}
+
+void WtsSessionProcessLauncher::OnJobNotification(DWORD message, DWORD pid) {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+ switch (message) {
+ case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
+ CHECK(SetEvent(process_exit_event_));
+ break;
+
+ case JOB_OBJECT_MSG_NEW_PROCESS:
+ // We report the exit code of the worker process to be |CONTROL_C_EXIT|
+ // if we cannot get the actual exit code. So here we can safely ignore
+ // the error returned by OpenProcess().
+ worker_process_.Set(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid));
+ break;
+ }
+}
+
+void WtsSessionProcessLauncher::LaunchProcess() {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+ DCHECK(attached_);
+ DCHECK(launcher_.get() == NULL);
+ DCHECK(!timer_.IsRunning());
+
+ launch_time_ = base::Time::Now();
+ launcher_.reset(new WorkerProcessLauncher(
+ this,
+ base::Bind(&WtsSessionProcessLauncher::OnLauncherStopped,
+ base::Unretained(this)),
+ main_message_loop_,
+ ipc_message_loop_));
+ launcher_->Start(kChromotingChannelSecurityDescriptor);
+}
+
+void WtsSessionProcessLauncher::OnLauncherStopped() {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+ DWORD exit_code = CONTROL_C_EXIT;
+ if (worker_process_.IsValid()) {
+ if (!::GetExitCodeProcess(worker_process_, &exit_code)) {
+ LOG_GETLASTERROR(INFO)
+ << "Failed to query the exit code of the worker process";
+ exit_code = CONTROL_C_EXIT;
+ }
+
+ worker_process_.Close();
+ }
+
+ launcher_.reset(NULL);
+
+ // Do not relaunch the worker process if the caller has asked us to stop.
+ if (stoppable_state() != Stoppable::kRunning) {
+ Stop();
+ return;
+ }
+
+ // Stop trying to restart the worker process if its process exited due to
+ // misconfiguration.
+ if (kMinPermanentErrorExitCode <= exit_code &&
+ exit_code <= kMaxPermanentErrorExitCode) {
+ Stop();
+ return;
+ }
+
+ // Try to restart the worker process if we are still attached to a session.
+ if (attached_) {
+ // Expand the backoff interval if the process has died quickly or reset it
+ // if it was up longer than the maximum backoff delay.
+ base::TimeDelta delta = base::Time::Now() - launch_time_;
+ if (delta < base::TimeDelta() ||
+ delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) {
+ launch_backoff_ = base::TimeDelta();
+ } else {
+ launch_backoff_ = std::max(
+ launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds));
+ launch_backoff_ = std::min(
+ launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds));
+ }
+
+ // Try to launch the worker process.
+ timer_.Start(FROM_HERE, launch_backoff_,
+ this, &WtsSessionProcessLauncher::LaunchProcess);
+ }
+}
+
+void WtsSessionProcessLauncher::OnSendSasToConsole() {
+ DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+ if (attached_) {
+ if (sas_injector_.get() == NULL) {
+ sas_injector_ = SasInjector::Create();
+ }
+
+ if (sas_injector_.get() != NULL) {
+ sas_injector_->InjectSas();
+ }
}
}
diff --git a/remoting/host/win/wts_session_process_launcher.h b/remoting/host/win/wts_session_process_launcher.h
index 9a91903..ce1a2f9 100644
--- a/remoting/host/win/wts_session_process_launcher.h
+++ b/remoting/host/win/wts_session_process_launcher.h
@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
#include "base/process.h"
#include "base/time.h"
#include "base/timer.h"
@@ -35,7 +36,8 @@ class SasInjector;
class WtsConsoleMonitor;
class WtsSessionProcessLauncher
- : public Stoppable,
+ : public base::MessagePumpForIO::IOHandler,
+ public Stoppable,
public WorkerProcessLauncher::Delegate,
public WtsConsoleObserver {
public:
@@ -51,6 +53,11 @@ class WtsSessionProcessLauncher
virtual ~WtsSessionProcessLauncher();
+ // base::MessagePumpForIO::IOHandler implementation.
+ virtual void OnIOCompleted(base::MessagePumpForIO::IOContext* context,
+ DWORD bytes_transferred,
+ DWORD error) OVERRIDE;
+
// WorkerProcessLauncher::Delegate implementation.
virtual bool DoLaunchProcess(
const std::string& channel_name,
@@ -68,6 +75,22 @@ class WtsSessionProcessLauncher
virtual void DoStop() OVERRIDE;
private:
+ // Drains the completion port queue to make sure that all job object
+ // notifications has been received.
+ void DrainJobNotifications();
+
+ // Notifies that the completion port queue has been drained.
+ void DrainJobNotificationsCompleted();
+
+ // Creates and initializes the job object that will sandbox the launched child
+ // processes.
+ void InitializeJob();
+
+ // Notifies that the job object initialization is complete.
+ void InitializeJobCompleted(scoped_ptr<base::win::ScopedHandle> job);
+
+ void OnJobNotification(DWORD message, DWORD pid);
+
// Attempts to launch the host process in the current console session.
// Schedules next launch attempt if creation of the process fails for any
// reason.
@@ -103,6 +126,22 @@ class WtsSessionProcessLauncher
scoped_ptr<WorkerProcessLauncher> launcher_;
+ // The job object used to control the lifetime of child processes.
+ base::win::ScopedHandle job_;
+
+ // A waiting handle that becomes signalled once all process associated with
+ // the job have been terminated.
+ base::win::ScopedHandle process_exit_event_;
+
+ enum JobState {
+ kJobUninitialized,
+ kJobRunning,
+ kJobStopping,
+ kJobStopped
+ };
+
+ JobState job_state_;
+
base::win::ScopedHandle worker_process_;
// The token to be used to launch a process in a different session.
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index a06ce228..8b0fbd4 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -632,6 +632,8 @@
'AdditionalDependencies': [
'wtsapi32.lib',
],
+ # 2 == /SUBSYSTEM:WINDOWS
+ 'SubSystem': '2',
},
},
}, # end of target 'remoting_service'
@@ -1518,6 +1520,7 @@
'msvs_settings': {
'VCLinkerTool': {
'AdditionalOptions': [
+ "\"/MANIFESTUAC:level='requireAdministrator' uiAccess='true'\"",
"\"/manifestdependency:type='win32' "
"name='Microsoft.Windows.Common-Controls' "
"version='6.0.0.0' "