| // Copyright (c) 2012 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/nacl_browser.h" |
| |
| #include "base/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/path_service.h" |
| #include "base/pickle.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_paths_internal.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace { |
| |
| // An arbitrary delay to coalesce multiple writes to the cache. |
| const int kValidationCacheCoalescingTimeMS = 6000; |
| const char kValidationCacheSequenceName[] = "NaClValidationCache"; |
| const FilePath::CharType kValidationCacheFileName[] = |
| FILE_PATH_LITERAL("nacl_validation_cache.bin"); |
| |
| enum ValidationCacheStatus { |
| CACHE_MISS = 0, |
| CACHE_HIT, |
| CACHE_MAX |
| }; |
| |
| // Determine the name of the IRT file based on the architecture. |
| #define NACL_IRT_FILE_NAME(arch_string) \ |
| (FILE_PATH_LITERAL("nacl_irt_") \ |
| FILE_PATH_LITERAL(arch_string) \ |
| FILE_PATH_LITERAL(".nexe")) |
| |
| const FilePath::StringType NaClIrtName() { |
| #if defined(ARCH_CPU_X86_FAMILY) |
| #if defined(ARCH_CPU_X86_64) |
| bool is64 = true; |
| #elif defined(OS_WIN) |
| bool is64 = (base::win::OSInfo::GetInstance()->wow64_status() == |
| base::win::OSInfo::WOW64_ENABLED); |
| #else |
| bool is64 = false; |
| #endif |
| return is64 ? NACL_IRT_FILE_NAME("x86_64") : NACL_IRT_FILE_NAME("x86_32"); |
| #elif defined(ARCH_CPU_ARMEL) |
| // TODO(mcgrathr): Eventually we'll need to distinguish arm32 vs thumb2. |
| // That may need to be based on the actual nexe rather than a static |
| // choice, which would require substantial refactoring. |
| return NACL_IRT_FILE_NAME("arm"); |
| #else |
| #error Add support for your architecture to NaCl IRT file selection |
| #endif |
| } |
| |
| bool CheckEnvVar(const char* name, bool default_value) { |
| bool result = default_value; |
| const char* var = getenv(name); |
| if (var && strlen(var) > 0) { |
| result = var[0] != '0'; |
| } |
| return result; |
| } |
| |
| void ReadCache(const FilePath& filename, std::string* data) { |
| if (!file_util::ReadFileToString(filename, data)) { |
| // Zero-size data used as an in-band error code. |
| data->clear(); |
| } |
| } |
| |
| void WriteCache(const FilePath& filename, const Pickle* pickle) { |
| file_util::WriteFile(filename, static_cast<const char*>(pickle->data()), |
| pickle->size()); |
| } |
| |
| void LogCacheQuery(ValidationCacheStatus status) { |
| UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Query", status, CACHE_MAX); |
| } |
| |
| void LogCacheSet(ValidationCacheStatus status) { |
| // Bucket zero is reserved for future use. |
| UMA_HISTOGRAM_ENUMERATION("NaCl.ValidationCache.Set", status, CACHE_MAX); |
| } |
| |
| } // namespace |
| |
| NaClBrowser::NaClBrowser() |
| : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
| irt_platform_file_(base::kInvalidPlatformFileValue), |
| irt_filepath_(), |
| irt_state_(NaClResourceUninitialized), |
| validation_cache_file_path_(), |
| validation_cache_is_enabled_(CheckEnvVar("NACL_VALIDATION_CACHE", false)), |
| validation_cache_is_modified_(false), |
| validation_cache_state_(NaClResourceUninitialized), |
| ok_(true) { |
| InitIrtFilePath(); |
| } |
| |
| NaClBrowser::~NaClBrowser() { |
| if (irt_platform_file_ != base::kInvalidPlatformFileValue) |
| base::ClosePlatformFile(irt_platform_file_); |
| } |
| |
| void NaClBrowser::InitIrtFilePath() { |
| // Allow the IRT library to be overridden via an environment |
| // variable. This allows the NaCl/Chromium integration bot to |
| // specify a newly-built IRT rather than using a prebuilt one |
| // downloaded via Chromium's DEPS file. We use the same environment |
| // variable that the standalone NaCl PPAPI plugin accepts. |
| const char* irt_path_var = getenv("NACL_IRT_LIBRARY"); |
| if (irt_path_var != NULL) { |
| FilePath::StringType path_string( |
| irt_path_var, const_cast<const char*>(strchr(irt_path_var, '\0'))); |
| irt_filepath_ = FilePath(path_string); |
| } else { |
| FilePath plugin_dir; |
| if (!PathService::Get(chrome::DIR_INTERNAL_PLUGINS, &plugin_dir)) { |
| DLOG(ERROR) << "Failed to locate the plugins directory, NaCl disabled."; |
| MarkAsFailed(); |
| return; |
| } |
| irt_filepath_ = plugin_dir.Append(NaClIrtName()); |
| } |
| } |
| |
| NaClBrowser* NaClBrowser::GetInstance() { |
| return Singleton<NaClBrowser>::get(); |
| } |
| |
| bool NaClBrowser::IsReady() const { |
| return (IsOk() && |
| irt_state_ == NaClResourceReady && |
| validation_cache_state_ == NaClResourceReady); |
| } |
| |
| bool NaClBrowser::IsOk() const { |
| return ok_; |
| } |
| |
| base::PlatformFile NaClBrowser::IrtFile() const { |
| CHECK_EQ(irt_state_, NaClResourceReady); |
| CHECK_NE(irt_platform_file_, base::kInvalidPlatformFileValue); |
| return irt_platform_file_; |
| } |
| |
| void NaClBrowser::EnsureAllResourcesAvailable() { |
| EnsureIrtAvailable(); |
| EnsureValidationCacheAvailable(); |
| } |
| |
| // Load the IRT async. |
| void NaClBrowser::EnsureIrtAvailable() { |
| if (IsOk() && irt_state_ == NaClResourceUninitialized) { |
| irt_state_ = NaClResourceRequested; |
| // TODO(ncbray) use blocking pool. |
| if (!base::FileUtilProxy::CreateOrOpen( |
| content::BrowserThread::GetMessageLoopProxyForThread( |
| content::BrowserThread::FILE), |
| irt_filepath_, |
| base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, |
| base::Bind(&NaClBrowser::OnIrtOpened, |
| weak_factory_.GetWeakPtr()))) { |
| LOG(ERROR) << "Internal error, NaCl disabled."; |
| MarkAsFailed(); |
| } |
| } |
| } |
| |
| void NaClBrowser::OnIrtOpened(base::PlatformFileError error_code, |
| base::PassPlatformFile file, |
| bool created) { |
| DCHECK_EQ(irt_state_, NaClResourceRequested); |
| DCHECK(!created); |
| if (error_code == base::PLATFORM_FILE_OK) { |
| irt_platform_file_ = file.ReleaseValue(); |
| } else { |
| LOG(ERROR) << "Failed to open NaCl IRT file \"" |
| << irt_filepath_.LossyDisplayName() |
| << "\": " << error_code; |
| MarkAsFailed(); |
| } |
| irt_state_ = NaClResourceReady; |
| CheckWaiting(); |
| } |
| |
| void NaClBrowser::EnsureValidationCacheAvailable() { |
| if (IsOk() && validation_cache_state_ == NaClResourceUninitialized) { |
| if (ValidationCacheIsEnabled()) { |
| validation_cache_state_ = NaClResourceRequested; |
| |
| // Determine where the validation cache resides in the file system. It |
| // exists in Chrome's cache directory and is not tied to any specific |
| // profile. |
| // Start by finding the user data directory. |
| FilePath user_data_dir; |
| if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) { |
| RunWithoutValidationCache(); |
| return; |
| } |
| // The cache directory may or may not be the user data directory. |
| FilePath cache_file_path; |
| chrome::GetUserCacheDirectory(user_data_dir, &cache_file_path); |
| // Append the base file name to the cache directory. |
| validation_cache_file_path_ = |
| cache_file_path.Append(kValidationCacheFileName); |
| |
| // Structure for carrying data between the callbacks. |
| std::string* data = new std::string(); |
| // We can get away not giving this a sequence ID because this is the first |
| // task and further file access will not occur until after we get a |
| // response. |
| if (!content::BrowserThread::PostBlockingPoolTaskAndReply( |
| FROM_HERE, |
| base::Bind(ReadCache, validation_cache_file_path_, data), |
| base::Bind(&NaClBrowser::OnValidationCacheLoaded, |
| weak_factory_.GetWeakPtr(), |
| base::Owned(data)))) { |
| RunWithoutValidationCache(); |
| } |
| } else { |
| RunWithoutValidationCache(); |
| } |
| } |
| } |
| |
| void NaClBrowser::OnValidationCacheLoaded(const std::string *data) { |
| if (data->size() == 0) { |
| // No file found. |
| validation_cache_.Reset(); |
| } else { |
| Pickle pickle(data->data(), data->size()); |
| validation_cache_.Deserialize(&pickle); |
| } |
| validation_cache_state_ = NaClResourceReady; |
| CheckWaiting(); |
| } |
| |
| void NaClBrowser::RunWithoutValidationCache() { |
| // Be paranoid. |
| validation_cache_.Reset(); |
| validation_cache_is_enabled_ = false; |
| validation_cache_state_ = NaClResourceReady; |
| CheckWaiting(); |
| } |
| |
| void NaClBrowser::CheckWaiting() { |
| if (!IsOk() || IsReady()) { |
| // Queue the waiting tasks into the message loop. This helps avoid |
| // re-entrancy problems that could occur if the closure was invoked |
| // directly. For example, this could result in use-after-free of the |
| // process host. |
| for (std::vector<base::Closure>::iterator iter = waiting_.begin(); |
| iter != waiting_.end(); ++iter) { |
| MessageLoop::current()->PostTask(FROM_HERE, *iter); |
| } |
| waiting_.clear(); |
| } |
| } |
| |
| void NaClBrowser::MarkAsFailed() { |
| ok_ = false; |
| CheckWaiting(); |
| } |
| |
| void NaClBrowser::WaitForResources(const base::Closure& reply) { |
| waiting_.push_back(reply); |
| EnsureAllResourcesAvailable(); |
| CheckWaiting(); |
| } |
| |
| const FilePath& NaClBrowser::GetIrtFilePath() { |
| return irt_filepath_; |
| } |
| |
| bool NaClBrowser::QueryKnownToValidate(const std::string& signature, |
| bool off_the_record) { |
| if (off_the_record) { |
| // If we're off the record, don't reorder the main cache. |
| return validation_cache_.QueryKnownToValidate(signature, false) || |
| off_the_record_validation_cache_.QueryKnownToValidate(signature, true); |
| } else { |
| bool result = validation_cache_.QueryKnownToValidate(signature, true); |
| LogCacheQuery(result ? CACHE_HIT : CACHE_MISS); |
| // Queries can modify the MRU order of the cache. |
| MarkValidationCacheAsModified(); |
| return result; |
| } |
| } |
| |
| void NaClBrowser::SetKnownToValidate(const std::string& signature, |
| bool off_the_record) { |
| if (off_the_record) { |
| off_the_record_validation_cache_.SetKnownToValidate(signature); |
| } else { |
| validation_cache_.SetKnownToValidate(signature); |
| // The number of sets should be equal to the number of cache misses, minus |
| // validation failures and successful validations where stubout occurs. |
| LogCacheSet(CACHE_HIT); |
| MarkValidationCacheAsModified(); |
| } |
| } |
| |
| void NaClBrowser::MarkValidationCacheAsModified() { |
| if (!validation_cache_is_modified_) { |
| // Wait before persisting to disk. This can coalesce multiple cache |
| // modifications info a single disk write. |
| MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&NaClBrowser::PersistValidationCache, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kValidationCacheCoalescingTimeMS)); |
| validation_cache_is_modified_ = true; |
| } |
| } |
| |
| void NaClBrowser::PersistValidationCache() { |
| if (!validation_cache_file_path_.empty()) { |
| Pickle* pickle = new Pickle(); |
| validation_cache_.Serialize(pickle); |
| |
| // Pass the serialized data to another thread to write to disk. File IO is |
| // not allowed on the IO thread (which is the thread this method runs on) |
| // because it can degrade the responsiveness of the browser. |
| // The task is sequenced so that multiple writes happen in order. |
| content::BrowserThread::PostBlockingPoolSequencedTask( |
| kValidationCacheSequenceName, |
| FROM_HERE, |
| base::Bind(WriteCache, validation_cache_file_path_, |
| base::Owned(pickle))); |
| } |
| validation_cache_is_modified_ = false; |
| } |