// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/service/service_utility_process_host.h" #include #include "base/bind.h" #include "base/command_line.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/message_loop/message_loop_proxy.h" #include "base/metrics/histogram.h" #include "base/process/launch.h" #include "base/task_runner_util.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_utility_printing_messages.h" #include "content/public/common/child_process_host.h" #include "content/public/common/result_codes.h" #include "content/public/common/sandbox_init.h" #include "content/public/common/sandboxed_process_launcher_delegate.h" #include "ipc/ipc_switches.h" #include "printing/emf_win.h" #include "sandbox/win/src/sandbox_policy_base.h" #include "ui/base/ui_base_switches.h" namespace { using content::ChildProcessHost; enum ServiceUtilityProcessHostEvent { SERVICE_UTILITY_STARTED, SERVICE_UTILITY_DISCONNECTED, SERVICE_UTILITY_METAFILE_REQUEST, SERVICE_UTILITY_METAFILE_SUCCEEDED, SERVICE_UTILITY_METAFILE_FAILED, SERVICE_UTILITY_CAPS_REQUEST, SERVICE_UTILITY_CAPS_SUCCEEDED, SERVICE_UTILITY_CAPS_FAILED, SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST, SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED, SERVICE_UTILITY_SEMANTIC_CAPS_FAILED, SERVICE_UTILITY_FAILED_TO_START, SERVICE_UTILITY_EVENT_MAX, }; void ReportUmaEvent(ServiceUtilityProcessHostEvent id) { UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent", id, SERVICE_UTILITY_EVENT_MAX); } // NOTE: changes to this class need to be reviewed by the security team. class ServiceSandboxedProcessLauncherDelegate : public content::SandboxedProcessLauncherDelegate { public: ServiceSandboxedProcessLauncherDelegate() {} void PreSpawnTarget(sandbox::TargetPolicy* policy, bool* success) override { // Service process may run as windows service and it fails to create a // window station. policy->SetAlternateDesktop(false); } private: DISALLOW_COPY_AND_ASSIGN(ServiceSandboxedProcessLauncherDelegate); }; } // namespace class ServiceUtilityProcessHost::PdfToEmfState { public: explicit PdfToEmfState(ServiceUtilityProcessHost* host) : host_(host), page_count_(0), current_page_(0), pages_in_progress_(0) {} ~PdfToEmfState() { Stop(); } bool Start(base::File pdf_file, const printing::PdfRenderSettings& conversion_settings) { if (!temp_dir_.CreateUniqueTempDir()) return false; return host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles( IPC::TakeFileHandleForProcess(pdf_file.Pass(), host_->handle()), conversion_settings)); } void GetMorePages() { const int kMaxNumberOfTempFilesPerDocument = 3; while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument && current_page_ < page_count_) { ++pages_in_progress_; emf_files_.push(CreateTempFile()); host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage( current_page_++, IPC::GetFileHandleForProcess( emf_files_.back().GetPlatformFile(), host_->handle(), false))); } } // Returns true if all pages processed and client should not expect more // results. bool OnPageProcessed() { --pages_in_progress_; GetMorePages(); if (pages_in_progress_ || current_page_ < page_count_) return false; Stop(); return true; } base::File TakeNextFile() { DCHECK(!emf_files_.empty()); base::File file; if (!emf_files_.empty()) file = emf_files_.front().Pass(); emf_files_.pop(); return file.Pass(); } void set_page_count(int page_count) { page_count_ = page_count; } bool has_page_count() { return page_count_ > 0; } private: void Stop() { host_->Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop()); } base::File CreateTempFile() { base::FilePath path; if (!base::CreateTemporaryFileInDir(temp_dir_.path(), &path)) return base::File(); return base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | base::File::FLAG_READ | base::File::FLAG_DELETE_ON_CLOSE | base::File::FLAG_TEMPORARY); } base::ScopedTempDir temp_dir_; ServiceUtilityProcessHost* host_; std::queue emf_files_; int page_count_; int current_page_; int pages_in_progress_; }; ServiceUtilityProcessHost::ServiceUtilityProcessHost( Client* client, base::MessageLoopProxy* client_message_loop_proxy) : client_(client), client_message_loop_proxy_(client_message_loop_proxy), waiting_for_reply_(false), weak_ptr_factory_(this) { child_process_host_.reset(ChildProcessHost::Create(this)); } ServiceUtilityProcessHost::~ServiceUtilityProcessHost() { // We need to kill the child process when the host dies. process_.Terminate(content::RESULT_CODE_NORMAL_EXIT, false); } bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile( const base::FilePath& pdf_path, const printing::PdfRenderSettings& render_settings) { ReportUmaEvent(SERVICE_UTILITY_METAFILE_REQUEST); start_time_ = base::Time::Now(); base::File pdf_file(pdf_path, base::File::FLAG_OPEN | base::File::FLAG_READ); if (!pdf_file.IsValid() || !StartProcess(false)) return false; DCHECK(!waiting_for_reply_); waiting_for_reply_ = true; pdf_to_emf_state_.reset(new PdfToEmfState(this)); return pdf_to_emf_state_->Start(pdf_file.Pass(), render_settings); } bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults( const std::string& printer_name) { ReportUmaEvent(SERVICE_UTILITY_CAPS_REQUEST); start_time_ = base::Time::Now(); if (!StartProcess(true)) return false; DCHECK(!waiting_for_reply_); waiting_for_reply_ = true; return Send(new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name)); } bool ServiceUtilityProcessHost::StartGetPrinterSemanticCapsAndDefaults( const std::string& printer_name) { ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_REQUEST); start_time_ = base::Time::Now(); if (!StartProcess(true)) return false; DCHECK(!waiting_for_reply_); waiting_for_reply_ = true; return Send( new ChromeUtilityMsg_GetPrinterSemanticCapsAndDefaults(printer_name)); } bool ServiceUtilityProcessHost::StartProcess(bool no_sandbox) { std::string channel_id = child_process_host_->CreateChannel(); if (channel_id.empty()) return false; base::FilePath exe_path = GetUtilityProcessCmd(); if (exe_path.empty()) { NOTREACHED() << "Unable to get utility process binary name."; return false; } base::CommandLine cmd_line(exe_path); cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess); cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id); cmd_line.AppendSwitch(switches::kLang); if (Launch(&cmd_line, no_sandbox)) { ReportUmaEvent(SERVICE_UTILITY_STARTED); return true; } ReportUmaEvent(SERVICE_UTILITY_FAILED_TO_START); return false; } bool ServiceUtilityProcessHost::Launch(base::CommandLine* cmd_line, bool no_sandbox) { if (no_sandbox) { cmd_line->AppendSwitch(switches::kNoSandbox); process_ = base::LaunchProcess(*cmd_line, base::LaunchOptions()); } else { ServiceSandboxedProcessLauncherDelegate delegate; process_ = content::StartSandboxedProcess(&delegate, cmd_line); } return process_.IsValid(); } bool ServiceUtilityProcessHost::Send(IPC::Message* msg) { if (child_process_host_) return child_process_host_->Send(msg); delete msg; return false; } base::FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() { #if defined(OS_LINUX) int flags = ChildProcessHost::CHILD_ALLOW_SELF; #else int flags = ChildProcessHost::CHILD_NORMAL; #endif return ChildProcessHost::GetChildPath(flags); } void ServiceUtilityProcessHost::OnChildDisconnected() { if (waiting_for_reply_) { // If we are yet to receive a reply then notify the client that the // child died. client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind(&Client::OnChildDied, client_.get())); ReportUmaEvent(SERVICE_UTILITY_DISCONNECTED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityDisconnectTime", base::Time::Now() - start_time_); } delete this; } bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message) IPC_MESSAGE_HANDLER( ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, OnRenderPDFPagesToMetafilesPageCount) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone, OnRenderPDFPagesToMetafilesPageDone) IPC_MESSAGE_HANDLER( ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded, OnGetPrinterCapsAndDefaultsSucceeded) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed, OnGetPrinterCapsAndDefaultsFailed) IPC_MESSAGE_HANDLER( ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Succeeded, OnGetPrinterSemanticCapsAndDefaultsSucceeded) IPC_MESSAGE_HANDLER( ChromeUtilityHostMsg_GetPrinterSemanticCapsAndDefaults_Failed, OnGetPrinterSemanticCapsAndDefaultsFailed) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } const base::Process& ServiceUtilityProcessHost::GetProcess() const { return process_; } void ServiceUtilityProcessHost::OnMetafileSpooled(bool success) { if (!success || pdf_to_emf_state_->OnPageProcessed()) OnPDFToEmfFinished(success); } void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageCount( int page_count) { DCHECK(waiting_for_reply_); if (!pdf_to_emf_state_ || page_count <= 0 || pdf_to_emf_state_->has_page_count()) { return OnPDFToEmfFinished(false); } pdf_to_emf_state_->set_page_count(page_count); pdf_to_emf_state_->GetMorePages(); } void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafilesPageDone( bool success, float scale_factor) { DCHECK(waiting_for_reply_); if (!pdf_to_emf_state_ || !success) return OnPDFToEmfFinished(false); base::File emf_file = pdf_to_emf_state_->TakeNextFile(); base::PostTaskAndReplyWithResult( client_message_loop_proxy_.get(), FROM_HERE, base::Bind(&Client::MetafileAvailable, client_.get(), scale_factor, base::Passed(&emf_file)), base::Bind(&ServiceUtilityProcessHost::OnMetafileSpooled, weak_ptr_factory_.GetWeakPtr())); } void ServiceUtilityProcessHost::OnPDFToEmfFinished(bool success) { if (!waiting_for_reply_) return; waiting_for_reply_ = false; if (success) { ReportUmaEvent(SERVICE_UTILITY_METAFILE_SUCCEEDED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime", base::Time::Now() - start_time_); } else { ReportUmaEvent(SERVICE_UTILITY_METAFILE_FAILED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime", base::Time::Now() - start_time_); } client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind( &Client::OnRenderPDFPagesToMetafileDone, client_.get(), success)); pdf_to_emf_state_.reset(); } void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded( const std::string& printer_name, const printing::PrinterCapsAndDefaults& caps_and_defaults) { DCHECK(waiting_for_reply_); ReportUmaEvent(SERVICE_UTILITY_CAPS_SUCCEEDED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsTime", base::Time::Now() - start_time_); waiting_for_reply_ = false; client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind(&Client::OnGetPrinterCapsAndDefaults, client_.get(), true, printer_name, caps_and_defaults)); } void ServiceUtilityProcessHost::OnGetPrinterSemanticCapsAndDefaultsSucceeded( const std::string& printer_name, const printing::PrinterSemanticCapsAndDefaults& caps_and_defaults) { DCHECK(waiting_for_reply_); ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_SUCCEEDED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilitySemanticCapsTime", base::Time::Now() - start_time_); waiting_for_reply_ = false; client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind(&Client::OnGetPrinterSemanticCapsAndDefaults, client_.get(), true, printer_name, caps_and_defaults)); } void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsFailed( const std::string& printer_name) { DCHECK(waiting_for_reply_); ReportUmaEvent(SERVICE_UTILITY_CAPS_FAILED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsFailTime", base::Time::Now() - start_time_); waiting_for_reply_ = false; client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind(&Client::OnGetPrinterCapsAndDefaults, client_.get(), false, printer_name, printing::PrinterCapsAndDefaults())); } void ServiceUtilityProcessHost::OnGetPrinterSemanticCapsAndDefaultsFailed( const std::string& printer_name) { DCHECK(waiting_for_reply_); ReportUmaEvent(SERVICE_UTILITY_SEMANTIC_CAPS_FAILED); UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilitySemanticCapsFailTime", base::Time::Now() - start_time_); waiting_for_reply_ = false; client_message_loop_proxy_->PostTask( FROM_HERE, base::Bind(&Client::OnGetPrinterSemanticCapsAndDefaults, client_.get(), false, printer_name, printing::PrinterSemanticCapsAndDefaults())); } bool ServiceUtilityProcessHost::Client::MetafileAvailable(float scale_factor, base::File file) { file.Seek(base::File::FROM_BEGIN, 0); int64 size = file.GetLength(); if (size <= 0) { OnRenderPDFPagesToMetafileDone(false); return false; } std::vector data(size); if (file.ReadAtCurrentPos(data.data(), data.size()) != size) { OnRenderPDFPagesToMetafileDone(false); return false; } printing::Emf emf; if (!emf.InitFromData(data.data(), data.size())) { OnRenderPDFPagesToMetafileDone(false); return false; } OnRenderPDFPagesToMetafilePageDone(scale_factor, emf); return true; }