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/service/service_utility_process_host.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/file_util.h"
     10 #include "base/files/scoped_temp_dir.h"
     11 #include "base/logging.h"
     12 #include "base/message_loop/message_loop.h"
     13 #include "base/message_loop/message_loop_proxy.h"
     14 #include "base/metrics/histogram.h"
     15 #include "base/process/kill.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/chrome_utility_messages.h"
     19 #include "content/public/common/child_process_host.h"
     20 #include "content/public/common/result_codes.h"
     21 #include "content/public/common/sandbox_init.h"
     22 #include "ipc/ipc_switches.h"
     23 #include "printing/page_range.h"
     24 #include "ui/base/ui_base_switches.h"
     25 #include "ui/gfx/rect.h"
     26 
     27 #if defined(OS_WIN)
     28 #include "base/files/file_path.h"
     29 #include "base/memory/scoped_ptr.h"
     30 #include "base/process/launch.h"
     31 #include "base/win/scoped_handle.h"
     32 #include "content/public/common/sandbox_init.h"
     33 #include "content/public/common/sandboxed_process_launcher_delegate.h"
     34 #include "printing/emf_win.h"
     35 
     36 namespace {
     37 
     38 // NOTE: changes to this class need to be reviewed by the security team.
     39 class ServiceSandboxedProcessLauncherDelegate
     40     : public content::SandboxedProcessLauncherDelegate {
     41  public:
     42   explicit ServiceSandboxedProcessLauncherDelegate(
     43       const base::FilePath& exposed_dir)
     44     : exposed_dir_(exposed_dir) {
     45   }
     46 
     47   virtual void PreSandbox(bool* disable_default_policy,
     48                           base::FilePath* exposed_dir) OVERRIDE {
     49     *exposed_dir = exposed_dir_;
     50   }
     51 
     52  private:
     53   base::FilePath exposed_dir_;
     54 };
     55 
     56 }  // namespace
     57 
     58 #endif  // OS_WIN
     59 
     60 using content::ChildProcessHost;
     61 
     62 namespace {
     63   enum ServiceUtilityProcessHostEvent {
     64   SERVICE_UTILITY_STARTED,
     65   SERVICE_UTILITY_DISCONNECTED,
     66   SERVICE_UTILITY_METAFILE_REQUEST,
     67   SERVICE_UTILITY_METAFILE_SUCCEEDED,
     68   SERVICE_UTILITY_METAFILE_FAILED,
     69   SERVICE_UTILITY_CAPS_REQUEST,
     70   SERVICE_UTILITY_CAPS_SUCCEEDED,
     71   SERVICE_UTILITY_CAPS_FAILED,
     72   SERVICE_UTILITY_EVENT_MAX,
     73 };
     74 }  // namespace
     75 
     76 ServiceUtilityProcessHost::ServiceUtilityProcessHost(
     77     Client* client, base::MessageLoopProxy* client_message_loop_proxy)
     78         : handle_(base::kNullProcessHandle),
     79           client_(client),
     80           client_message_loop_proxy_(client_message_loop_proxy),
     81           waiting_for_reply_(false) {
     82   child_process_host_.reset(ChildProcessHost::Create(this));
     83 }
     84 
     85 ServiceUtilityProcessHost::~ServiceUtilityProcessHost() {
     86   // We need to kill the child process when the host dies.
     87   base::KillProcess(handle_, content::RESULT_CODE_NORMAL_EXIT, false);
     88 }
     89 
     90 bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile(
     91     const base::FilePath& pdf_path,
     92     const printing::PdfRenderSettings& render_settings,
     93     const std::vector<printing::PageRange>& page_ranges) {
     94   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
     95                             SERVICE_UTILITY_METAFILE_REQUEST,
     96                             SERVICE_UTILITY_EVENT_MAX);
     97   start_time_ = base::Time::Now();
     98 #if !defined(OS_WIN)
     99   // This is only implemented on Windows (because currently it is only needed
    100   // on Windows). Will add implementations on other platforms when needed.
    101   NOTIMPLEMENTED();
    102   return false;
    103 #else  // !defined(OS_WIN)
    104   scratch_metafile_dir_.reset(new base::ScopedTempDir);
    105   if (!scratch_metafile_dir_->CreateUniqueTempDir())
    106     return false;
    107   if (!base::CreateTemporaryFileInDir(scratch_metafile_dir_->path(),
    108                                       &metafile_path_)) {
    109     return false;
    110   }
    111 
    112   if (!StartProcess(false, scratch_metafile_dir_->path()))
    113     return false;
    114 
    115   base::win::ScopedHandle pdf_file(
    116       ::CreateFile(pdf_path.value().c_str(),
    117                    GENERIC_READ,
    118                    FILE_SHARE_READ | FILE_SHARE_WRITE,
    119                    NULL,
    120                    OPEN_EXISTING,
    121                    FILE_ATTRIBUTE_NORMAL,
    122                    NULL));
    123   if (pdf_file == INVALID_HANDLE_VALUE)
    124     return false;
    125   HANDLE pdf_file_in_utility_process = NULL;
    126   ::DuplicateHandle(::GetCurrentProcess(), pdf_file, handle(),
    127                     &pdf_file_in_utility_process, 0, false,
    128                     DUPLICATE_SAME_ACCESS);
    129   if (!pdf_file_in_utility_process)
    130     return false;
    131   waiting_for_reply_ = true;
    132   return child_process_host_->Send(
    133       new ChromeUtilityMsg_RenderPDFPagesToMetafile(
    134           pdf_file_in_utility_process,
    135           metafile_path_,
    136           render_settings,
    137           page_ranges));
    138 #endif  // !defined(OS_WIN)
    139 }
    140 
    141 bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults(
    142     const std::string& printer_name) {
    143   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    144                             SERVICE_UTILITY_CAPS_REQUEST,
    145                             SERVICE_UTILITY_EVENT_MAX);
    146   start_time_ = base::Time::Now();
    147   base::FilePath exposed_path;
    148   if (!StartProcess(true, exposed_path))
    149     return false;
    150   waiting_for_reply_ = true;
    151   return child_process_host_->Send(
    152       new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name));
    153 }
    154 
    155 bool ServiceUtilityProcessHost::StartProcess(
    156     bool no_sandbox,
    157     const base::FilePath& exposed_dir) {
    158   std::string channel_id = child_process_host_->CreateChannel();
    159   if (channel_id.empty())
    160     return false;
    161 
    162   base::FilePath exe_path = GetUtilityProcessCmd();
    163   if (exe_path.empty()) {
    164     NOTREACHED() << "Unable to get utility process binary name.";
    165     return false;
    166   }
    167 
    168   CommandLine cmd_line(exe_path);
    169   cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess);
    170   cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id);
    171   cmd_line.AppendSwitch(switches::kLang);
    172 
    173   if (Launch(&cmd_line, no_sandbox, exposed_dir)) {
    174     UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    175                               SERVICE_UTILITY_STARTED,
    176                               SERVICE_UTILITY_EVENT_MAX);
    177     return true;
    178   }
    179   return false;
    180 }
    181 
    182 bool ServiceUtilityProcessHost::Launch(CommandLine* cmd_line,
    183                                        bool no_sandbox,
    184                                        const base::FilePath& exposed_dir) {
    185 #if !defined(OS_WIN)
    186   // TODO(sanjeevr): Implement for non-Windows OSes.
    187   NOTIMPLEMENTED();
    188   return false;
    189 #else  // !defined(OS_WIN)
    190 
    191   if (no_sandbox) {
    192     base::ProcessHandle process = base::kNullProcessHandle;
    193     cmd_line->AppendSwitch(switches::kNoSandbox);
    194     base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_);
    195   } else {
    196     ServiceSandboxedProcessLauncherDelegate delegate(exposed_dir);
    197     handle_ = content::StartSandboxedProcess(&delegate, cmd_line);
    198   }
    199   return (handle_ != base::kNullProcessHandle);
    200 #endif  // !defined(OS_WIN)
    201 }
    202 
    203 base::FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() {
    204 #if defined(OS_LINUX)
    205   int flags = ChildProcessHost::CHILD_ALLOW_SELF;
    206 #else
    207   int flags = ChildProcessHost::CHILD_NORMAL;
    208 #endif
    209   return ChildProcessHost::GetChildPath(flags);
    210 }
    211 
    212 void ServiceUtilityProcessHost::OnChildDisconnected() {
    213   if (waiting_for_reply_) {
    214     // If we are yet to receive a reply then notify the client that the
    215     // child died.
    216     client_message_loop_proxy_->PostTask(
    217         FROM_HERE, base::Bind(&Client::OnChildDied, client_.get()));
    218     UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    219                               SERVICE_UTILITY_DISCONNECTED,
    220                               SERVICE_UTILITY_EVENT_MAX);
    221     UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityDisconnectTime",
    222                         base::Time::Now() - start_time_);
    223   }
    224   delete this;
    225 }
    226 
    227 bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) {
    228   bool handled = true;
    229   IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message)
    230     IPC_MESSAGE_HANDLER(
    231         ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Succeeded,
    232         OnRenderPDFPagesToMetafileSucceeded)
    233     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed,
    234                         OnRenderPDFPagesToMetafileFailed)
    235     IPC_MESSAGE_HANDLER(
    236         ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
    237         OnGetPrinterCapsAndDefaultsSucceeded)
    238     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
    239                         OnGetPrinterCapsAndDefaultsFailed)
    240     IPC_MESSAGE_UNHANDLED(handled = false)
    241   IPC_END_MESSAGE_MAP()
    242   return handled;
    243 }
    244 
    245 void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileSucceeded(
    246     int highest_rendered_page_number,
    247     double scale_factor) {
    248   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    249                             SERVICE_UTILITY_METAFILE_SUCCEEDED,
    250                             SERVICE_UTILITY_EVENT_MAX);
    251   UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime",
    252                       base::Time::Now() - start_time_);
    253   DCHECK(waiting_for_reply_);
    254   waiting_for_reply_ = false;
    255   // If the metafile was successfully created, we need to take our hands off the
    256   // scratch metafile directory. The client will delete it when it is done with
    257   // metafile.
    258   scratch_metafile_dir_->Take();
    259   client_message_loop_proxy_->PostTask(
    260       FROM_HERE,
    261       base::Bind(&Client::MetafileAvailable, client_.get(), metafile_path_,
    262                  highest_rendered_page_number, scale_factor));
    263 }
    264 
    265 void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileFailed() {
    266   DCHECK(waiting_for_reply_);
    267   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    268                             SERVICE_UTILITY_METAFILE_FAILED,
    269                             SERVICE_UTILITY_EVENT_MAX);
    270   UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime",
    271                       base::Time::Now() - start_time_);
    272   waiting_for_reply_ = false;
    273   client_message_loop_proxy_->PostTask(
    274       FROM_HERE,
    275       base::Bind(&Client::OnRenderPDFPagesToMetafileFailed, client_.get()));
    276 }
    277 
    278 void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded(
    279     const std::string& printer_name,
    280     const printing::PrinterCapsAndDefaults& caps_and_defaults) {
    281   DCHECK(waiting_for_reply_);
    282   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    283                             SERVICE_UTILITY_CAPS_SUCCEEDED,
    284                             SERVICE_UTILITY_EVENT_MAX);
    285   UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsTime",
    286                       base::Time::Now() - start_time_);
    287   waiting_for_reply_ = false;
    288   client_message_loop_proxy_->PostTask(
    289       FROM_HERE,
    290       base::Bind(&Client::OnGetPrinterCapsAndDefaultsSucceeded, client_.get(),
    291                  printer_name, caps_and_defaults));
    292 }
    293 
    294 void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsFailed(
    295     const std::string& printer_name) {
    296   DCHECK(waiting_for_reply_);
    297   UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
    298                             SERVICE_UTILITY_CAPS_FAILED,
    299                             SERVICE_UTILITY_EVENT_MAX);
    300   UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsFailTime",
    301                       base::Time::Now() - start_time_);
    302   waiting_for_reply_ = false;
    303   client_message_loop_proxy_->PostTask(
    304       FROM_HERE,
    305       base::Bind(&Client::OnGetPrinterCapsAndDefaultsFailed, client_.get(),
    306                  printer_name));
    307 }
    308 
    309 void ServiceUtilityProcessHost::Client::MetafileAvailable(
    310     const base::FilePath& metafile_path,
    311     int highest_rendered_page_number,
    312     double scale_factor) {
    313   // The metafile was created in a temp folder which needs to get deleted after
    314   // we have processed it.
    315   base::ScopedTempDir scratch_metafile_dir;
    316   if (!scratch_metafile_dir.Set(metafile_path.DirName()))
    317     LOG(WARNING) << "Unable to set scratch metafile directory";
    318 #if defined(OS_WIN)
    319   // It's important that metafile is declared after scratch_metafile_dir so
    320   // that the metafile destructor closes the file before the base::ScopedTempDir
    321   // destructor tries to remove the directory.
    322   printing::Emf metafile;
    323   if (!metafile.InitFromFile(metafile_path)) {
    324     OnRenderPDFPagesToMetafileFailed();
    325   } else {
    326     OnRenderPDFPagesToMetafileSucceeded(metafile,
    327                                         highest_rendered_page_number,
    328                                         scale_factor);
    329   }
    330 #endif  // defined(OS_WIN)
    331 }
    332 
    333