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"
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"
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"
38 // NOTE: changes to this class need to be reviewed by the security team.
39 class ServiceSandboxedProcessLauncherDelegate
40 : public content::SandboxedProcessLauncherDelegate
{
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_
;
53 base::FilePath exposed_dir_
;
60 using content::ChildProcessHost
;
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
,
76 ServiceUtilityProcessHost::ServiceUtilityProcessHost(
77 Client
* client
, base::MessageLoopProxy
* client_message_loop_proxy
)
78 : handle_(base::kNullProcessHandle
),
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();
99 // This is only implemented on Windows (because currently it is only needed
100 // on Windows). Will add implementations on other platforms when needed.
103 #else // !defined(OS_WIN)
104 scratch_metafile_dir_
.reset(new base::ScopedTempDir
);
105 if (!scratch_metafile_dir_
->CreateUniqueTempDir())
107 if (!base::CreateTemporaryFileInDir(scratch_metafile_dir_
->path(),
112 if (!StartProcess(false, scratch_metafile_dir_
->path()))
115 base::win::ScopedHandle
pdf_file(
116 ::CreateFile(pdf_path
.value().c_str(),
118 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
121 FILE_ATTRIBUTE_NORMAL
,
123 if (pdf_file
== INVALID_HANDLE_VALUE
)
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
)
131 waiting_for_reply_
= true;
132 return child_process_host_
->Send(
133 new ChromeUtilityMsg_RenderPDFPagesToMetafile(
134 pdf_file_in_utility_process
,
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
))
150 waiting_for_reply_
= true;
151 return child_process_host_
->Send(
152 new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name
));
155 bool ServiceUtilityProcessHost::StartProcess(
157 const base::FilePath
& exposed_dir
) {
158 std::string channel_id
= child_process_host_
->CreateChannel();
159 if (channel_id
.empty())
162 base::FilePath exe_path
= GetUtilityProcessCmd();
163 if (exe_path
.empty()) {
164 NOTREACHED() << "Unable to get utility process binary name.";
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
);
182 bool ServiceUtilityProcessHost::Launch(CommandLine
* cmd_line
,
184 const base::FilePath
& exposed_dir
) {
186 // TODO(sanjeevr): Implement for non-Windows OSes.
189 #else // !defined(OS_WIN)
192 base::ProcessHandle process
= base::kNullProcessHandle
;
193 cmd_line
->AppendSwitch(switches::kNoSandbox
);
194 base::LaunchProcess(*cmd_line
, base::LaunchOptions(), &handle_
);
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
;
207 int flags
= ChildProcessHost::CHILD_NORMAL
;
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
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_
);
227 bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message
& message
) {
229 IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost
, message
)
231 ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Succeeded
,
232 OnRenderPDFPagesToMetafileSucceeded
)
233 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed
,
234 OnRenderPDFPagesToMetafileFailed
)
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()
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
258 scratch_metafile_dir_
->Take();
259 client_message_loop_proxy_
->PostTask(
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(
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(
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(
305 base::Bind(&Client::OnGetPrinterCapsAndDefaultsFailed
, client_
.get(),
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";
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();
326 OnRenderPDFPagesToMetafileSucceeded(metafile
,
327 highest_rendered_page_number
,
330 #endif // defined(OS_WIN)