Home | History | Annotate | Download | only in service
      1 // Copyright (c) 2011 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/command_line.h"
      8 #include "base/file_path.h"
      9 #include "base/process_util.h"
     10 #include "base/stl_util-inl.h"
     11 #include "base/threading/thread.h"
     12 #include "base/threading/thread_restrictions.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/io_thread.h"
     15 #include "chrome/browser/upgrade_detector.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/service_messages.h"
     18 #include "chrome/common/service_process_util.h"
     19 #include "content/browser/browser_thread.h"
     20 #include "content/common/child_process_host.h"
     21 #include "content/common/notification_service.h"
     22 #include "ui/base/ui_base_switches.h"
     23 
     24 
     25 // ServiceProcessControl implementation.
     26 ServiceProcessControl::ServiceProcessControl(Profile* profile)
     27     : profile_(profile) {
     28 }
     29 
     30 ServiceProcessControl::~ServiceProcessControl() {
     31   STLDeleteElements(&connect_done_tasks_);
     32   STLDeleteElements(&connect_success_tasks_);
     33   STLDeleteElements(&connect_failure_tasks_);
     34 }
     35 
     36 void ServiceProcessControl::ConnectInternal() {
     37   // If the channel has already been established then we run the task
     38   // and return.
     39   if (channel_.get()) {
     40     RunConnectDoneTasks();
     41     return;
     42   }
     43 
     44   // Actually going to connect.
     45   VLOG(1) << "Connecting to Service Process IPC Server";
     46   // Run the IPC channel on the shared IO thread.
     47   base::Thread* io_thread = g_browser_process->io_thread();
     48 
     49   // TODO(hclam): Handle error connecting to channel.
     50   const IPC::ChannelHandle channel_id = GetServiceProcessChannel();
     51   channel_.reset(
     52       new IPC::SyncChannel(channel_id, IPC::Channel::MODE_NAMED_CLIENT, this,
     53                            io_thread->message_loop(), true,
     54                            g_browser_process->shutdown_event()));
     55 }
     56 
     57 void ServiceProcessControl::RunConnectDoneTasks() {
     58   // The tasks executed here may add more tasks to the vector. So copy
     59   // them to the stack before executing them. This way recursion is
     60   // avoided.
     61   TaskList tasks;
     62   tasks.swap(connect_done_tasks_);
     63   RunAllTasksHelper(&tasks);
     64   DCHECK(tasks.empty());
     65 
     66   if (is_connected()) {
     67     tasks.swap(connect_success_tasks_);
     68     RunAllTasksHelper(&tasks);
     69     DCHECK(tasks.empty());
     70 
     71     STLDeleteElements(&connect_failure_tasks_);
     72   } else {
     73     tasks.swap(connect_failure_tasks_);
     74     RunAllTasksHelper(&tasks);
     75     DCHECK(tasks.empty());
     76 
     77     STLDeleteElements(&connect_success_tasks_);
     78   }
     79 
     80   DCHECK(connect_done_tasks_.empty());
     81   DCHECK(connect_success_tasks_.empty());
     82   DCHECK(connect_failure_tasks_.empty());
     83 }
     84 
     85 // static
     86 void ServiceProcessControl::RunAllTasksHelper(TaskList* task_list) {
     87   TaskList::iterator index = task_list->begin();
     88   while (index != task_list->end()) {
     89     (*index)->Run();
     90     delete (*index);
     91     index = task_list->erase(index);
     92   }
     93 }
     94 
     95 void ServiceProcessControl::Launch(Task* success_task, Task* failure_task) {
     96   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     97 
     98   if (success_task) {
     99     if (success_task == failure_task) {
    100       // If the tasks are the same, then the same task needs to be invoked
    101       // for success and failure.
    102       failure_task = NULL;
    103       connect_done_tasks_.push_back(success_task);
    104     } else {
    105       connect_success_tasks_.push_back(success_task);
    106     }
    107   }
    108 
    109   if (failure_task)
    110     connect_failure_tasks_.push_back(failure_task);
    111 
    112   // If we already in the process of launching, then we are done.
    113   if (launcher_) {
    114     return;
    115   }
    116 
    117   // If the service process is already running then connects to it.
    118   if (CheckServiceProcessReady()) {
    119     ConnectInternal();
    120     return;
    121   }
    122 
    123   // A service process should have a different mechanism for starting, but now
    124   // we start it as if it is a child process.
    125   FilePath exe_path = ChildProcessHost::GetChildPath(true);
    126   if (exe_path.empty()) {
    127     NOTREACHED() << "Unable to get service process binary name.";
    128   }
    129 
    130   CommandLine* cmd_line = new CommandLine(exe_path);
    131   cmd_line->AppendSwitchASCII(switches::kProcessType,
    132                               switches::kServiceProcess);
    133 
    134   const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
    135   FilePath user_data_dir =
    136       browser_command_line.GetSwitchValuePath(switches::kUserDataDir);
    137   if (!user_data_dir.empty())
    138     cmd_line->AppendSwitchPath(switches::kUserDataDir, user_data_dir);
    139 
    140   std::string logging_level = browser_command_line.GetSwitchValueASCII(
    141       switches::kLoggingLevel);
    142   if (!logging_level.empty())
    143     cmd_line->AppendSwitchASCII(switches::kLoggingLevel, logging_level);
    144 
    145   std::string v_level = browser_command_line.GetSwitchValueASCII(
    146       switches::kV);
    147   if (!v_level.empty())
    148     cmd_line->AppendSwitchASCII(switches::kV, v_level);
    149 
    150   std::string v_modules = browser_command_line.GetSwitchValueASCII(
    151       switches::kVModule);
    152   if (!v_modules.empty())
    153     cmd_line->AppendSwitchASCII(switches::kVModule, v_modules);
    154 
    155   if (browser_command_line.HasSwitch(switches::kWaitForDebuggerChildren)) {
    156     cmd_line->AppendSwitch(switches::kWaitForDebugger);
    157   }
    158 
    159   if (browser_command_line.HasSwitch(switches::kEnableLogging)) {
    160     cmd_line->AppendSwitch(switches::kEnableLogging);
    161   }
    162 
    163   std::string locale = g_browser_process->GetApplicationLocale();
    164   cmd_line->AppendSwitchASCII(switches::kLang, locale);
    165 
    166   // And then start the process asynchronously.
    167   launcher_ = new Launcher(this, cmd_line);
    168   launcher_->Run(
    169       NewRunnableMethod(this, &ServiceProcessControl::OnProcessLaunched));
    170 }
    171 
    172 void ServiceProcessControl::OnProcessLaunched() {
    173   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    174   if (launcher_->launched()) {
    175     // After we have successfully created the service process we try to connect
    176     // to it. The launch task is transfered to a connect task.
    177     ConnectInternal();
    178   } else {
    179     // If we don't have process handle that means launching the service process
    180     // has failed.
    181     RunConnectDoneTasks();
    182   }
    183 
    184   // We don't need the launcher anymore.
    185   launcher_ = NULL;
    186 }
    187 
    188 bool ServiceProcessControl::OnMessageReceived(const IPC::Message& message) {
    189   bool handled = true;
    190   IPC_BEGIN_MESSAGE_MAP(ServiceProcessControl, message)
    191     IPC_MESSAGE_HANDLER(ServiceHostMsg_CloudPrintProxy_IsEnabled,
    192                         OnCloudPrintProxyIsEnabled)
    193     IPC_MESSAGE_HANDLER(ServiceHostMsg_RemotingHost_HostInfo,
    194                          OnRemotingHostInfo)
    195     IPC_MESSAGE_UNHANDLED(handled = false)
    196   IPC_END_MESSAGE_MAP()
    197   return handled;
    198 }
    199 
    200 void ServiceProcessControl::OnChannelConnected(int32 peer_pid) {
    201   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    202   channel_->set_sync_messages_with_no_timeout_allowed(false);
    203 
    204   // We just established a channel with the service process. Notify it if an
    205   // upgrade is available.
    206   if (UpgradeDetector::GetInstance()->notify_upgrade()) {
    207     Send(new ServiceMsg_UpdateAvailable);
    208   } else {
    209     if (registrar_.IsEmpty())
    210       registrar_.Add(this, NotificationType::UPGRADE_RECOMMENDED,
    211                      NotificationService::AllSources());
    212   }
    213   RunConnectDoneTasks();
    214 }
    215 
    216 void ServiceProcessControl::OnChannelError() {
    217   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    218   channel_.reset();
    219   RunConnectDoneTasks();
    220 }
    221 
    222 bool ServiceProcessControl::Send(IPC::Message* message) {
    223   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    224   if (!channel_.get())
    225     return false;
    226   return channel_->Send(message);
    227 }
    228 
    229 // NotificationObserver implementation.
    230 void ServiceProcessControl::Observe(NotificationType type,
    231                                     const NotificationSource& source,
    232                                     const NotificationDetails& details) {
    233   if (type == NotificationType::UPGRADE_RECOMMENDED) {
    234     Send(new ServiceMsg_UpdateAvailable);
    235   }
    236 }
    237 
    238 void ServiceProcessControl::OnCloudPrintProxyIsEnabled(bool enabled,
    239                                                        std::string email) {
    240   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    241   if (cloud_print_status_callback_ != NULL) {
    242     cloud_print_status_callback_->Run(enabled, email);
    243     cloud_print_status_callback_.reset();
    244   }
    245 }
    246 
    247 void ServiceProcessControl::OnRemotingHostInfo(
    248     const remoting::ChromotingHostInfo& host_info) {
    249   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    250   for (std::set<MessageHandler*>::iterator it = message_handlers_.begin();
    251        it != message_handlers_.end(); ++it) {
    252     (*it)->OnRemotingHostInfo(host_info);
    253   }
    254 }
    255 
    256 bool ServiceProcessControl::GetCloudPrintProxyStatus(
    257     Callback2<bool, std::string>::Type* cloud_print_status_callback) {
    258   DCHECK(cloud_print_status_callback);
    259   cloud_print_status_callback_.reset(cloud_print_status_callback);
    260   return Send(new ServiceMsg_IsCloudPrintProxyEnabled);
    261 }
    262 
    263 bool ServiceProcessControl::Shutdown() {
    264   bool ret = Send(new ServiceMsg_Shutdown());
    265   channel_.reset();
    266   return ret;
    267 }
    268 
    269 bool ServiceProcessControl::SetRemotingHostCredentials(
    270     const std::string& user,
    271     const std::string& talk_token) {
    272   return Send(
    273       new ServiceMsg_SetRemotingHostCredentials(user, talk_token));
    274 }
    275 
    276 bool ServiceProcessControl::EnableRemotingHost() {
    277   return Send(new ServiceMsg_EnableRemotingHost());
    278 }
    279 
    280 bool ServiceProcessControl::DisableRemotingHost() {
    281   return Send(new ServiceMsg_DisableRemotingHost());
    282 }
    283 
    284 bool ServiceProcessControl::RequestRemotingHostStatus() {
    285   if (CheckServiceProcessReady()) {
    286     remoting::ChromotingHostInfo failure_host_info;
    287     failure_host_info.enabled = false;
    288 
    289     Launch(NewRunnableMethod(this, &ServiceProcessControl::Send,
    290                              new ServiceMsg_GetRemotingHostInfo),
    291            NewRunnableMethod(this,
    292                              &ServiceProcessControl::OnRemotingHostInfo,
    293                              failure_host_info));
    294     return true;
    295   }
    296   return false;
    297 }
    298 
    299 void ServiceProcessControl::AddMessageHandler(
    300     MessageHandler* message_handler) {
    301   message_handlers_.insert(message_handler);
    302 }
    303 
    304 void ServiceProcessControl::RemoveMessageHandler(
    305     MessageHandler* message_handler) {
    306   message_handlers_.erase(message_handler);
    307 }
    308 
    309 DISABLE_RUNNABLE_METHOD_REFCOUNT(ServiceProcessControl);
    310 
    311 ServiceProcessControl::Launcher::Launcher(ServiceProcessControl* process,
    312                                           CommandLine* cmd_line)
    313     : process_(process),
    314       cmd_line_(cmd_line),
    315       launched_(false),
    316       retry_count_(0) {
    317 }
    318 
    319 // Execute the command line to start the process asynchronously.
    320 // After the command is executed, |task| is called with the process handle on
    321 // the UI thread.
    322 void ServiceProcessControl::Launcher::Run(Task* task) {
    323   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    324   notify_task_.reset(task);
    325   BrowserThread::PostTask(BrowserThread::PROCESS_LAUNCHER, FROM_HERE,
    326                          NewRunnableMethod(this, &Launcher::DoRun));
    327 }
    328 
    329 ServiceProcessControl::Launcher::~Launcher() {}
    330 
    331 void ServiceProcessControl::Launcher::Notify() {
    332   DCHECK(notify_task_.get());
    333   notify_task_->Run();
    334   notify_task_.reset();
    335 }
    336 
    337 #if !defined(OS_MACOSX)
    338 void ServiceProcessControl::Launcher::DoDetectLaunched() {
    339   DCHECK(notify_task_.get());
    340   const uint32 kMaxLaunchDetectRetries = 10;
    341   launched_ = CheckServiceProcessReady();
    342   if (launched_ || (retry_count_ >= kMaxLaunchDetectRetries)) {
    343     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    344         NewRunnableMethod(this, &Launcher::Notify));
    345     return;
    346   }
    347   retry_count_++;
    348 
    349   // If the service process is not launched yet then check again in 2 seconds.
    350   const int kDetectLaunchRetry = 2000;
    351   MessageLoop::current()->PostDelayedTask(
    352       FROM_HERE,
    353       NewRunnableMethod(this, &Launcher::DoDetectLaunched),
    354       kDetectLaunchRetry);
    355 }
    356 
    357 void ServiceProcessControl::Launcher::DoRun() {
    358   DCHECK(notify_task_.get());
    359   if (base::LaunchApp(*cmd_line_, false, true, NULL)) {
    360     BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
    361                             NewRunnableMethod(this,
    362                                               &Launcher::DoDetectLaunched));
    363   } else {
    364     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    365                             NewRunnableMethod(this, &Launcher::Notify));
    366   }
    367 }
    368 #endif  // !OS_MACOSX
    369