// 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 <queue>
#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/kill.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() {}
virtual 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<base::File> emf_files_;
int page_count_;
int current_page_;
int pages_in_progress_;
};
ServiceUtilityProcessHost::ServiceUtilityProcessHost(
Client* client,
base::MessageLoopProxy* client_message_loop_proxy)
: handle_(base::kNullProcessHandle),
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.
base::KillProcess(handle_, 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) {
base::ProcessHandle process = base::kNullProcessHandle;
cmd_line->AppendSwitch(switches::kNoSandbox);
base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_);
} else {
ServiceSandboxedProcessLauncherDelegate delegate;
handle_ = content::StartSandboxedProcess(&delegate, cmd_line);
}
return (handle_ != base::kNullProcessHandle);
}
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;
}
base::ProcessHandle ServiceUtilityProcessHost::GetHandle() const {
return handle_;
}
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,
double 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_,
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(double 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<char> 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;
}