Home | History | Annotate | Download | only in service
      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 
     27 using content::BrowserThread;
     28 using content::ChildProcessHost;
     29 
     30 // ServiceProcessControl implementation.
     31 ServiceProcessControl::ServiceProcessControl() {
     32 }
     33 
     34 ServiceProcessControl::~ServiceProcessControl() {
     35 }
     36 
     37 void 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 
     57 void ServiceProcessControl::SetChannel(IPC::ChannelProxy* channel) {
     58   channel_.reset(channel);
     59 }
     60 
     61 void 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
     81 void 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 
     89 bool ServiceProcessControl::IsConnected() const {
     90   return channel_ != NULL;
     91 }
     92 
     93 void 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 
    155 void ServiceProcessControl::Disconnect() {
    156   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    157   channel_.reset();
    158 }
    159 
    160 void 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 
    176 bool 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 
    186 void 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 
    201 void ServiceProcessControl::OnChannelError() {
    202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    203   channel_.reset();
    204   RunConnectDoneTasks();
    205 }
    206 
    207 bool 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.
    215 void 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 
    224 void 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 
    233 bool 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 
    242 bool 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
    250 ServiceProcessControl* ServiceProcessControl::GetInstance() {
    251   return Singleton<ServiceProcessControl>::get();
    252 }
    253 
    254 ServiceProcessControl::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.
    265 void 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 
    272 ServiceProcessControl::Launcher::~Launcher() {}
    273 
    274 void 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)
    281 void 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 
    300 void 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