1 // Copyright 2013 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/browser/nacl_host/pnacl_host.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/file_util.h" 10 #include "base/files/file_path.h" 11 #include "base/logging.h" 12 #include "base/task_runner_util.h" 13 #include "base/threading/sequenced_worker_pool.h" 14 #include "chrome/browser/nacl_host/nacl_browser.h" 15 #include "chrome/browser/nacl_host/pnacl_translation_cache.h" 16 #include "content/public/browser/browser_thread.h" 17 #include "net/base/io_buffer.h" 18 #include "net/base/net_errors.h" 19 20 using content::BrowserThread; 21 22 namespace { 23 static const base::FilePath::CharType kTranslationCacheDirectoryName[] = 24 FILE_PATH_LITERAL("PnaclTranslationCache"); 25 // Delay to wait for initialization of the cache backend 26 static const int kTranslationCacheInitializationDelayMs = 20; 27 } 28 29 PnaclHost::PnaclHost() 30 : cache_state_(CacheUninitialized), weak_factory_(this) {} 31 32 PnaclHost::~PnaclHost() {} 33 34 PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); } 35 36 PnaclHost::PendingTranslation::PendingTranslation() 37 : process_handle(base::kNullProcessHandle), 38 render_view_id(0), 39 nexe_fd(base::kInvalidPlatformFileValue), 40 got_nexe_fd(false), 41 got_cache_reply(false), 42 got_cache_hit(false), 43 is_incognito(false), 44 callback(NexeFdCallback()), 45 cache_info(nacl::PnaclCacheInfo()) {} 46 PnaclHost::PendingTranslation::~PendingTranslation() {} 47 48 /////////////////////////////////////// Initialization 49 50 static base::FilePath GetCachePath() { 51 NaClBrowserDelegate* browser_delegate = NaClBrowser::GetDelegate(); 52 // Determine where the translation cache resides in the file system. It 53 // exists in Chrome's cache directory and is not tied to any specific 54 // profile. If we fail, return an empty path. 55 // Start by finding the user data directory. 56 base::FilePath user_data_dir; 57 if (!browser_delegate || 58 !browser_delegate->GetUserDirectory(&user_data_dir)) { 59 return base::FilePath(); 60 } 61 // The cache directory may or may not be the user data directory. 62 base::FilePath cache_file_path; 63 browser_delegate->GetCacheDirectory(&cache_file_path); 64 65 // Append the base file name to the cache directory. 66 return cache_file_path.Append(kTranslationCacheDirectoryName); 67 } 68 69 void PnaclHost::OnCacheInitialized(int net_error) { 70 DCHECK(thread_checker_.CalledOnValidThread()); 71 // If the cache was cleared before the load completed, ignore. 72 if (cache_state_ == CacheReady) 73 return; 74 if (net_error != net::OK) { 75 // This will cause the cache to attempt to re-init on the next call to 76 // GetNexeFd. 77 cache_state_ = CacheUninitialized; 78 } else { 79 cache_state_ = CacheReady; 80 } 81 } 82 83 void PnaclHost::Init() { 84 // Extra check that we're on the real IO thread since this version of 85 // Init isn't used in unit tests. 86 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 87 DCHECK(thread_checker_.CalledOnValidThread()); 88 base::FilePath cache_path(GetCachePath()); 89 if (cache_path.empty() || cache_state_ != CacheUninitialized) 90 return; 91 disk_cache_.reset(new pnacl::PnaclTranslationCache()); 92 cache_state_ = CacheInitializing; 93 disk_cache_->InitCache( 94 cache_path, 95 false, 96 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); 97 } 98 99 // Initialize using the in-memory backend, and manually set the temporary file 100 // directory instead of using the system directory. 101 void PnaclHost::InitForTest(base::FilePath temp_dir) { 102 DCHECK(thread_checker_.CalledOnValidThread()); 103 disk_cache_.reset(new pnacl::PnaclTranslationCache()); 104 cache_state_ = CacheInitializing; 105 temp_dir_ = temp_dir; 106 disk_cache_->InitCache( 107 temp_dir, 108 true, // Use in-memory backend 109 base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); 110 } 111 112 ///////////////////////////////////////// Temp files 113 114 // Create a temporary file on the blocking pool 115 // static 116 base::PlatformFile PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir) { 117 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 118 119 base::FilePath file_path; 120 bool rv = temp_dir.empty() 121 ? file_util::CreateTemporaryFile(&file_path) 122 : file_util::CreateTemporaryFileInDir(temp_dir, &file_path); 123 124 if (!rv) { 125 LOG(ERROR) << "PnaclHost:: Temp file creation failed."; 126 return base::kInvalidPlatformFileValue; 127 } 128 base::PlatformFileError error; 129 base::PlatformFile file_handle(base::CreatePlatformFile( 130 file_path, 131 base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ | 132 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY | 133 base::PLATFORM_FILE_DELETE_ON_CLOSE, 134 NULL, 135 &error)); 136 137 if (error != base::PLATFORM_FILE_OK) { 138 LOG(ERROR) << "PnaclHost: Temp file open failed: " << error; 139 return base::kInvalidPlatformFileValue; 140 } 141 142 return file_handle; 143 } 144 145 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) { 146 if (!base::PostTaskAndReplyWithResult( 147 BrowserThread::GetBlockingPool(), 148 FROM_HERE, 149 base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_), 150 cb)) { 151 DCHECK(thread_checker_.CalledOnValidThread()); 152 cb.Run(base::kInvalidPlatformFileValue); 153 } 154 } 155 156 ///////////////////////////////////////// GetNexeFd implementation 157 ////////////////////// Common steps 158 159 void PnaclHost::GetNexeFd(int render_process_id, 160 int render_view_id, 161 int pp_instance, 162 bool is_incognito, 163 const nacl::PnaclCacheInfo& cache_info, 164 const NexeFdCallback& cb) { 165 DCHECK(thread_checker_.CalledOnValidThread()); 166 if (cache_state_ == CacheUninitialized) { 167 Init(); 168 } 169 if (cache_state_ != CacheReady) { 170 // If the backend hasn't yet initialized, try the request again later. 171 BrowserThread::PostDelayedTask(BrowserThread::IO, 172 FROM_HERE, 173 base::Bind(&PnaclHost::GetNexeFd, 174 weak_factory_.GetWeakPtr(), 175 render_process_id, 176 render_view_id, 177 pp_instance, 178 is_incognito, 179 cache_info, 180 cb), 181 base::TimeDelta::FromMilliseconds( 182 kTranslationCacheInitializationDelayMs)); 183 return; 184 } 185 186 TranslationID id(render_process_id, pp_instance); 187 PendingTranslationMap::iterator entry = pending_translations_.find(id); 188 if (entry != pending_translations_.end()) { 189 // Existing translation must have been abandonded. Clean it up. 190 LOG(ERROR) << "PnaclHost::GetNexeFd for already-pending translation"; 191 pending_translations_.erase(entry); 192 } 193 194 std::string cache_key(disk_cache_->GetKey(cache_info)); 195 if (cache_key.empty()) { 196 LOG(ERROR) << "PnaclHost::GetNexeFd: Invalid cache info"; 197 cb.Run(base::kInvalidPlatformFileValue, false); 198 return; 199 } 200 201 PendingTranslation pt; 202 pt.render_view_id = render_view_id; 203 pt.callback = cb; 204 pt.cache_info = cache_info; 205 pt.cache_key = cache_key; 206 pt.is_incognito = is_incognito; 207 pending_translations_[id] = pt; 208 SendCacheQueryAndTempFileRequest(cache_key, id); 209 } 210 211 // Dispatch the cache read request and the temp file creation request 212 // simultaneously; currently we need a temp file regardless of whether the 213 // request hits. 214 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key, 215 const TranslationID& id) { 216 disk_cache_->GetNexe( 217 cache_key, 218 base::Bind( 219 &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id)); 220 221 CreateTemporaryFile( 222 base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id)); 223 } 224 225 // Callback from the translation cache query. |id| is bound from 226 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for 227 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated 228 // by PnaclTranslationCache and now belongs to PnaclHost. 229 // (Bound callbacks must re-lookup the TranslationID because the translation 230 // could be cancelled before they get called). 231 void PnaclHost::OnCacheQueryReturn( 232 const TranslationID& id, 233 int net_error, 234 scoped_refptr<net::DrainableIOBuffer> buffer) { 235 DCHECK(thread_checker_.CalledOnValidThread()); 236 PendingTranslationMap::iterator entry(pending_translations_.find(id)); 237 if (entry == pending_translations_.end()) { 238 LOG(ERROR) << "PnaclHost::OnCacheQueryReturn: id not found"; 239 return; 240 } 241 PendingTranslation* pt = &entry->second; 242 pt->got_cache_reply = true; 243 pt->got_cache_hit = (net_error == net::OK); 244 if (pt->got_cache_hit) 245 pt->nexe_read_buffer = buffer; 246 CheckCacheQueryReady(entry); 247 } 248 249 // Callback from temp file creation. |id| is bound from 250 // SendCacheQueryAndTempFileRequest, and fd is the created file descriptor. 251 // If there was an error, fd is kInvalidPlatformFileValue. 252 // (Bound callbacks must re-lookup the TranslationID because the translation 253 // could be cancelled before they get called). 254 void PnaclHost::OnTempFileReturn(const TranslationID& id, 255 base::PlatformFile fd) { 256 DCHECK(thread_checker_.CalledOnValidThread()); 257 PendingTranslationMap::iterator entry(pending_translations_.find(id)); 258 if (entry == pending_translations_.end()) { 259 // The renderer may have signaled an error or closed while the temp 260 // file was being created. 261 LOG(ERROR) << "PnaclHost::OnTempFileReturn: id not found"; 262 BrowserThread::PostBlockingPoolTask( 263 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); 264 return; 265 } 266 if (fd == base::kInvalidPlatformFileValue) { 267 // This translation will fail, but we need to retry any translation 268 // waiting for its result. 269 LOG(ERROR) << "PnaclHost::OnTempFileReturn: temp file creation failed"; 270 std::string key(entry->second.cache_key); 271 bool is_incognito = entry->second.is_incognito; 272 entry->second.callback.Run(fd, false); 273 pending_translations_.erase(entry); 274 // No translations will be waiting for an incongnito translation 275 if (!is_incognito) 276 RequeryMatchingTranslations(key); 277 return; 278 } 279 PendingTranslation* pt = &entry->second; 280 pt->got_nexe_fd = true; 281 pt->nexe_fd = fd; 282 CheckCacheQueryReady(entry); 283 } 284 285 // Check whether both the cache query and the temp file have returned, and check 286 // whether we actually got a hit or not. 287 void PnaclHost::CheckCacheQueryReady( 288 const PendingTranslationMap::iterator& entry) { 289 PendingTranslation* pt = &entry->second; 290 if (!(pt->got_cache_reply && pt->got_nexe_fd)) 291 return; 292 if (!pt->got_cache_hit) { 293 // Check if there is already a pending translation for this file. If there 294 // is, we will wait for it to come back, to avoid redundant translations. 295 for (PendingTranslationMap::iterator it = pending_translations_.begin(); 296 it != pending_translations_.end(); 297 ++it) { 298 // Another translation matches if it's a request for the same file, 299 if (it->second.cache_key == entry->second.cache_key && 300 // and it's not this translation, 301 it->first != entry->first && 302 // and it's not incognito, 303 !it->second.is_incognito && 304 // and if it's already gotten past this check and returned the miss. 305 it->second.got_cache_reply && 306 it->second.got_nexe_fd) { 307 return; 308 } 309 } 310 ReturnMiss(entry); 311 return; 312 } 313 314 if (!base::PostTaskAndReplyWithResult( 315 BrowserThread::GetBlockingPool(), 316 FROM_HERE, 317 base::Bind( 318 &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer), 319 base::Bind(&PnaclHost::OnBufferCopiedToTempFile, 320 weak_factory_.GetWeakPtr(), 321 entry->first))) { 322 pt->callback.Run(base::kInvalidPlatformFileValue, false); 323 } 324 } 325 326 //////////////////// GetNexeFd miss path 327 // Return the temp fd to the renderer, reporting a miss. 328 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) { 329 // Return the fd 330 PendingTranslation* pt = &entry->second; 331 NexeFdCallback cb(pt->callback); 332 if (pt->nexe_fd == base::kInvalidPlatformFileValue) { 333 // Bad FD is unrecoverable, so clear out the entry 334 pending_translations_.erase(entry); 335 } 336 cb.Run(pt->nexe_fd, false); 337 } 338 339 // On error, just return a null refptr. 340 // static 341 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer( 342 base::PlatformFile fd) { 343 base::PlatformFileInfo info; 344 scoped_refptr<net::DrainableIOBuffer> buffer; 345 bool error = false; 346 if (!base::GetPlatformFileInfo(fd, &info) || 347 info.size >= std::numeric_limits<int>::max()) { 348 LOG(ERROR) << "PnaclHost: GetPlatformFileInfo failed"; 349 error = true; 350 } else { 351 buffer = new net::DrainableIOBuffer( 352 new net::IOBuffer(static_cast<int>(info.size)), info.size); 353 if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) != 354 info.size) { 355 LOG(ERROR) << "PnaclHost: CopyFileToBuffer write failed"; 356 error = true; 357 } 358 } 359 if (error) { 360 buffer = NULL; 361 } 362 base::ClosePlatformFile(fd); 363 return buffer; 364 } 365 366 // Called by the renderer in the miss path to report a finished translation 367 void PnaclHost::TranslationFinished(int render_process_id, 368 int pp_instance, 369 bool success) { 370 DCHECK(thread_checker_.CalledOnValidThread()); 371 if (cache_state_ != CacheReady) 372 return; 373 TranslationID id(render_process_id, pp_instance); 374 PendingTranslationMap::iterator entry(pending_translations_.find(id)); 375 if (entry == pending_translations_.end()) { 376 LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id 377 << "," << pp_instance << " not found."; 378 return; 379 } 380 bool store_nexe = true; 381 // If this is a premature response (i.e. we haven't returned a temp file 382 // yet) or if it's an unsuccessful translation, or if we are incognito, 383 // don't store in the cache. 384 // TODO(dschuff): use a separate in-memory cache for incognito 385 // translations. 386 if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply || 387 !success || entry->second.is_incognito) { 388 store_nexe = false; 389 } else if (!base::PostTaskAndReplyWithResult( 390 BrowserThread::GetBlockingPool(), 391 FROM_HERE, 392 base::Bind(&PnaclHost::CopyFileToBuffer, 393 entry->second.nexe_fd), 394 base::Bind(&PnaclHost::StoreTranslatedNexe, 395 weak_factory_.GetWeakPtr(), 396 id))) { 397 store_nexe = false; 398 } 399 400 if (!store_nexe) { 401 // If store_nexe is true, the fd will be closed by CopyFileToBuffer. 402 if (entry->second.got_nexe_fd) { 403 BrowserThread::PostBlockingPoolTask( 404 FROM_HERE, 405 base::Bind(base::IgnoreResult(base::ClosePlatformFile), 406 entry->second.nexe_fd)); 407 } 408 pending_translations_.erase(entry); 409 } 410 } 411 412 // Store the translated nexe in the translation cache. Called back with the 413 // TranslationID from the host and the result of CopyFileToBuffer. 414 // (Bound callbacks must re-lookup the TranslationID because the translation 415 // could be cancelled before they get called). 416 void PnaclHost::StoreTranslatedNexe( 417 TranslationID id, 418 scoped_refptr<net::DrainableIOBuffer> buffer) { 419 DCHECK(thread_checker_.CalledOnValidThread()); 420 if (cache_state_ != CacheReady) 421 return; 422 PendingTranslationMap::iterator it(pending_translations_.find(id)); 423 if (it == pending_translations_.end()) { 424 LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << "," 425 << id.second << " not found."; 426 return; 427 } 428 429 if (buffer.get() == NULL) { 430 LOG(ERROR) << "Error reading translated nexe"; 431 return; 432 } 433 434 disk_cache_->StoreNexe(it->second.cache_key, 435 buffer, 436 base::Bind(&PnaclHost::OnTranslatedNexeStored, 437 weak_factory_.GetWeakPtr(), 438 it->first)); 439 } 440 441 // After we know the nexe has been stored, we can clean up, and unblock any 442 // outstanding requests for the same file. 443 // (Bound callbacks must re-lookup the TranslationID because the translation 444 // could be cancelled before they get called). 445 void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) { 446 PendingTranslationMap::iterator entry(pending_translations_.find(id)); 447 if (entry == pending_translations_.end()) { 448 return; 449 } 450 std::string key(entry->second.cache_key); 451 pending_translations_.erase(entry); 452 RequeryMatchingTranslations(key); 453 } 454 455 // Check if any pending translations match |key|. If so, re-issue the cache 456 // query. In the overlapped miss case, we expect a hit this time, but a miss 457 // is also possible in case of an error. 458 void PnaclHost::RequeryMatchingTranslations(const std::string& key) { 459 // Check for outstanding misses to this same file 460 for (PendingTranslationMap::iterator it = pending_translations_.begin(); 461 it != pending_translations_.end(); 462 ++it) { 463 if (it->second.cache_key == key) { 464 // Re-send the cache read request. This time we expect a hit, but if 465 // something goes wrong, it will just handle it like a miss. 466 it->second.got_cache_reply = false; 467 disk_cache_->GetNexe(key, 468 base::Bind(&PnaclHost::OnCacheQueryReturn, 469 weak_factory_.GetWeakPtr(), 470 it->first)); 471 } 472 } 473 } 474 475 //////////////////// GetNexeFd hit path 476 477 // static 478 int PnaclHost::CopyBufferToFile(base::PlatformFile fd, 479 scoped_refptr<net::DrainableIOBuffer> buffer) { 480 return base::WritePlatformFile(fd, 0, buffer->data(), buffer->size()); 481 } 482 483 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id, 484 int file_error) { 485 DCHECK(thread_checker_.CalledOnValidThread()); 486 PendingTranslationMap::iterator entry(pending_translations_.find(id)); 487 if (entry == pending_translations_.end()) { 488 return; 489 } 490 if (file_error == -1) { 491 // Write error on the temp file. Request a new file and start over. 492 LOG(ERROR) << "PnaclHost::OnBufferCopiedToTempFile write error"; 493 BrowserThread::PostBlockingPoolTask( 494 FROM_HERE, 495 base::Bind(base::IgnoreResult(base::ClosePlatformFile), 496 entry->second.nexe_fd)); 497 entry->second.got_nexe_fd = false; 498 CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn, 499 weak_factory_.GetWeakPtr(), 500 entry->first)); 501 return; 502 } 503 base::PlatformFile fd = entry->second.nexe_fd; 504 entry->second.callback.Run(fd, true); 505 BrowserThread::PostBlockingPoolTask( 506 FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); 507 pending_translations_.erase(entry); 508 } 509 510 /////////////////// 511 512 void PnaclHost::RendererClosing(int render_process_id) { 513 DCHECK(thread_checker_.CalledOnValidThread()); 514 if (cache_state_ != CacheReady) 515 return; 516 for (PendingTranslationMap::iterator it = pending_translations_.begin(); 517 it != pending_translations_.end();) { 518 PendingTranslationMap::iterator to_erase(it++); 519 if (to_erase->first.first == render_process_id) { 520 // Clean up the open files. 521 BrowserThread::PostBlockingPoolTask( 522 FROM_HERE, 523 base::Bind(base::IgnoreResult(base::ClosePlatformFile), 524 to_erase->second.nexe_fd)); 525 std::string key(to_erase->second.cache_key); 526 bool is_incognito = to_erase->second.is_incognito; 527 pending_translations_.erase(to_erase); 528 // No translations will be blocked waiting for an incongnito translation 529 if (!is_incognito) 530 RequeryMatchingTranslations(key); 531 } 532 } 533 if (pending_translations_.empty()) { 534 cache_state_ = CacheUninitialized; 535 // Freeing the backend causes it to flush to disk, so do it when the 536 // last renderer closes rather than on destruction. 537 disk_cache_.reset(); 538 } 539 } 540 541 ////////////////// Cache data removal 542 void PnaclHost::ClearTranslationCacheEntriesBetween( 543 base::Time initial_time, 544 base::Time end_time, 545 const base::Closure& callback) { 546 DCHECK(thread_checker_.CalledOnValidThread()); 547 if (cache_state_ == CacheUninitialized) { 548 Init(); 549 } 550 if (cache_state_ == CacheInitializing) { 551 // If the backend hasn't yet initialized, try the request again later. 552 BrowserThread::PostDelayedTask( 553 BrowserThread::IO, 554 FROM_HERE, 555 base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween, 556 weak_factory_.GetWeakPtr(), 557 initial_time, 558 end_time, 559 callback), 560 base::TimeDelta::FromMilliseconds( 561 kTranslationCacheInitializationDelayMs)); 562 return; 563 } 564 int rv = disk_cache_->DoomEntriesBetween( 565 initial_time, 566 end_time, 567 base::Bind(&PnaclHost::OnEntriesDoomed, callback)); 568 if (rv != net::ERR_IO_PENDING) 569 OnEntriesDoomed(callback, rv); 570 } 571 572 // static 573 void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) { 574 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback); 575 } 576