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
3// found in the LICENSE file.
4
5#include "chrome/browser/service/service_process_control.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/command_line.h"
10#include "base/files/file_path.h"
11#include "base/process/launch.h"
12#include "base/stl_util.h"
13#include "base/threading/thread.h"
14#include "base/threading/thread_restrictions.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/upgrade_detector.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/common/service_messages.h"
20#include "chrome/common/service_process_util.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/common/child_process_host.h"
24#include "google_apis/gaia/gaia_switches.h"
25#include "ui/base/ui_base_switches.h"
26
27using content::BrowserThread;
28using content::ChildProcessHost;
29
30// ServiceProcessControl implementation.
31ServiceProcessControl::ServiceProcessControl() {
32}
33
34ServiceProcessControl::~ServiceProcessControl() {
35}
36
37void ServiceProcessControl::ConnectInternal() {
38  // If the channel has already been established then we run the task
39  // and return.
40  if (channel_.get()) {
41    RunConnectDoneTasks();
42    return;
43  }
44
45  // Actually going to connect.
46  VLOG(1) << "Connecting to Service Process IPC Server";
47
48  // TODO(hclam): Handle error connecting to channel.
49  const IPC::ChannelHandle channel_id = GetServiceProcessChannel();
50  SetChannel(new IPC::ChannelProxy(
51      channel_id,
52      IPC::Channel::MODE_NAMED_CLIENT,
53      this,
54      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO).get()));
55}
56
57void ServiceProcessControl::SetChannel(IPC::ChannelProxy* channel) {
58  channel_.reset(channel);
59}
60
61void ServiceProcessControl::RunConnectDoneTasks() {
62  // The tasks executed here may add more tasks to the vector. So copy
63  // them to the stack before executing them. This way recursion is
64  // avoided.
65  TaskList tasks;
66
67  if (IsConnected()) {
68    tasks.swap(connect_success_tasks_);
69    RunAllTasksHelper(&tasks);
70    DCHECK(tasks.empty());
71    connect_failure_tasks_.clear();
72  } else {
73    tasks.swap(connect_failure_tasks_);
74    RunAllTasksHelper(&tasks);
75    DCHECK(tasks.empty());
76    connect_success_tasks_.clear();
77  }
78}
79
80// static
81void ServiceProcessControl::RunAllTasksHelper(TaskList* task_list) {
82  TaskList::iterator index = task_list->begin();
83  while (index != task_list->end()) {
84    (*index).Run();
85    index = task_list->erase(index);
86  }
87}
88
89bool ServiceProcessControl::IsConnected() const {
90  return channel_ != NULL;
91}
92
93void ServiceProcessControl::Launch(const base::Closure& success_task,
94                                   const base::Closure& failure_task) {
95  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
96
97  base::Closure failure = failure_task;
98  if (!success_task.is_null())
99    connect_success_tasks_.push_back(success_task);
100
101  if (!failure.is_null())
102    connect_failure_tasks_.push_back(failure);
103
104  // If we already in the process of launching, then we are done.
105  if (launcher_.get())
106    return;
107
108  // If the service process is already running then connects to it.
109  if (CheckServiceProcessReady()) {
110    ConnectInternal();
111    return;
112  }
113
114  // A service process should have a different mechanism for starting, but now
115  // we start it as if it is a child process.
116
117#if defined(OS_LINUX)
118  int flags = ChildProcessHost::CHILD_ALLOW_SELF;
119#else
120  int flags = ChildProcessHost::CHILD_NORMAL;
121#endif
122
123  base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
124  if (exe_path.empty())
125    NOTREACHED() << "Unable to get service process binary name.";
126
127  CommandLine* cmd_line = new CommandLine(exe_path);
128  cmd_line->AppendSwitchASCII(switches::kProcessType,
129                              switches::kServiceProcess);
130
131  static const char* const kSwitchesToCopy[] = {
132    switches::kCloudPrintServiceURL,
133    switches::kCloudPrintSetupProxy,
134    switches::kEnableLogging,
135    switches::kIgnoreUrlFetcherCertRequests,
136    switches::kLang,
137    switches::kLoggingLevel,
138    switches::kLsoUrl,
139    switches::kNoServiceAutorun,
140    switches::kUserDataDir,
141    switches::kV,
142    switches::kVModule,
143    switches::kWaitForDebugger,
144  };
145  cmd_line->CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
146                             kSwitchesToCopy,
147                             arraysize(kSwitchesToCopy));
148
149  // And then start the process asynchronously.
150  launcher_ = new Launcher(this, cmd_line);
151  launcher_->Run(base::Bind(&ServiceProcessControl::OnProcessLaunched,
152                            base::Unretained(this)));
153}
154
155void ServiceProcessControl::Disconnect() {
156  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
157  channel_.reset();
158}
159
160void ServiceProcessControl::OnProcessLaunched() {
161  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
162  if (launcher_->launched()) {
163    // After we have successfully created the service process we try to connect
164    // to it. The launch task is transfered to a connect task.
165    ConnectInternal();
166  } else {
167    // If we don't have process handle that means launching the service process
168    // has failed.
169    RunConnectDoneTasks();
170  }
171
172  // We don't need the launcher anymore.
173  launcher_ = NULL;
174}
175
176bool ServiceProcessControl::OnMessageReceived(const IPC::Message& message) {
177  bool handled = true;
178  IPC_BEGIN_MESSAGE_MAP(ServiceProcessControl, message)
179    IPC_MESSAGE_HANDLER(ServiceHostMsg_CloudPrintProxy_Info,
180                        OnCloudPrintProxyInfo)
181    IPC_MESSAGE_UNHANDLED(handled = false)
182  IPC_END_MESSAGE_MAP()
183  return handled;
184}
185
186void ServiceProcessControl::OnChannelConnected(int32 peer_pid) {
187  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
188
189  // We just established a channel with the service process. Notify it if an
190  // upgrade is available.
191  if (UpgradeDetector::GetInstance()->notify_upgrade()) {
192    Send(new ServiceMsg_UpdateAvailable);
193  } else {
194    if (registrar_.IsEmpty())
195      registrar_.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED,
196                     content::NotificationService::AllSources());
197  }
198  RunConnectDoneTasks();
199}
200
201void ServiceProcessControl::OnChannelError() {
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203  channel_.reset();
204  RunConnectDoneTasks();
205}
206
207bool ServiceProcessControl::Send(IPC::Message* message) {
208  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
209  if (!channel_.get())
210    return false;
211  return channel_->Send(message);
212}
213
214// content::NotificationObserver implementation.
215void ServiceProcessControl::Observe(
216    int type,
217    const content::NotificationSource& source,
218    const content::NotificationDetails& details) {
219  if (type == chrome::NOTIFICATION_UPGRADE_RECOMMENDED) {
220    Send(new ServiceMsg_UpdateAvailable);
221  }
222}
223
224void ServiceProcessControl::OnCloudPrintProxyInfo(
225    const cloud_print::CloudPrintProxyInfo& proxy_info) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227  if (!cloud_print_info_callback_.is_null()) {
228    cloud_print_info_callback_.Run(proxy_info);
229    cloud_print_info_callback_.Reset();
230  }
231}
232
233bool ServiceProcessControl::GetCloudPrintProxyInfo(
234    const CloudPrintProxyInfoHandler& cloud_print_info_callback) {
235  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
236  DCHECK_EQ(false, cloud_print_info_callback.is_null());
237
238  cloud_print_info_callback_ = cloud_print_info_callback;
239  return Send(new ServiceMsg_GetCloudPrintProxyInfo());
240}
241
242bool ServiceProcessControl::Shutdown() {
243  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244  bool ret = Send(new ServiceMsg_Shutdown());
245  channel_.reset();
246  return ret;
247}
248
249// static
250ServiceProcessControl* ServiceProcessControl::GetInstance() {
251  return Singleton<ServiceProcessControl>::get();
252}
253
254ServiceProcessControl::Launcher::Launcher(ServiceProcessControl* process,
255                                          CommandLine* cmd_line)
256    : process_(process),
257      cmd_line_(cmd_line),
258      launched_(false),
259      retry_count_(0) {
260}
261
262// Execute the command line to start the process asynchronously.
263// After the command is executed, |task| is called with the process handle on
264// the UI thread.
265void ServiceProcessControl::Launcher::Run(const base::Closure& task) {
266  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
267  notify_task_ = task;
268  BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
269                          base::Bind(&Launcher::DoRun, this));
270}
271
272ServiceProcessControl::Launcher::~Launcher() {}
273
274void ServiceProcessControl::Launcher::Notify() {
275  DCHECK_EQ(false, notify_task_.is_null());
276  notify_task_.Run();
277  notify_task_.Reset();
278}
279
280#if !defined(OS_MACOSX)
281void ServiceProcessControl::Launcher::DoDetectLaunched() {
282  DCHECK_EQ(false, notify_task_.is_null());
283
284  const uint32 kMaxLaunchDetectRetries = 10;
285  launched_ = CheckServiceProcessReady();
286  if (launched_ || (retry_count_ >= kMaxLaunchDetectRetries)) {
287    BrowserThread::PostTask(
288        BrowserThread::UI, FROM_HERE, base::Bind(&Launcher::Notify, this));
289    return;
290  }
291  retry_count_++;
292
293  // If the service process is not launched yet then check again in 2 seconds.
294  const base::TimeDelta kDetectLaunchRetry = base::TimeDelta::FromSeconds(2);
295  base::MessageLoop::current()->PostDelayedTask(
296      FROM_HERE, base::Bind(&Launcher::DoDetectLaunched, this),
297      kDetectLaunchRetry);
298}
299
300void ServiceProcessControl::Launcher::DoRun() {
301  DCHECK_EQ(false, notify_task_.is_null());
302
303  base::LaunchOptions options;
304#if defined(OS_WIN)
305  options.start_hidden = true;
306#endif
307  if (base::LaunchProcess(*cmd_line_, options, NULL)) {
308    BrowserThread::PostTask(
309        BrowserThread::IO, FROM_HERE,
310        base::Bind(&Launcher::DoDetectLaunched, this));
311  } else {
312    BrowserThread::PostTask(
313        BrowserThread::UI, FROM_HERE, base::Bind(&Launcher::Notify, this));
314  }
315}
316#endif  // !OS_MACOSX
317