Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/host/daemon_process.h" | 5 #include "remoting/host/daemon_process.h" |
| 6 | 6 |
| 7 #include "base/base_switches.h" | 7 #include "base/base_switches.h" |
| 8 #include "base/bind.h" | 8 #include "base/bind.h" |
| 9 #include "base/bind_helpers.h" | 9 #include "base/bind_helpers.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/path_service.h" | 11 #include "base/path_service.h" |
| 12 #include "base/single_thread_task_runner.h" | 12 #include "base/single_thread_task_runner.h" |
| 13 #include "base/time.h" | 13 #include "base/time.h" |
| 14 #include "base/timer.h" | 14 #include "base/timer.h" |
| 15 #include "base/utf_string_conversions.h" | 15 #include "base/utf_string_conversions.h" |
| 16 #include "base/win/scoped_handle.h" | 16 #include "base/win/scoped_handle.h" |
| 17 #include "remoting/host/host_exit_codes.h" | 17 #include "remoting/host/host_exit_codes.h" |
| 18 #include "remoting/host/win/launch_process_with_token.h" | 18 #include "remoting/host/win/launch_process_with_token.h" |
| 19 #include "remoting/host/win/unprivileged_process_delegate.h" | |
| 19 #include "remoting/host/win/worker_process_launcher.h" | 20 #include "remoting/host/win/worker_process_launcher.h" |
| 20 | 21 |
| 21 using base::win::ScopedHandle; | 22 using base::win::ScopedHandle; |
| 22 using base::TimeDelta; | 23 using base::TimeDelta; |
| 23 | 24 |
| 24 namespace { | 25 namespace { |
| 25 | 26 |
| 26 // The minimum and maximum delays between attempts to launch the networking | |
| 27 // process. | |
| 28 const int kMaxLaunchDelaySeconds = 60; | |
| 29 const int kMinLaunchDelaySeconds = 1; | |
| 30 | |
| 31 const FilePath::CharType kMe2meHostBinaryName[] = | 27 const FilePath::CharType kMe2meHostBinaryName[] = |
| 32 FILE_PATH_LITERAL("remoting_host.exe"); | 28 FILE_PATH_LITERAL("remoting_host.exe"); |
| 33 | 29 |
| 34 // The IPC channel name is passed to the networking process in the command line. | |
| 35 const char kDaemonPipeSwitchName[] = "daemon-pipe"; | |
| 36 | |
| 37 // The command line parameters that should be copied from the service's command | |
| 38 // line to the network process. | |
| 39 const char* kCopiedSwitchNames[] = { | |
| 40 "host-config", switches::kV, switches::kVModule }; | |
| 41 | |
| 42 // The security descriptor of the daemon IPC endpoint. It gives full access | 30 // The security descriptor of the daemon IPC endpoint. It gives full access |
| 43 // to LocalSystem and denies access by anyone else. | 31 // to LocalSystem and denies access by anyone else. |
| 44 const char kDaemonPipeSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; | 32 const char kDaemonPipeSecurityDescriptor[] = "O:SYG:SYD:(A;;GA;;;SY)"; |
| 45 | 33 |
| 46 } // namespace | 34 } // namespace |
| 47 | 35 |
| 48 namespace remoting { | 36 namespace remoting { |
| 49 | 37 |
| 50 class DaemonProcessWin : public DaemonProcess, | 38 class DaemonProcessWin : public DaemonProcess { |
| 51 public WorkerProcessLauncher::Delegate { | |
| 52 public: | 39 public: |
| 53 DaemonProcessWin(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | 40 DaemonProcessWin(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| 54 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, | 41 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| 55 const base::Closure& stopped_callback); | 42 const base::Closure& stopped_callback); |
| 56 virtual ~DaemonProcessWin(); | 43 virtual ~DaemonProcessWin(); |
| 57 | 44 |
| 58 virtual void OnChannelConnected() OVERRIDE; | 45 virtual void OnChannelConnected() OVERRIDE; |
| 59 | 46 |
| 60 // Sends an IPC message to the worker process. This method can be called only | 47 // Sends an IPC message to the worker process. This method can be called only |
| 61 // after successful Start() and until Stop() is called or an error occurred. | 48 // after successful Start() and until Stop() is called or an error occurred. |
| 62 virtual void Send(IPC::Message* message) OVERRIDE; | 49 virtual void Send(IPC::Message* message) OVERRIDE; |
| 63 | 50 |
| 64 // WorkerProcessLauncher::Delegate implementation. | |
| 65 virtual bool DoLaunchProcess( | |
| 66 const std::string& channel_name, | |
| 67 ScopedHandle* process_exit_event_out) OVERRIDE; | |
| 68 virtual void DoKillProcess(DWORD exit_code) OVERRIDE; | |
| 69 | |
| 70 protected: | 51 protected: |
| 71 // Stoppable implementation. | 52 // Stoppable implementation. |
| 72 virtual void DoStop() OVERRIDE; | 53 virtual void DoStop() OVERRIDE; |
| 73 | 54 |
| 74 // DaemonProcess implementation. | 55 // DaemonProcess implementation. |
| 75 virtual void LaunchNetworkProcess() OVERRIDE; | 56 virtual void LaunchNetworkProcess() OVERRIDE; |
| 76 | 57 |
| 77 private: | 58 private: |
| 78 // Called when the launcher reports the worker process has stopped. | 59 // True if the network process has connected to the daemon at least once. |
|
Wez
2012/10/09 03:40:39
You clear this flag in DoStop, though, so that's n
alexeypa (please no reviews)
2012/10/09 19:42:04
Removed |connected_|. This is not much value in it
| |
| 79 void OnLauncherStopped(); | |
| 80 | |
| 81 // True if the network process is connected to the daemon. | |
| 82 bool connected_; | 60 bool connected_; |
| 83 | 61 |
| 84 // Time of the last launch attempt. | 62 scoped_refptr<WorkerProcessLauncher> launcher_; |
| 85 base::Time launch_time_; | |
| 86 | |
| 87 // Current backoff delay. | |
| 88 base::TimeDelta launch_backoff_; | |
| 89 | |
| 90 // Timer used to schedule the next attempt to launch the process. | |
| 91 base::OneShotTimer<DaemonProcessWin> timer_; | |
| 92 | |
| 93 scoped_ptr<WorkerProcessLauncher> launcher_; | |
| 94 | |
| 95 ScopedHandle network_process_; | |
| 96 | 63 |
| 97 DISALLOW_COPY_AND_ASSIGN(DaemonProcessWin); | 64 DISALLOW_COPY_AND_ASSIGN(DaemonProcessWin); |
| 98 }; | 65 }; |
| 99 | 66 |
| 100 DaemonProcessWin::DaemonProcessWin( | 67 DaemonProcessWin::DaemonProcessWin( |
| 101 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | 68 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| 102 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, | 69 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| 103 const base::Closure& stopped_callback) | 70 const base::Closure& stopped_callback) |
| 104 : DaemonProcess(main_task_runner, io_task_runner, stopped_callback), | 71 : DaemonProcess(main_task_runner, io_task_runner, stopped_callback), |
| 105 connected_(false) { | 72 connected_(false) { |
| 106 } | 73 } |
| 107 | 74 |
| 108 DaemonProcessWin::~DaemonProcessWin() { | 75 DaemonProcessWin::~DaemonProcessWin() { |
| 109 // Make sure that the object is completely stopped. The same check exists | 76 // Make sure that the object is completely stopped. The same check exists |
| 110 // in Stoppable::~Stoppable() but this one helps us to fail early and | 77 // in Stoppable::~Stoppable() but this one helps us to fail early and |
| 111 // predictably. | 78 // predictably. |
| 112 CHECK_EQ(stoppable_state(), Stoppable::kStopped); | 79 CHECK_EQ(stoppable_state(), Stoppable::kStopped); |
| 113 } | 80 } |
| 114 | 81 |
| 115 void DaemonProcessWin::LaunchNetworkProcess() { | 82 void DaemonProcessWin::LaunchNetworkProcess() { |
| 116 DCHECK(main_task_runner()->BelongsToCurrentThread()); | 83 DCHECK(main_task_runner()->BelongsToCurrentThread()); |
| 117 DCHECK(launcher_.get() == NULL); | 84 DCHECK(launcher_.get() == NULL); |
| 118 DCHECK(!network_process_.IsValid()); | |
| 119 DCHECK(!timer_.IsRunning()); | |
| 120 | 85 |
| 121 launch_time_ = base::Time::Now(); | 86 // Construct the host binary name. |
| 122 launcher_.reset(new WorkerProcessLauncher( | 87 FilePath dir_path; |
| 123 this, this, | 88 if (!PathService::Get(base::DIR_EXE, &dir_path)) { |
| 124 base::Bind(&DaemonProcessWin::OnLauncherStopped, base::Unretained(this)), | 89 LOG(ERROR) << "Failed to get the executable file name."; |
| 125 main_task_runner(), | 90 Stop(); |
| 126 io_task_runner())); | 91 return; |
| 127 launcher_->Start(kDaemonPipeSecurityDescriptor); | 92 } |
| 93 | |
| 94 scoped_ptr<UnprivilegedProcessDelegate> delegate( | |
| 95 new UnprivilegedProcessDelegate(main_task_runner(), io_task_runner(), | |
| 96 dir_path.Append(kMe2meHostBinaryName))); | |
| 97 launcher_ = new WorkerProcessLauncher(main_task_runner(), | |
| 98 io_task_runner(), | |
| 99 delegate.Pass(), | |
| 100 this, | |
| 101 kDaemonPipeSecurityDescriptor); | |
| 102 launcher_->Start(); | |
| 128 } | 103 } |
| 129 | 104 |
| 130 void DaemonProcessWin::OnChannelConnected() { | 105 void DaemonProcessWin::OnChannelConnected() { |
| 131 connected_ = true; | 106 connected_ = true; |
| 132 DaemonProcess::OnChannelConnected(); | 107 DaemonProcess::OnChannelConnected(); |
| 133 } | 108 } |
| 134 | 109 |
| 135 void DaemonProcessWin::Send(IPC::Message* message) { | 110 void DaemonProcessWin::Send(IPC::Message* message) { |
| 136 if (connected_) { | 111 if (connected_) { |
| 137 launcher_->Send(message); | 112 launcher_->Send(message); |
| 138 } else { | 113 } else { |
| 139 delete message; | 114 delete message; |
| 140 } | 115 } |
| 141 } | 116 } |
| 142 | 117 |
| 143 bool DaemonProcessWin::DoLaunchProcess( | |
| 144 const std::string& channel_name, | |
| 145 ScopedHandle* process_exit_event_out) { | |
| 146 DCHECK(main_task_runner()->BelongsToCurrentThread()); | |
| 147 DCHECK(!network_process_.IsValid()); | |
| 148 | |
| 149 // Construct the host binary name. | |
| 150 FilePath dir_path; | |
| 151 if (!PathService::Get(base::DIR_EXE, &dir_path)) { | |
| 152 LOG(ERROR) << "Failed to get the executable file name."; | |
| 153 return false; | |
| 154 } | |
| 155 FilePath host_binary = dir_path.Append(kMe2meHostBinaryName); | |
| 156 | |
| 157 // Create the host process command line, passing the name of the IPC channel | |
| 158 // to use and copying known switches from the service's command line. | |
| 159 CommandLine command_line(host_binary); | |
| 160 command_line.AppendSwitchNative(kDaemonPipeSwitchName, | |
| 161 UTF8ToWide(channel_name)); | |
| 162 command_line.CopySwitchesFrom(*CommandLine::ForCurrentProcess(), | |
| 163 kCopiedSwitchNames, | |
| 164 _countof(kCopiedSwitchNames)); | |
| 165 | |
| 166 ScopedHandle token; | |
| 167 if (!OpenProcessToken(GetCurrentProcess(), | |
| 168 MAXIMUM_ALLOWED, | |
| 169 token.Receive())) { | |
| 170 LOG_GETLASTERROR(FATAL) << "Failed to open process token"; | |
| 171 return false; | |
| 172 } | |
| 173 | |
| 174 // Try to launch the process and attach an object watcher to the returned | |
| 175 // handle so that we get notified when the process terminates. | |
| 176 // TODO(alexeypa): Pass a restricted process token. | |
| 177 // See http://crbug.com/134694. | |
| 178 ScopedHandle worker_thread; | |
| 179 if (!LaunchProcessWithToken(host_binary, | |
| 180 command_line.GetCommandLineString(), | |
| 181 token, | |
| 182 0, | |
| 183 &network_process_, | |
| 184 &worker_thread)) { | |
| 185 return false; | |
| 186 } | |
| 187 | |
| 188 ScopedHandle process_exit_event; | |
| 189 if (!DuplicateHandle(GetCurrentProcess(), | |
| 190 network_process_, | |
| 191 GetCurrentProcess(), | |
| 192 process_exit_event.Receive(), | |
| 193 SYNCHRONIZE, | |
| 194 FALSE, | |
| 195 0)) { | |
| 196 LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle"; | |
| 197 DoKillProcess(CONTROL_C_EXIT); | |
| 198 return false; | |
| 199 } | |
| 200 | |
| 201 *process_exit_event_out = process_exit_event.Pass(); | |
| 202 return true; | |
| 203 } | |
| 204 | |
| 205 void DaemonProcessWin::DoKillProcess(DWORD exit_code) { | |
| 206 DCHECK(main_task_runner()->BelongsToCurrentThread()); | |
| 207 CHECK(network_process_.IsValid()); | |
| 208 | |
| 209 TerminateProcess(network_process_, exit_code); | |
| 210 } | |
| 211 | |
| 212 void DaemonProcessWin::DoStop() { | 118 void DaemonProcessWin::DoStop() { |
| 213 DCHECK(main_task_runner()->BelongsToCurrentThread()); | 119 DCHECK(main_task_runner()->BelongsToCurrentThread()); |
| 214 | 120 |
| 215 timer_.Stop(); | 121 connected_ = false; |
| 216 | |
| 217 if (launcher_.get() != NULL) { | 122 if (launcher_.get() != NULL) { |
| 218 launcher_->Stop(); | 123 launcher_->Stop(); |
| 219 } | 124 launcher_ = NULL; |
| 220 | |
| 221 // Early exit if we're still waiting for |launcher_| to stop. | |
| 222 if (launcher_.get() != NULL) { | |
| 223 return; | |
| 224 } | 125 } |
| 225 | 126 |
| 226 DaemonProcess::DoStop(); | 127 DaemonProcess::DoStop(); |
| 227 } | 128 } |
| 228 | 129 |
| 229 void DaemonProcessWin::OnLauncherStopped() { | |
| 230 DCHECK(main_task_runner()->BelongsToCurrentThread()); | |
| 231 CHECK(network_process_.IsValid()); | |
| 232 | |
| 233 DWORD exit_code = CONTROL_C_EXIT; | |
| 234 if (!::GetExitCodeProcess(network_process_, &exit_code)) { | |
| 235 LOG_GETLASTERROR(INFO) | |
| 236 << "Failed to query the exit code of the worker process"; | |
| 237 exit_code = CONTROL_C_EXIT; | |
| 238 } | |
| 239 | |
| 240 network_process_.Close(); | |
| 241 connected_ = false; | |
| 242 launcher_.reset(NULL); | |
| 243 | |
| 244 // Do not relaunch the network process if the caller has asked us to stop. | |
| 245 if (stoppable_state() != Stoppable::kRunning) { | |
| 246 Stop(); | |
| 247 return; | |
| 248 } | |
| 249 | |
| 250 // Stop trying to restart the worker process if its process exited due to | |
| 251 // misconfiguration. | |
| 252 if (kMinPermanentErrorExitCode <= exit_code && | |
| 253 exit_code <= kMaxPermanentErrorExitCode) { | |
| 254 Stop(); | |
| 255 return; | |
| 256 } | |
| 257 | |
| 258 // Expand the backoff interval if the process has died quickly or reset it | |
| 259 // if it was up longer than the maximum backoff delay. | |
| 260 base::TimeDelta delta = base::Time::Now() - launch_time_; | |
| 261 if (delta < base::TimeDelta() || | |
| 262 delta >= base::TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)) { | |
| 263 launch_backoff_ = base::TimeDelta(); | |
| 264 } else { | |
| 265 launch_backoff_ = std::max( | |
| 266 launch_backoff_ * 2, TimeDelta::FromSeconds(kMinLaunchDelaySeconds)); | |
| 267 launch_backoff_ = std::min( | |
| 268 launch_backoff_, TimeDelta::FromSeconds(kMaxLaunchDelaySeconds)); | |
| 269 } | |
| 270 | |
| 271 // Try to launch the worker process. | |
| 272 timer_.Start(FROM_HERE, launch_backoff_, | |
| 273 this, &DaemonProcessWin::LaunchNetworkProcess); | |
| 274 } | |
| 275 | |
| 276 scoped_ptr<DaemonProcess> DaemonProcess::Create( | 130 scoped_ptr<DaemonProcess> DaemonProcess::Create( |
| 277 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | 131 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| 278 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, | 132 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, |
| 279 const base::Closure& stopped_callback) { | 133 const base::Closure& stopped_callback) { |
| 280 scoped_ptr<DaemonProcessWin> daemon_process( | 134 scoped_ptr<DaemonProcessWin> daemon_process( |
| 281 new DaemonProcessWin(main_task_runner, io_task_runner, stopped_callback)); | 135 new DaemonProcessWin(main_task_runner, io_task_runner, stopped_callback)); |
| 282 return daemon_process.PassAs<DaemonProcess>(); | 136 return daemon_process.PassAs<DaemonProcess>(); |
| 283 } | 137 } |
| 284 | 138 |
| 285 } // namespace remoting | 139 } // namespace remoting |
| OLD | NEW |