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. 4 5#include "chrome/service/service_utility_process_host.h" 6 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/process/kill.h" 15#include "base/strings/utf_string_conversions.h" 16#include "chrome/common/chrome_switches.h" 17#include "chrome/common/chrome_utility_messages.h" 18#include "content/public/common/child_process_host.h" 19#include "content/public/common/result_codes.h" 20#include "content/public/common/sandbox_init.h" 21#include "ipc/ipc_switches.h" 22#include "printing/page_range.h" 23#include "ui/base/ui_base_switches.h" 24#include "ui/gfx/rect.h" 25 26#if defined(OS_WIN) 27#include "base/files/file_path.h" 28#include "base/memory/scoped_ptr.h" 29#include "base/process/launch.h" 30#include "base/win/scoped_handle.h" 31#include "content/public/common/sandbox_init.h" 32#include "content/public/common/sandboxed_process_launcher_delegate.h" 33#include "printing/emf_win.h" 34 35namespace { 36// NOTE: changes to this class need to be reviewed by the security team. 37class ServiceSandboxedProcessLauncherDelegate 38 : public content::SandboxedProcessLauncherDelegate { 39 public: 40 explicit ServiceSandboxedProcessLauncherDelegate( 41 const base::FilePath& exposed_dir) 42 : exposed_dir_(exposed_dir) { 43 } 44 45 virtual void PreSandbox(bool* disable_default_policy, 46 base::FilePath* exposed_dir) OVERRIDE { 47 *exposed_dir = exposed_dir_; 48 } 49 50 private: 51 base::FilePath exposed_dir_; 52}; 53} 54 55#endif // OS_WIN 56 57using content::ChildProcessHost; 58 59ServiceUtilityProcessHost::ServiceUtilityProcessHost( 60 Client* client, base::MessageLoopProxy* client_message_loop_proxy) 61 : handle_(base::kNullProcessHandle), 62 client_(client), 63 client_message_loop_proxy_(client_message_loop_proxy), 64 waiting_for_reply_(false) { 65 child_process_host_.reset(ChildProcessHost::Create(this)); 66} 67 68ServiceUtilityProcessHost::~ServiceUtilityProcessHost() { 69 // We need to kill the child process when the host dies. 70 base::KillProcess(handle_, content::RESULT_CODE_NORMAL_EXIT, false); 71} 72 73bool ServiceUtilityProcessHost::StartRenderPDFPagesToMetafile( 74 const base::FilePath& pdf_path, 75 const printing::PdfRenderSettings& render_settings, 76 const std::vector<printing::PageRange>& page_ranges) { 77#if !defined(OS_WIN) 78 // This is only implemented on Windows (because currently it is only needed 79 // on Windows). Will add implementations on other platforms when needed. 80 NOTIMPLEMENTED(); 81 return false; 82#else // !defined(OS_WIN) 83 scratch_metafile_dir_.reset(new base::ScopedTempDir); 84 if (!scratch_metafile_dir_->CreateUniqueTempDir()) 85 return false; 86 if (!file_util::CreateTemporaryFileInDir(scratch_metafile_dir_->path(), 87 &metafile_path_)) { 88 return false; 89 } 90 91 if (!StartProcess(false, scratch_metafile_dir_->path())) 92 return false; 93 94 base::win::ScopedHandle pdf_file( 95 ::CreateFile(pdf_path.value().c_str(), 96 GENERIC_READ, 97 FILE_SHARE_READ | FILE_SHARE_WRITE, 98 NULL, 99 OPEN_EXISTING, 100 FILE_ATTRIBUTE_NORMAL, 101 NULL)); 102 if (pdf_file == INVALID_HANDLE_VALUE) 103 return false; 104 HANDLE pdf_file_in_utility_process = NULL; 105 ::DuplicateHandle(::GetCurrentProcess(), pdf_file, handle(), 106 &pdf_file_in_utility_process, 0, false, 107 DUPLICATE_SAME_ACCESS); 108 if (!pdf_file_in_utility_process) 109 return false; 110 waiting_for_reply_ = true; 111 return child_process_host_->Send( 112 new ChromeUtilityMsg_RenderPDFPagesToMetafile( 113 pdf_file_in_utility_process, 114 metafile_path_, 115 render_settings, 116 page_ranges)); 117#endif // !defined(OS_WIN) 118} 119 120bool ServiceUtilityProcessHost::StartGetPrinterCapsAndDefaults( 121 const std::string& printer_name) { 122 base::FilePath exposed_path; 123 if (!StartProcess(true, exposed_path)) 124 return false; 125 waiting_for_reply_ = true; 126 return child_process_host_->Send( 127 new ChromeUtilityMsg_GetPrinterCapsAndDefaults(printer_name)); 128} 129 130bool ServiceUtilityProcessHost::StartProcess( 131 bool no_sandbox, 132 const base::FilePath& exposed_dir) { 133 std::string channel_id = child_process_host_->CreateChannel(); 134 if (channel_id.empty()) 135 return false; 136 137 base::FilePath exe_path = GetUtilityProcessCmd(); 138 if (exe_path.empty()) { 139 NOTREACHED() << "Unable to get utility process binary name."; 140 return false; 141 } 142 143 CommandLine cmd_line(exe_path); 144 cmd_line.AppendSwitchASCII(switches::kProcessType, switches::kUtilityProcess); 145 cmd_line.AppendSwitchASCII(switches::kProcessChannelID, channel_id); 146 cmd_line.AppendSwitch(switches::kLang); 147 148 return Launch(&cmd_line, no_sandbox, exposed_dir); 149} 150 151bool ServiceUtilityProcessHost::Launch(CommandLine* cmd_line, 152 bool no_sandbox, 153 const base::FilePath& exposed_dir) { 154#if !defined(OS_WIN) 155 // TODO(sanjeevr): Implement for non-Windows OSes. 156 NOTIMPLEMENTED(); 157 return false; 158#else // !defined(OS_WIN) 159 160 if (no_sandbox) { 161 base::ProcessHandle process = base::kNullProcessHandle; 162 cmd_line->AppendSwitch(switches::kNoSandbox); 163 base::LaunchProcess(*cmd_line, base::LaunchOptions(), &handle_); 164 } else { 165 ServiceSandboxedProcessLauncherDelegate delegate(exposed_dir); 166 handle_ = content::StartSandboxedProcess(&delegate, cmd_line); 167 } 168 return (handle_ != base::kNullProcessHandle); 169#endif // !defined(OS_WIN) 170} 171 172base::FilePath ServiceUtilityProcessHost::GetUtilityProcessCmd() { 173#if defined(OS_LINUX) 174 int flags = ChildProcessHost::CHILD_ALLOW_SELF; 175#else 176 int flags = ChildProcessHost::CHILD_NORMAL; 177#endif 178 return ChildProcessHost::GetChildPath(flags); 179} 180 181void ServiceUtilityProcessHost::OnChildDisconnected() { 182 if (waiting_for_reply_) { 183 // If we are yet to receive a reply then notify the client that the 184 // child died. 185 client_message_loop_proxy_->PostTask( 186 FROM_HERE, base::Bind(&Client::OnChildDied, client_.get())); 187 } 188 delete this; 189} 190 191bool ServiceUtilityProcessHost::OnMessageReceived(const IPC::Message& message) { 192 bool handled = true; 193 IPC_BEGIN_MESSAGE_MAP(ServiceUtilityProcessHost, message) 194 IPC_MESSAGE_HANDLER( 195 ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Succeeded, 196 OnRenderPDFPagesToMetafileSucceeded) 197 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafile_Failed, 198 OnRenderPDFPagesToMetafileFailed) 199 IPC_MESSAGE_HANDLER( 200 ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Succeeded, 201 OnGetPrinterCapsAndDefaultsSucceeded) 202 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetPrinterCapsAndDefaults_Failed, 203 OnGetPrinterCapsAndDefaultsFailed) 204 IPC_MESSAGE_UNHANDLED(handled = false) 205 IPC_END_MESSAGE_MAP() 206 return handled; 207} 208 209void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileSucceeded( 210 int highest_rendered_page_number, 211 double scale_factor) { 212 DCHECK(waiting_for_reply_); 213 waiting_for_reply_ = false; 214 // If the metafile was successfully created, we need to take our hands off the 215 // scratch metafile directory. The client will delete it when it is done with 216 // metafile. 217 scratch_metafile_dir_->Take(); 218 client_message_loop_proxy_->PostTask( 219 FROM_HERE, 220 base::Bind(&Client::MetafileAvailable, client_.get(), metafile_path_, 221 highest_rendered_page_number, scale_factor)); 222} 223 224void ServiceUtilityProcessHost::OnRenderPDFPagesToMetafileFailed() { 225 DCHECK(waiting_for_reply_); 226 waiting_for_reply_ = false; 227 client_message_loop_proxy_->PostTask( 228 FROM_HERE, 229 base::Bind(&Client::OnRenderPDFPagesToMetafileFailed, client_.get())); 230} 231 232void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsSucceeded( 233 const std::string& printer_name, 234 const printing::PrinterCapsAndDefaults& caps_and_defaults) { 235 DCHECK(waiting_for_reply_); 236 waiting_for_reply_ = false; 237 client_message_loop_proxy_->PostTask( 238 FROM_HERE, 239 base::Bind(&Client::OnGetPrinterCapsAndDefaultsSucceeded, client_.get(), 240 printer_name, caps_and_defaults)); 241} 242 243void ServiceUtilityProcessHost::OnGetPrinterCapsAndDefaultsFailed( 244 const std::string& printer_name) { 245 DCHECK(waiting_for_reply_); 246 waiting_for_reply_ = false; 247 client_message_loop_proxy_->PostTask( 248 FROM_HERE, 249 base::Bind(&Client::OnGetPrinterCapsAndDefaultsFailed, client_.get(), 250 printer_name)); 251} 252 253void ServiceUtilityProcessHost::Client::MetafileAvailable( 254 const base::FilePath& metafile_path, 255 int highest_rendered_page_number, 256 double scale_factor) { 257 // The metafile was created in a temp folder which needs to get deleted after 258 // we have processed it. 259 base::ScopedTempDir scratch_metafile_dir; 260 if (!scratch_metafile_dir.Set(metafile_path.DirName())) 261 LOG(WARNING) << "Unable to set scratch metafile directory"; 262#if defined(OS_WIN) 263 // It's important that metafile is declared after scratch_metafile_dir so 264 // that the metafile destructor closes the file before the base::ScopedTempDir 265 // destructor tries to remove the directory. 266 printing::Emf metafile; 267 if (!metafile.InitFromFile(metafile_path)) { 268 OnRenderPDFPagesToMetafileFailed(); 269 } else { 270 OnRenderPDFPagesToMetafileSucceeded(metafile, 271 highest_rendered_page_number, 272 scale_factor); 273 } 274#endif // defined(OS_WIN) 275} 276 277