// 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 "build/build_config.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"); #if defined(OS_CHROMEOS) // TODO(ncbray) enable on ChromeOS. // http://code.google.com/p/chromium/issues/detail?id=131218 const bool kValidationCacheEnabledByDefault = false; #else const bool kValidationCacheEnabledByDefault = true; #endif 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(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", kValidationCacheEnabledByDefault)), 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(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::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::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; }