UMA instrumentation for CloudPrint connector.
[chromium-blink-merge.git] / chrome / service / service_utility_process_host.cc
blobd3b0aa6e0231365d719b005bd9d96bf909390011
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.
5 #include "chrome/service/service_utility_process_host.h"
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"
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"
36 namespace {
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) {
47 virtual void PreSandbox(bool* disable_default_policy,
48 base::FilePath* exposed_dir) OVERRIDE {
49 *exposed_dir = exposed_dir_;
52 private:
53 base::FilePath exposed_dir_;
56 } // namespace
58 #endif // OS_WIN
60 using content::ChildProcessHost;
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,
74 } // namespace
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));
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);
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 (!file_util::CreateTemporaryFileInDir(scratch_metafile_dir_->path(),
108 &metafile_path_)) {
109 return false;
112 if (!StartProcess(false, scratch_metafile_dir_->path()))
113 return false;
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)
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));
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;
162 base::FilePath exe_path = GetUtilityProcessCmd();
163 if (exe_path.empty()) {
164 NOTREACHED() << "Unable to get utility process binary name.";
165 return false;
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);
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;
179 return false;
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)
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);
199 return (handle_ != base::kNullProcessHandle);
200 #endif // !defined(OS_WIN)
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);
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_);
224 delete this;
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;
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));
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()));
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));
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));
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);
330 #endif // defined(OS_WIN)