blob: a2374806ee77b7cb4eed9b2aa9ffa18760de3a90 [file] [log] [blame]
// 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;
}