// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/nacl_host/pnacl_host.h" #include "base/bind.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/task_runner_util.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/nacl_host/nacl_browser.h" #include "chrome/browser/nacl_host/pnacl_translation_cache.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_errors.h" using content::BrowserThread; namespace { static const base::FilePath::CharType kTranslationCacheDirectoryName[] = FILE_PATH_LITERAL("PnaclTranslationCache"); } PnaclHost::PnaclHost() : cache_state_(CacheUninitialized), weak_factory_(this) {} PnaclHost::~PnaclHost() {} PnaclHost* PnaclHost::GetInstance() { return Singleton::get(); } PnaclHost::PendingTranslation::PendingTranslation() {} PnaclHost::PendingTranslation::~PendingTranslation() {} /////////////////////////////////////// Initialization static base::FilePath GetCachePath() { NaClBrowserDelegate* browser_delegate = NaClBrowser::GetDelegate(); // Determine where the translation cache resides in the file system. It // exists in Chrome's cache directory and is not tied to any specific // profile. If we fail, return an empty path. // Start by finding the user data directory. base::FilePath user_data_dir; if (!browser_delegate || !browser_delegate->GetUserDirectory(&user_data_dir)) { return base::FilePath(); } // The cache directory may or may not be the user data directory. base::FilePath cache_file_path; browser_delegate->GetCacheDirectory(&cache_file_path); // Append the base file name to the cache directory. return cache_file_path.Append(kTranslationCacheDirectoryName); } void PnaclHost::OnCacheInitialized(int error) { // If the cache was cleared before the load completed, ignore. if (cache_state_ == CacheReady) return; if (error != net::OK) { LOG(ERROR) << "PNaCl translation cache initalization failure: " << error << "\n"; } else { cache_state_ = CacheReady; } } void PnaclHost::Init() { DCHECK(thread_checker_.CalledOnValidThread()); base::FilePath cache_path(GetCachePath()); if (cache_path.empty() || cache_state_ != CacheUninitialized) return; disk_cache_.reset(new pnacl::PnaclTranslationCache()); cache_state_ = CacheInitializing; disk_cache_->InitCache( cache_path, true, base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); } // Initialize using the in-memory backend, and manually set the temporary file // directory instead of using the system directory. void PnaclHost::InitForTest(base::FilePath temp_dir) { DCHECK(thread_checker_.CalledOnValidThread()); disk_cache_.reset(new pnacl::PnaclTranslationCache()); cache_state_ = CacheInitializing; temp_dir_ = temp_dir; disk_cache_->InitCache( temp_dir, true, // Use in-memory backend base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); } ///////////////////////////////////////// Temp files // Create a temporary file on the blocking pool IPC::PlatformFileForTransit PnaclHost::DoCreateTemporaryFile( base::ProcessHandle process_handle, base::FilePath temp_dir) { DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); base::FilePath file_path; bool rv = temp_dir.empty() ? file_util::CreateTemporaryFile(&file_path) : file_util::CreateTemporaryFileInDir(temp_dir, &file_path); if (!rv) return IPC::InvalidPlatformFileForTransit(); base::PlatformFileError error; base::PlatformFile file_handle(base::CreatePlatformFile( file_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ | base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY | base::PLATFORM_FILE_DELETE_ON_CLOSE, NULL, &error)); if (error != base::PLATFORM_FILE_OK) return IPC::InvalidPlatformFileForTransit(); // Do any DuplicateHandle magic that is necessary first. return IPC::GetFileHandleForProcess(file_handle, process_handle, true); } void PnaclHost::CreateTemporaryFile(base::ProcessHandle process_handle, TempFileCallback cb) { if (!base::PostTaskAndReplyWithResult( BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind( &PnaclHost::DoCreateTemporaryFile, process_handle, temp_dir_), cb)) { DCHECK(thread_checker_.CalledOnValidThread()); cb.Run(IPC::InvalidPlatformFileForTransit()); } } ///////////////////////////////////////// GetNexeFd implementation void PnaclHost::ReturnMiss(TranslationID id, IPC::PlatformFileForTransit fd) { DCHECK(thread_checker_.CalledOnValidThread()); PendingTranslationMap::iterator entry(pending_translations_.find(id)); if (entry == pending_translations_.end()) { LOG(ERROR) << "PnaclHost::ReturnMiss: Failed to find TranslationID " << id.first << "," << id.second; return; } NexeFdCallback cb(entry->second.callback); if (fd == IPC::InvalidPlatformFileForTransit()) { pending_translations_.erase(entry); } else { entry->second.nexe_fd = fd; } cb.Run(fd, false); } void PnaclHost::GetNexeFd(int render_process_id, base::ProcessHandle process_handle, int render_view_id, int pp_instance, const nacl::PnaclCacheInfo& cache_info, const NexeFdCallback& cb) { DCHECK(thread_checker_.CalledOnValidThread()); if (cache_state_ != CacheReady) return; PendingTranslation pt; pt.render_view_id = render_view_id; pt.callback = cb; pt.cache_info = cache_info; pending_translations_[TranslationID(render_process_id, pp_instance)] = pt; CreateTemporaryFile( process_handle, base::Bind(&PnaclHost::ReturnMiss, weak_factory_.GetWeakPtr(), TranslationID(render_process_id, pp_instance))); } /////////////////// Cleanup void PnaclHost::TranslationFinished(int render_process_id, int pp_instance) { DCHECK(thread_checker_.CalledOnValidThread()); if (cache_state_ != CacheReady) return; PendingTranslationMap::iterator it(pending_translations_.find( TranslationID(render_process_id, pp_instance))); if (it == pending_translations_.end()) { LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id << "," << pp_instance << " not found."; } else { pending_translations_.erase(it); } } void PnaclHost::RendererClosing(int render_process_id) { if (cache_state_ != CacheReady) return; DCHECK(thread_checker_.CalledOnValidThread()); for (PendingTranslationMap::iterator it = pending_translations_.begin(); it != pending_translations_.end();) { PendingTranslationMap::iterator to_erase(it++); if (to_erase->first.first == render_process_id) { // clean up the open files pending_translations_.erase(to_erase); } } }