// 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 "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/metrics/histogram.h"
#include "base/process/kill.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_utility_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 "ipc/ipc_switches.h"
#include "printing/page_range.h"
#include "ui/base/ui_base_switches.h"
#include "ui/gfx/rect.h"
#if defined(OS_WIN)
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/launch.h"
#include "base/win/scoped_handle.h"
#include "content/public/common/sandbox_init.h"
#include "content/public/common/sandboxed_process_launcher_delegate.h"
#include "printing/emf_win.h"
namespace {
// NOTE: changes to this class need to be reviewed by the security team.
class ServiceSandboxedProcessLauncherDelegate
: public content::SandboxedProcessLauncherDelegate {
public:
explicit ServiceSandboxedProcessLauncherDelegate(
const base::FilePath& exposed_dir)
: exposed_dir_(exposed_dir) {
}
virtual void PreSandbox(bool* disable_default_policy,
base::FilePath* exposed_dir) OVERRIDE {
*exposed_dir = exposed_dir_;
}
private:
base::FilePath exposed_dir_;
};
} // namespace
#endif // OS_WIN
using content::ChildProcessHost;
namespace {
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_EVENT_MAX,
};
} // namespace
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) {
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,
const std::vector<printing::PageRange>& page_ranges) {
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_METAFILE_REQUEST,
SERVICE_UTILITY_EVENT_MAX);
start_time_ = base::Time::Now();
#if !defined(OS_WIN)
// This is only implemented on Windows (because currently it is only needed
// on Windows). Will add implementations on other platforms when needed.
NOTIMPLEMENTED();
return false;
#else // !defined(OS_WIN)
scratch_metafile_dir_.reset(new base::ScopedTempDir);
if (!scratch_metafile_dir_->CreateUniqueTempDir())
return false;
if (!base::CreateTemporaryFileInDir(scratch_metafile_dir_->path(),
&metafile_path_)) {
return false;
}
if (!StartProcess(false, scratch_metafile_dir_->path()))
return false;
base::win::ScopedHandle pdf_file(
::CreateFile(pdf_path.value().c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL));
if (pdf_file == INVALID_HANDLE_VALUE)
return false;
HANDLE pdf_file_in_utility_process = NULL;
::DuplicateHandle(::GetCurrentProcess(), pdf_file, handle(),
&pdf_file_in_utility_process, 0, false,
DUPLICATE_SAME_ACCESS);
if (!pdf_file_in_utility_process)
return false;
waiting_for_reply_ = true;
return child_process_host_->Send(
new ChromeUtilityMsg_RenderPDFPagesToMetafile(
pdf_file_in_utility_process,
metafile_path_,
render_settings,
page_ranges));
#endif // !defined(OS_WIN)
}
bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults(
const std::string& printer_name) {
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_CAPS_REQUEST,
SERVICE_UTILITY_EVENT_MAX);
start_time_ = base::Time::Now();
base::FilePath exposed_path;
if (!StartProcess(true, exposed_path))
return false;
waiting_for_reply_ = true;
return child_process_host_->Send(
new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name));
}
bool ServiceUtilityProcessHost::StartProcess(
bool no_sandbox,
const base::FilePath& exposed_dir) {
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;
}
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, exposed_dir)) {
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_STARTED,
SERVICE_UTILITY_EVENT_MAX);
return true;
}
return false;
}
bool ServiceUtilityProcessHost::Launch(CommandLine* cmd_line,
bool no_sandbox,
const base::FilePath& exposed_dir) {
#if !defined(OS_WIN)
// TODO(sanjeevr): Implement for non-Windows OSes.
NOTIMPLEMENTED();
return false;
#else // !defined(OS_WIN)
if (no_sandbox) {
base::ProcessHandle process = base::kNullProcessHandle;
cmd_line->AppendSwitch(switches::kNoSandbox);
base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_);
} else {
ServiceSandboxedProcessLauncherDelegate delegate(exposed_dir);
handle_ = content::StartSandboxedProcess(&delegate, cmd_line);
}
return (handle_ != base::kNullProcessHandle);
#endif // !defined(OS_WIN)
}
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()));
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_DISCONNECTED,
SERVICE_UTILITY_EVENT_MAX);
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_RenderPDFPagesToMetafile_Succeeded,
OnRenderPDFPagesToMetafileSucceeded)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed,
OnRenderPDFPagesToMetafileFailed)
IPC_MESSAGE_HANDLER(
ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded,
OnGetPrinterCapsAndDefaultsSucceeded)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed,
OnGetPrinterCapsAndDefaultsFailed)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileSucceeded(
int highest_rendered_page_number,
double scale_factor) {
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_METAFILE_SUCCEEDED,
SERVICE_UTILITY_EVENT_MAX);
UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileTime",
base::Time::Now() - start_time_);
DCHECK(waiting_for_reply_);
waiting_for_reply_ = false;
// If the metafile was successfully created, we need to take our hands off the
// scratch metafile directory. The client will delete it when it is done with
// metafile.
scratch_metafile_dir_->Take();
client_message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&Client::MetafileAvailable, client_.get(), metafile_path_,
highest_rendered_page_number, scale_factor));
}
void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileFailed() {
DCHECK(waiting_for_reply_);
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_METAFILE_FAILED,
SERVICE_UTILITY_EVENT_MAX);
UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityMetafileFailTime",
base::Time::Now() - start_time_);
waiting_for_reply_ = false;
client_message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&Client::OnRenderPDFPagesToMetafileFailed, client_.get()));
}
void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded(
const std::string& printer_name,
const printing::PrinterCapsAndDefaults& caps_and_defaults) {
DCHECK(waiting_for_reply_);
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_CAPS_SUCCEEDED,
SERVICE_UTILITY_EVENT_MAX);
UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsTime",
base::Time::Now() - start_time_);
waiting_for_reply_ = false;
client_message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&Client::OnGetPrinterCapsAndDefaultsSucceeded, client_.get(),
printer_name, caps_and_defaults));
}
void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsFailed(
const std::string& printer_name) {
DCHECK(waiting_for_reply_);
UMA_HISTOGRAM_ENUMERATION("CloudPrint.ServiceUtilityProcessHostEvent",
SERVICE_UTILITY_CAPS_FAILED,
SERVICE_UTILITY_EVENT_MAX);
UMA_HISTOGRAM_TIMES("CloudPrint.ServiceUtilityCapsFailTime",
base::Time::Now() - start_time_);
waiting_for_reply_ = false;
client_message_loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&Client::OnGetPrinterCapsAndDefaultsFailed, client_.get(),
printer_name));
}
void ServiceUtilityProcessHost::Client::MetafileAvailable(
const base::FilePath& metafile_path,
int highest_rendered_page_number,
double scale_factor) {
// The metafile was created in a temp folder which needs to get deleted after
// we have processed it.
base::ScopedTempDir scratch_metafile_dir;
if (!scratch_metafile_dir.Set(metafile_path.DirName()))
LOG(WARNING) << "Unable to set scratch metafile directory";
#if defined(OS_WIN)
// It's important that metafile is declared after scratch_metafile_dir so
// that the metafile destructor closes the file before the base::ScopedTempDir
// destructor tries to remove the directory.
printing::Emf metafile;
if (!metafile.InitFromFile(metafile_path)) {
OnRenderPDFPagesToMetafileFailed();
} else {
OnRenderPDFPagesToMetafileSucceeded(metafile,
highest_rendered_page_number,
scale_factor);
}
#endif // defined(OS_WIN)
}