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/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" 26 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" 35 36 namespace { 37 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) { 45 } 46 47 virtual void PreSandbox(bool* disable_default_policy, 48 base::FilePath* exposed_dir) OVERRIDE { 49 *exposed_dir = exposed_dir_; 50 } 51 52 private: 53 base::FilePath exposed_dir_; 54 }; 55 56 } // namespace 57 58 #endif // OS_WIN 59 60 using content::ChildProcessHost; 61 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, 73 }; 74 } // namespace 75 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)); 83 } 84 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); 88 } 89 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 (!base::CreateTemporaryFileInDir(scratch_metafile_dir_->path(), 108 &metafile_path_)) { 109 return false; 110 } 111 112 if (!StartProcess(false, scratch_metafile_dir_->path())) 113 return false; 114 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) 139 } 140 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)); 153 } 154 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; 161 162 base::FilePath exe_path = GetUtilityProcessCmd(); 163 if (exe_path.empty()) { 164 NOTREACHED() << "Unable to get utility process binary name."; 165 return false; 166 } 167 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); 172 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; 178 } 179 return false; 180 } 181 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) 190 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); 198 } 199 return (handle_ != base::kNullProcessHandle); 200 #endif // !defined(OS_WIN) 201 } 202 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); 210 } 211 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_); 223 } 224 delete this; 225 } 226 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; 243 } 244 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)); 263 } 264 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())); 276 } 277 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)); 292 } 293 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)); 307 } 308 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); 329 } 330 #endif // defined(OS_WIN) 331 } 332 333