summaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authoralexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-22 20:44:02 +0000
committeralexis.menard@intel.com <alexis.menard@intel.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-22 20:44:02 +0000
commit7c766e9434441f945508f2bbdbe416fd557b7d02 (patch)
tree985dc3422d9f359ede2d26e893696391632a5728 /components
parent879517bd47bd27255ae5d29a1b6cfe3b2bb793fe (diff)
downloadchromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.zip
chromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.tar.gz
chromium_src-7c766e9434441f945508f2bbdbe416fd557b7d02.tar.bz2
Move more files from chrome/browser/nacl_host/ to components/nacl/browser/
These files have no dependencies on chrome/ so they can be safely moved to the components/ directory. This is part of an effort to componentize NaCl code. BUG=244791 R=brettw@chromium.org, cpu@chromium.org, jochen@chromium.org, mseaborn@chromium.org Review URL: https://codereview.chromium.org/75463005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@236821 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
-rw-r--r--components/components_tests.gyp4
-rw-r--r--components/nacl.gyp24
-rw-r--r--components/nacl/browser/DEPS4
-rw-r--r--components/nacl/browser/nacl_broker_host_win.cc118
-rw-r--r--components/nacl/browser/nacl_broker_host_win.h64
-rw-r--r--components/nacl/browser/nacl_broker_service_win.cc107
-rw-r--r--components/nacl/browser/nacl_broker_service_win.h70
-rw-r--r--components/nacl/browser/nacl_file_host.cc249
-rw-r--r--components/nacl/browser/nacl_file_host.h50
-rw-r--r--components/nacl/browser/nacl_file_host_unittest.cc119
-rw-r--r--components/nacl/browser/nacl_host_message_filter.cc177
-rw-r--r--components/nacl/browser/nacl_host_message_filter.h86
-rw-r--r--components/nacl/browser/nacl_process_host.cc1026
-rw-r--r--components/nacl/browser/nacl_process_host.h241
-rw-r--r--components/nacl/browser/nacl_process_host_unittest.cc15
-rw-r--r--components/nacl/browser/pnacl_host.cc634
-rw-r--r--components/nacl/browser/pnacl_host.h178
-rw-r--r--components/nacl/browser/pnacl_host_unittest.cc432
-rw-r--r--components/nacl/common/nacl_host_messages.cc34
-rw-r--r--components/nacl/common/nacl_switches.cc6
-rw-r--r--components/nacl/common/nacl_switches.h2
-rw-r--r--components/nacl_common.gyp4
22 files changed, 3643 insertions, 1 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 2e00f4a..9847b90 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -134,8 +134,12 @@
}],
['disable_nacl==0', {
'sources': [
+ 'nacl/browser/nacl_file_host_unittest.cc',
+ 'nacl/browser/nacl_process_host_unittest.cc',
'nacl/browser/nacl_validation_cache_unittest.cc',
+ 'nacl/browser/pnacl_host_unittest.cc',
'nacl/browser/pnacl_translation_cache_unittest.cc',
+ 'nacl/browser/test_nacl_browser_delegate.cc',
],
'dependencies': [
'nacl.gyp:nacl_browser',
diff --git a/components/nacl.gyp b/components/nacl.gyp
index 21fce79..cb9b554 100644
--- a/components/nacl.gyp
+++ b/components/nacl.gyp
@@ -40,7 +40,6 @@
'nacl/loader/nacl_validation_db.h',
'nacl/loader/nacl_validation_query.cc',
'nacl/loader/nacl_validation_query.h',
- 'nacl/browser/test_nacl_browser_delegate.cc',
],
# TODO(gregoryd): consider switching NaCl to use Chrome OS defines
'conditions': [
@@ -103,16 +102,39 @@
'target_name': 'nacl_browser',
'type': 'static_library',
'sources': [
+ 'nacl/browser/nacl_broker_host_win.cc',
+ 'nacl/browser/nacl_broker_host_win.h',
+ 'nacl/browser/nacl_broker_service_win.cc',
+ 'nacl/browser/nacl_broker_service_win.h',
'nacl/browser/nacl_browser.cc',
'nacl/browser/nacl_browser.h',
+ 'nacl/browser/nacl_file_host.cc',
+ 'nacl/browser/nacl_file_host.h',
+ 'nacl/browser/nacl_host_message_filter.cc',
+ 'nacl/browser/nacl_host_message_filter.h',
+ 'nacl/browser/nacl_process_host.cc',
+ 'nacl/browser/nacl_process_host.h',
'nacl/browser/nacl_validation_cache.cc',
'nacl/browser/nacl_validation_cache.h',
+ 'nacl/browser/pnacl_host.cc',
+ 'nacl/browser/pnacl_host.h',
'nacl/browser/pnacl_translation_cache.cc',
'nacl/browser/pnacl_translation_cache.h',
+ 'nacl/common/nacl_debug_exception_handler_win.cc',
+ 'nacl/common/nacl_debug_exception_handler_win.h',
],
'include_dirs': [
'..',
],
+ 'dependencies': [
+ 'nacl_common.gyp:nacl_common',
+ 'nacl_common.gyp:nacl_switches',
+ '../native_client/src/trusted/service_runtime/service_runtime.gyp:sel',
+ '../content/content.gyp:content_browser',
+ ],
+ 'defines': [
+ '<@(nacl_defines)',
+ ],
# TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
'msvs_disabled_warnings': [4267, ],
},
diff --git a/components/nacl/browser/DEPS b/components/nacl/browser/DEPS
index 67b5b47..ed24f51 100644
--- a/components/nacl/browser/DEPS
+++ b/components/nacl/browser/DEPS
@@ -1,5 +1,9 @@
include_rules = [
"+content/public/browser",
"+content/public/test",
+ "+native_client",
"+net",
+ "+ppapi/host",
+ "+ppapi/proxy",
+ "+ppapi/shared_impl",
]
diff --git a/components/nacl/browser/nacl_broker_host_win.cc b/components/nacl/browser/nacl_broker_host_win.cc
new file mode 100644
index 0000000..878ca6e
--- /dev/null
+++ b/components/nacl/browser/nacl_broker_host_win.cc
@@ -0,0 +1,118 @@
+// 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 "components/nacl/browser/nacl_broker_host_win.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/path_service.h"
+#include "components/nacl/browser/nacl_broker_service_win.h"
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/common/nacl_cmd_line.h"
+#include "components/nacl/common/nacl_messages.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "components/nacl/common/nacl_switches.h"
+#include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/common/child_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#include "ipc/ipc_switches.h"
+
+namespace {
+// NOTE: changes to this class need to be reviewed by the security team.
+class NaClBrokerSandboxedProcessLauncherDelegate
+ : public content::SandboxedProcessLauncherDelegate {
+ public:
+ NaClBrokerSandboxedProcessLauncherDelegate() {}
+ virtual ~NaClBrokerSandboxedProcessLauncherDelegate() {}
+
+ virtual void ShouldSandbox(bool* in_sandbox) OVERRIDE {
+ *in_sandbox = false;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NaClBrokerSandboxedProcessLauncherDelegate);
+};
+} // namespace
+
+namespace nacl {
+
+NaClBrokerHost::NaClBrokerHost() : is_terminating_(false) {
+ process_.reset(content::BrowserChildProcessHost::Create(
+ PROCESS_TYPE_NACL_BROKER, this));
+}
+
+NaClBrokerHost::~NaClBrokerHost() {
+}
+
+bool NaClBrokerHost::Init() {
+ // Create the channel that will be used for communicating with the broker.
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty())
+ return false;
+
+ // Create the path to the nacl broker/loader executable.
+ base::FilePath nacl_path;
+ if (!NaClBrowser::GetInstance()->GetNaCl64ExePath(&nacl_path))
+ return false;
+
+ CommandLine* cmd_line = new CommandLine(nacl_path);
+ CopyNaClCommandLineArguments(cmd_line);
+
+ cmd_line->AppendSwitchASCII(switches::kProcessType,
+ switches::kNaClBrokerProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+ if (NaClBrowser::GetDelegate()->DialogsAreSuppressed())
+ cmd_line->AppendSwitch(switches::kNoErrorDialogs);
+
+ process_->Launch(new NaClBrokerSandboxedProcessLauncherDelegate, cmd_line);
+ return true;
+}
+
+bool NaClBrokerHost::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(NaClBrokerHost, msg)
+ IPC_MESSAGE_HANDLER(NaClProcessMsg_LoaderLaunched, OnLoaderLaunched)
+ IPC_MESSAGE_HANDLER(NaClProcessMsg_DebugExceptionHandlerLaunched,
+ OnDebugExceptionHandlerLaunched)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+bool NaClBrokerHost::LaunchLoader(const std::string& loader_channel_id) {
+ return process_->Send(
+ new NaClProcessMsg_LaunchLoaderThroughBroker(loader_channel_id));
+}
+
+void NaClBrokerHost::OnLoaderLaunched(const std::string& loader_channel_id,
+ base::ProcessHandle handle) {
+ NaClBrokerService::GetInstance()->OnLoaderLaunched(loader_channel_id, handle);
+}
+
+bool NaClBrokerHost::LaunchDebugExceptionHandler(
+ int32 pid, base::ProcessHandle process_handle,
+ const std::string& startup_info) {
+ base::ProcessHandle broker_process = process_->GetData().handle;
+ base::ProcessHandle handle_in_broker_process;
+ if (!DuplicateHandle(::GetCurrentProcess(), process_handle,
+ broker_process, &handle_in_broker_process,
+ 0, /* bInheritHandle= */ FALSE, DUPLICATE_SAME_ACCESS))
+ return false;
+ return process_->Send(new NaClProcessMsg_LaunchDebugExceptionHandler(
+ pid, handle_in_broker_process, startup_info));
+}
+
+void NaClBrokerHost::OnDebugExceptionHandlerLaunched(int32 pid, bool success) {
+ NaClBrokerService::GetInstance()->OnDebugExceptionHandlerLaunched(pid,
+ success);
+}
+
+void NaClBrokerHost::StopBroker() {
+ is_terminating_ = true;
+ process_->Send(new NaClProcessMsg_StopBroker());
+}
+
+} // namespace nacl
diff --git a/components/nacl/browser/nacl_broker_host_win.h b/components/nacl/browser/nacl_broker_host_win.h
new file mode 100644
index 0000000..bbafdff
--- /dev/null
+++ b/components/nacl/browser/nacl_broker_host_win.h
@@ -0,0 +1,64 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_
+#define COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/process/process.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+
+namespace content {
+class BrowserChildProcessHost;
+}
+
+namespace nacl {
+
+class NaClBrokerHost : public content::BrowserChildProcessHostDelegate {
+ public:
+ NaClBrokerHost();
+ ~NaClBrokerHost();
+
+ // This function starts the broker process. It needs to be called
+ // before loaders can be launched.
+ bool Init();
+
+ // Send a message to the broker process, causing it to launch
+ // a Native Client loader process.
+ bool LaunchLoader(const std::string& loader_channel_id);
+
+ bool LaunchDebugExceptionHandler(int32 pid,
+ base::ProcessHandle process_handle,
+ const std::string& startup_info);
+
+ // Stop the broker process.
+ void StopBroker();
+
+ // Returns true if the process has been asked to terminate. If true, this
+ // object should no longer be used; it will eventually be destroyed by
+ // BrowserChildProcessHostImpl::OnChildDisconnected()
+ bool IsTerminating() { return is_terminating_; }
+
+ private:
+ // Handler for NaClProcessMsg_LoaderLaunched message
+ void OnLoaderLaunched(const std::string& loader_channel_id,
+ base::ProcessHandle handle);
+ // Handler for NaClProcessMsg_DebugExceptionHandlerLaunched message
+ void OnDebugExceptionHandlerLaunched(int32 pid, bool success);
+
+ // BrowserChildProcessHostDelegate implementation:
+ virtual bool OnMessageReceived(const IPC::Message& msg);
+
+ scoped_ptr<content::BrowserChildProcessHost> process_;
+ bool is_terminating_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClBrokerHost);
+};
+
+} // namespace nacl
+
+#endif // COMPONENTS_NACL_BROWSER_NACL_BROKER_HOST_WIN_H_
diff --git a/components/nacl/browser/nacl_broker_service_win.cc b/components/nacl/browser/nacl_broker_service_win.cc
new file mode 100644
index 0000000..af23be1
--- /dev/null
+++ b/components/nacl/browser/nacl_broker_service_win.cc
@@ -0,0 +1,107 @@
+// 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 "components/nacl/browser/nacl_broker_service_win.h"
+
+#include "components/nacl/browser/nacl_process_host.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+
+using content::BrowserChildProcessHostIterator;
+
+namespace nacl {
+
+NaClBrokerService* NaClBrokerService::GetInstance() {
+ return Singleton<NaClBrokerService>::get();
+}
+
+NaClBrokerService::NaClBrokerService()
+ : loaders_running_(0) {
+}
+
+bool NaClBrokerService::StartBroker() {
+ NaClBrokerHost* broker_host = new NaClBrokerHost;
+ if (!broker_host->Init()) {
+ delete broker_host;
+ return false;
+ }
+ return true;
+}
+
+bool NaClBrokerService::LaunchLoader(
+ base::WeakPtr<nacl::NaClProcessHost> nacl_process_host,
+ const std::string& loader_channel_id) {
+ // Add task to the list
+ pending_launches_[loader_channel_id] = nacl_process_host;
+ NaClBrokerHost* broker_host = GetBrokerHost();
+
+ if (!broker_host) {
+ if (!StartBroker())
+ return false;
+ broker_host = GetBrokerHost();
+ }
+ broker_host->LaunchLoader(loader_channel_id);
+
+ return true;
+}
+
+void NaClBrokerService::OnLoaderLaunched(const std::string& channel_id,
+ base::ProcessHandle handle) {
+ PendingLaunchesMap::iterator it = pending_launches_.find(channel_id);
+ if (pending_launches_.end() == it)
+ NOTREACHED();
+
+ NaClProcessHost* client = it->second.get();
+ if (client)
+ client->OnProcessLaunchedByBroker(handle);
+ pending_launches_.erase(it);
+ ++loaders_running_;
+}
+
+void NaClBrokerService::OnLoaderDied() {
+ DCHECK(loaders_running_ > 0);
+ --loaders_running_;
+ // Stop the broker only if there are no loaders running or being launched.
+ NaClBrokerHost* broker_host = GetBrokerHost();
+ if (loaders_running_ + pending_launches_.size() == 0 && broker_host != NULL) {
+ broker_host->StopBroker();
+ }
+}
+
+bool NaClBrokerService::LaunchDebugExceptionHandler(
+ base::WeakPtr<NaClProcessHost> nacl_process_host, int32 pid,
+ base::ProcessHandle process_handle, const std::string& startup_info) {
+ pending_debuggers_[pid] = nacl_process_host;
+ NaClBrokerHost* broker_host = GetBrokerHost();
+ if (!broker_host)
+ return false;
+ return broker_host->LaunchDebugExceptionHandler(pid, process_handle,
+ startup_info);
+}
+
+void NaClBrokerService::OnDebugExceptionHandlerLaunched(int32 pid,
+ bool success) {
+ PendingDebugExceptionHandlersMap::iterator it = pending_debuggers_.find(pid);
+ if (pending_debuggers_.end() == it)
+ NOTREACHED();
+
+ NaClProcessHost* client = it->second.get();
+ if (client)
+ client->OnDebugExceptionHandlerLaunchedByBroker(success);
+ pending_debuggers_.erase(it);
+}
+
+NaClBrokerHost* NaClBrokerService::GetBrokerHost() {
+ BrowserChildProcessHostIterator iter(PROCESS_TYPE_NACL_BROKER);
+ while (!iter.Done()) {
+ NaClBrokerHost* host = static_cast<NaClBrokerHost*>(
+ iter.GetDelegate());
+ if (!host->IsTerminating())
+ return host;
+ ++iter;
+ }
+ return NULL;
+}
+
+} // namespace nacl
diff --git a/components/nacl/browser/nacl_broker_service_win.h b/components/nacl/browser/nacl_broker_service_win.h
new file mode 100644
index 0000000..77d69dd
--- /dev/null
+++ b/components/nacl/browser/nacl_broker_service_win.h
@@ -0,0 +1,70 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_
+#define COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/memory/weak_ptr.h"
+#include "components/nacl/browser/nacl_broker_host_win.h"
+
+namespace nacl {
+
+class NaClProcessHost;
+
+class NaClBrokerService {
+ public:
+ // Returns the NaClBrokerService singleton.
+ static NaClBrokerService* GetInstance();
+
+ // Can be called several times, must be called before LaunchLoader.
+ bool StartBroker();
+
+ // Send a message to the broker process, causing it to launch
+ // a Native Client loader process.
+ bool LaunchLoader(base::WeakPtr<NaClProcessHost> client,
+ const std::string& loader_channel_id);
+
+ // Called by NaClBrokerHost to notify the service that a loader was launched.
+ void OnLoaderLaunched(const std::string& channel_id,
+ base::ProcessHandle handle);
+
+ // Called by NaClProcessHost when a loader process is terminated
+ void OnLoaderDied();
+
+ bool LaunchDebugExceptionHandler(base::WeakPtr<NaClProcessHost> client,
+ int32 pid,
+ base::ProcessHandle process_handle,
+ const std::string& startup_info);
+
+ // Called by NaClBrokerHost to notify the service that a debug
+ // exception handler was started.
+ void OnDebugExceptionHandlerLaunched(int32 pid, bool success);
+
+ private:
+ typedef std::map<std::string, base::WeakPtr<NaClProcessHost> >
+ PendingLaunchesMap;
+ typedef std::map<int, base::WeakPtr<NaClProcessHost> >
+ PendingDebugExceptionHandlersMap;
+
+ friend struct DefaultSingletonTraits<NaClBrokerService>;
+
+ NaClBrokerService();
+ ~NaClBrokerService() {}
+
+ NaClBrokerHost* GetBrokerHost();
+
+ int loaders_running_;
+ PendingLaunchesMap pending_launches_;
+ PendingDebugExceptionHandlersMap pending_debuggers_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClBrokerService);
+};
+
+} // namespace nacl
+
+#endif // COMPONENTS_NACL_BROWSER_NACL_BROKER_SERVICE_WIN_H_
diff --git a/components/nacl/browser/nacl_file_host.cc b/components/nacl/browser/nacl_file_host.cc
new file mode 100644
index 0000000..44a1c83
--- /dev/null
+++ b/components/nacl/browser/nacl_file_host.cc
@@ -0,0 +1,249 @@
+// 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 "components/nacl/browser/nacl_file_host.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/platform_file.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_browser_delegate.h"
+#include "components/nacl/browser/nacl_host_message_filter.h"
+#include "components/nacl/common/nacl_host_messages.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/site_instance.h"
+#include "ipc/ipc_platform_file.h"
+
+using content::BrowserThread;
+
+namespace {
+
+// Force a prefix to prevent user from opening "magic" files.
+const char* kExpectedFilePrefix = "pnacl_public_";
+
+// Restrict PNaCl file lengths to reduce likelyhood of hitting bugs
+// in file name limit error-handling-code-paths, etc.
+const size_t kMaxFileLength = 40;
+
+void NotifyRendererOfError(
+ nacl::NaClHostMessageFilter* nacl_host_message_filter,
+ IPC::Message* reply_msg) {
+ reply_msg->set_reply_error();
+ nacl_host_message_filter->Send(reply_msg);
+}
+
+bool PnaclDoOpenFile(const base::FilePath& file_to_open,
+ base::PlatformFile* out_file) {
+ base::PlatformFileError error_code;
+ *out_file = base::CreatePlatformFile(file_to_open,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ,
+ NULL,
+ &error_code);
+ if (error_code != base::PLATFORM_FILE_OK) {
+ return false;
+ }
+ return true;
+}
+
+void DoOpenPnaclFile(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ const std::string& filename,
+ IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+ base::FilePath full_filepath;
+
+ // PNaCl must be installed.
+ base::FilePath pnacl_dir;
+ if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) ||
+ !base::PathExists(pnacl_dir)) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+
+ // Do some validation.
+ if (!nacl_file_host::PnaclCanOpenFile(filename, &full_filepath)) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+
+ base::PlatformFile file_to_open;
+ if (!PnaclDoOpenFile(full_filepath, &file_to_open)) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+
+ // Send the reply!
+ // Do any DuplicateHandle magic that is necessary first.
+ IPC::PlatformFileForTransit target_desc =
+ IPC::GetFileHandleForProcess(file_to_open,
+ nacl_host_message_filter->PeerHandle(),
+ true /* Close source */);
+ if (target_desc == IPC::InvalidPlatformFileForTransit()) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+ NaClHostMsg_GetReadonlyPnaclFD::WriteReplyParams(
+ reply_msg, target_desc);
+ nacl_host_message_filter->Send(reply_msg);
+}
+
+void DoRegisterOpenedNaClExecutableFile(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ base::PlatformFile file,
+ base::FilePath file_path,
+ IPC::Message* reply_msg) {
+ // IO thread owns the NaClBrowser singleton.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ nacl::NaClBrowser* nacl_browser = nacl::NaClBrowser::GetInstance();
+ uint64 file_token_lo = 0;
+ uint64 file_token_hi = 0;
+ nacl_browser->PutFilePath(file_path, &file_token_lo, &file_token_hi);
+
+ IPC::PlatformFileForTransit file_desc = IPC::GetFileHandleForProcess(
+ file,
+ nacl_host_message_filter->PeerHandle(),
+ true /* close_source */);
+
+ NaClHostMsg_OpenNaClExecutable::WriteReplyParams(
+ reply_msg, file_desc, file_token_lo, file_token_hi);
+ nacl_host_message_filter->Send(reply_msg);
+}
+
+// Convert the file URL into a file descriptor.
+// This function is security sensitive. Be sure to check with a security
+// person before you modify it.
+void DoOpenNaClExecutableOnThreadPool(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ const GURL& file_url,
+ IPC::Message* reply_msg) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+
+ base::FilePath file_path;
+ if (!nacl::NaClBrowser::GetDelegate()->MapUrlToLocalFilePath(
+ file_url, true /* use_blocking_api */, &file_path)) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+
+ base::PlatformFile file;
+ nacl::OpenNaClExecutableImpl(file_path, &file);
+ if (file != base::kInvalidPlatformFileValue) {
+ // This function is running on the blocking pool, but the path needs to be
+ // registered in a structure owned by the IO thread.
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ base::Bind(
+ &DoRegisterOpenedNaClExecutableFile,
+ nacl_host_message_filter,
+ file, file_path, reply_msg));
+ } else {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+}
+
+} // namespace
+
+namespace nacl_file_host {
+
+void GetReadonlyPnaclFd(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ const std::string& filename,
+ IPC::Message* reply_msg) {
+ if (!BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(&DoOpenPnaclFile,
+ nacl_host_message_filter,
+ filename,
+ reply_msg))) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ }
+}
+
+// This function is security sensitive. Be sure to check with a security
+// person before you modify it.
+bool PnaclCanOpenFile(const std::string& filename,
+ base::FilePath* file_to_open) {
+ if (filename.length() > kMaxFileLength)
+ return false;
+
+ if (filename.empty())
+ return false;
+
+ // Restrict character set of the file name to something really simple
+ // (a-z, 0-9, and underscores).
+ for (size_t i = 0; i < filename.length(); ++i) {
+ char charAt = filename[i];
+ if (charAt < 'a' || charAt > 'z')
+ if (charAt < '0' || charAt > '9')
+ if (charAt != '_')
+ return false;
+ }
+
+ // PNaCl must be installed.
+ base::FilePath pnacl_dir;
+ if (!nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(&pnacl_dir) ||
+ pnacl_dir.empty())
+ return false;
+
+ // Prepend the prefix to restrict files to a whitelisted set.
+ base::FilePath full_path = pnacl_dir.AppendASCII(
+ std::string(kExpectedFilePrefix) + filename);
+ *file_to_open = full_path;
+ return true;
+}
+
+void OpenNaClExecutable(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ int render_view_id,
+ const GURL& file_url,
+ IPC::Message* reply_msg) {
+ if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &OpenNaClExecutable,
+ nacl_host_message_filter,
+ render_view_id, file_url, reply_msg));
+ return;
+ }
+
+ // Make sure render_view_id is valid and that the URL is a part of the
+ // render view's site. Without these checks, apps could probe the extension
+ // directory or run NaCl code from other extensions.
+ content::RenderViewHost* rvh = content::RenderViewHost::FromID(
+ nacl_host_message_filter->render_process_id(), render_view_id);
+ if (!rvh) {
+ nacl_host_message_filter->BadMessageReceived(); // Kill the renderer.
+ return;
+ }
+ content::SiteInstance* site_instance = rvh->GetSiteInstance();
+ if (!content::SiteInstance::IsSameWebSite(site_instance->GetBrowserContext(),
+ site_instance->GetSiteURL(),
+ file_url)) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ return;
+ }
+
+ // The URL is part of the current app. Now query the extension system for the
+ // file path and convert that to a file descriptor. This should be done on a
+ // blocking pool thread.
+ if (!BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(
+ &DoOpenNaClExecutableOnThreadPool,
+ nacl_host_message_filter,
+ file_url, reply_msg))) {
+ NotifyRendererOfError(nacl_host_message_filter.get(), reply_msg);
+ }
+}
+
+} // namespace nacl_file_host
diff --git a/components/nacl/browser/nacl_file_host.h b/components/nacl/browser/nacl_file_host.h
new file mode 100644
index 0000000..abe75b1
--- /dev/null
+++ b/components/nacl/browser/nacl_file_host.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_
+#define COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace IPC {
+class Message;
+}
+
+namespace nacl {
+class NaClHostMessageFilter;
+}
+
+// Opens NaCl Files in the Browser process, on behalf of the NaCl plugin.
+
+namespace nacl_file_host {
+
+// Open a PNaCl file (readonly) on behalf of the NaCl plugin.
+void GetReadonlyPnaclFd(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ const std::string& filename,
+ IPC::Message* reply_msg);
+
+// Return true if the filename requested is valid for opening.
+// Sets file_to_open to the base::FilePath which we will attempt to open.
+bool PnaclCanOpenFile(const std::string& filename,
+ base::FilePath* file_to_open);
+
+// Opens a NaCl executable file for reading and executing.
+void OpenNaClExecutable(
+ scoped_refptr<nacl::NaClHostMessageFilter> nacl_host_message_filter,
+ int render_view_id,
+ const GURL& file_url,
+ IPC::Message* reply_msg);
+
+} // namespace nacl_file_host
+
+#endif // COMPONENTS_NACL_BROWSER_NACL_FILE_HOST_H_
diff --git a/components/nacl/browser/nacl_file_host_unittest.cc b/components/nacl/browser/nacl_file_host_unittest.cc
new file mode 100644
index 0000000..0a83b7f
--- /dev/null
+++ b/components/nacl/browser/nacl_file_host_unittest.cc
@@ -0,0 +1,119 @@
+// 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 "components/nacl/browser/nacl_file_host.h"
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_path_override.h"
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_browser_delegate.h"
+#include "components/nacl/browser/test_nacl_browser_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using nacl_file_host::PnaclCanOpenFile;
+
+class FileHostTestNaClBrowserDelegate : public TestNaClBrowserDelegate {
+ public:
+ FileHostTestNaClBrowserDelegate() {}
+
+ virtual bool GetPnaclDirectory(base::FilePath* pnacl_dir) OVERRIDE {
+ *pnacl_dir = pnacl_path_;
+ return true;
+ }
+
+ void SetPnaclDirectory(const base::FilePath& pnacl_dir) {
+ pnacl_path_ = pnacl_dir;
+ }
+
+ private:
+ base::FilePath pnacl_path_;
+};
+
+class NaClFileHostTest : public testing::Test {
+ protected:
+ NaClFileHostTest();
+ virtual ~NaClFileHostTest();
+
+ virtual void SetUp() OVERRIDE {
+ nacl_browser_delegate_ = new FileHostTestNaClBrowserDelegate;
+ nacl::NaClBrowser::SetDelegate(nacl_browser_delegate_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // This deletes nacl_browser_delegate_.
+ nacl::NaClBrowser::SetDelegate(NULL);
+ }
+
+ FileHostTestNaClBrowserDelegate* nacl_browser_delegate() {
+ return nacl_browser_delegate_;
+ }
+
+ private:
+ FileHostTestNaClBrowserDelegate* nacl_browser_delegate_;
+ DISALLOW_COPY_AND_ASSIGN(NaClFileHostTest);
+};
+
+NaClFileHostTest::NaClFileHostTest() : nacl_browser_delegate_(NULL) {}
+
+NaClFileHostTest::~NaClFileHostTest() {
+}
+
+// Try to pass a few funny filenames with a dummy PNaCl directory set.
+TEST_F(NaClFileHostTest, TestFilenamesWithPnaclPath) {
+ base::ScopedTempDir scoped_tmp_dir;
+ ASSERT_TRUE(scoped_tmp_dir.CreateUniqueTempDir());
+
+ base::FilePath kTestPnaclPath = scoped_tmp_dir.path();
+
+ nacl_browser_delegate()->SetPnaclDirectory(kTestPnaclPath);
+ ASSERT_TRUE(nacl::NaClBrowser::GetDelegate()->GetPnaclDirectory(
+ &kTestPnaclPath));
+
+ // Check allowed strings, and check that the expected prefix is added.
+ base::FilePath out_path;
+ EXPECT_TRUE(PnaclCanOpenFile("pnacl_json", &out_path));
+ base::FilePath expected_path = kTestPnaclPath.Append(
+ FILE_PATH_LITERAL("pnacl_public_pnacl_json"));
+ EXPECT_EQ(expected_path, out_path);
+
+ EXPECT_TRUE(PnaclCanOpenFile("x86_32_llc", &out_path));
+ expected_path = kTestPnaclPath.Append(
+ FILE_PATH_LITERAL("pnacl_public_x86_32_llc"));
+ EXPECT_EQ(expected_path, out_path);
+
+ // Check character ranges.
+ EXPECT_FALSE(PnaclCanOpenFile(".xchars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("/xchars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("x/chars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("\\xchars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("x\\chars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("$xchars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("%xchars", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("CAPS", &out_path));
+ const char non_ascii[] = "\xff\xfe";
+ EXPECT_FALSE(PnaclCanOpenFile(non_ascii, &out_path));
+
+ // Check file length restriction.
+ EXPECT_FALSE(PnaclCanOpenFile("thisstringisactuallywaaaaaaaaaaaaaaaaaaaaaaaa"
+ "toolongwaytoolongwaaaaayyyyytoooooooooooooooo"
+ "looooooooong",
+ &out_path));
+
+ // Other bad files.
+ EXPECT_FALSE(PnaclCanOpenFile(std::string(), &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile(".", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("..", &out_path));
+#if defined(OS_WIN)
+ EXPECT_FALSE(PnaclCanOpenFile("..\\llc", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("%SystemRoot%", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("%SystemRoot%\\explorer.exe", &out_path));
+#else
+ EXPECT_FALSE(PnaclCanOpenFile("../llc", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("/bin/sh", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("$HOME", &out_path));
+ EXPECT_FALSE(PnaclCanOpenFile("$HOME/.bashrc", &out_path));
+#endif
+}
diff --git a/components/nacl/browser/nacl_host_message_filter.cc b/components/nacl/browser/nacl_host_message_filter.cc
new file mode 100644
index 0000000..1334ffa
--- /dev/null
+++ b/components/nacl/browser/nacl_host_message_filter.cc
@@ -0,0 +1,177 @@
+// 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 "components/nacl/browser/nacl_host_message_filter.h"
+
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_file_host.h"
+#include "components/nacl/browser/nacl_process_host.h"
+#include "components/nacl/browser/pnacl_host.h"
+#include "components/nacl/common/nacl_host_messages.h"
+#include "ipc/ipc_platform_file.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace nacl {
+
+NaClHostMessageFilter::NaClHostMessageFilter(
+ int render_process_id,
+ bool is_off_the_record,
+ const base::FilePath& profile_directory,
+ net::URLRequestContextGetter* request_context)
+ : render_process_id_(render_process_id),
+ off_the_record_(is_off_the_record),
+ profile_directory_(profile_directory),
+ request_context_(request_context),
+ weak_ptr_factory_(this) {
+}
+
+NaClHostMessageFilter::~NaClHostMessageFilter() {
+}
+
+void NaClHostMessageFilter::OnChannelClosing() {
+ pnacl::PnaclHost::GetInstance()->RendererClosing(render_process_id_);
+}
+
+bool NaClHostMessageFilter::OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP_EX(NaClHostMessageFilter, message, *message_was_ok)
+#if !defined(DISABLE_NACL)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_LaunchNaCl, OnLaunchNaCl)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_GetReadonlyPnaclFD,
+ OnGetReadonlyPnaclFd)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_NaClCreateTemporaryFile,
+ OnNaClCreateTemporaryFile)
+ IPC_MESSAGE_HANDLER(NaClHostMsg_NexeTempFileRequest,
+ OnGetNexeFd)
+ IPC_MESSAGE_HANDLER(NaClHostMsg_ReportTranslationFinished,
+ OnTranslationFinished)
+ IPC_MESSAGE_HANDLER(NaClHostMsg_NaClErrorStatus, OnNaClErrorStatus)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClHostMsg_OpenNaClExecutable,
+ OnOpenNaClExecutable)
+#endif
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
+}
+
+net::HostResolver* NaClHostMessageFilter::GetHostResolver() {
+ return request_context_->GetURLRequestContext()->host_resolver();
+}
+
+#if !defined(DISABLE_NACL)
+void NaClHostMessageFilter::OnLaunchNaCl(
+ const nacl::NaClLaunchParams& launch_params,
+ IPC::Message* reply_msg) {
+ NaClProcessHost* host = new NaClProcessHost(
+ GURL(launch_params.manifest_url),
+ launch_params.render_view_id,
+ launch_params.permission_bits,
+ launch_params.uses_irt,
+ launch_params.enable_dyncode_syscalls,
+ launch_params.enable_exception_handling,
+ launch_params.enable_crash_throttling,
+ off_the_record_,
+ profile_directory_);
+ GURL manifest_url(launch_params.manifest_url);
+ base::FilePath manifest_path;
+ // We're calling MapUrlToLocalFilePath with the non-blocking API
+ // because we're running in the I/O thread. Ideally we'd use the other path,
+ // which would cover more cases.
+ nacl::NaClBrowser::GetDelegate()->MapUrlToLocalFilePath(
+ manifest_url, false /* use_blocking_api */, &manifest_path);
+ host->Launch(this, reply_msg, manifest_path);
+}
+
+void NaClHostMessageFilter::OnGetReadonlyPnaclFd(
+ const std::string& filename, IPC::Message* reply_msg) {
+ // This posts a task to another thread, but the renderer will
+ // block until the reply is sent.
+ nacl_file_host::GetReadonlyPnaclFd(this, filename, reply_msg);
+
+ // This is the first message we receive from the renderer once it knows we
+ // want to use PNaCl, so start the translation cache initialization here.
+ pnacl::PnaclHost::GetInstance()->Init();
+}
+
+// Return the temporary file via a reply to the
+// NaClHostMsg_NaClCreateTemporaryFile sync message.
+void NaClHostMessageFilter::SyncReturnTemporaryFile(
+ IPC::Message* reply_msg,
+ base::PlatformFile fd) {
+ if (fd == base::kInvalidPlatformFileValue) {
+ reply_msg->set_reply_error();
+ } else {
+ NaClHostMsg_NaClCreateTemporaryFile::WriteReplyParams(
+ reply_msg,
+ IPC::GetFileHandleForProcess(fd, PeerHandle(), true));
+ }
+ Send(reply_msg);
+}
+
+void NaClHostMessageFilter::OnNaClCreateTemporaryFile(
+ IPC::Message* reply_msg) {
+ pnacl::PnaclHost::GetInstance()->CreateTemporaryFile(
+ base::Bind(&NaClHostMessageFilter::SyncReturnTemporaryFile,
+ this,
+ reply_msg));
+}
+
+void NaClHostMessageFilter::AsyncReturnTemporaryFile(
+ int pp_instance,
+ base::PlatformFile fd,
+ bool is_hit) {
+ Send(new NaClViewMsg_NexeTempFileReply(
+ pp_instance,
+ is_hit,
+ // Don't close our copy of the handle, because PnaclHost will use it
+ // when the translation finishes.
+ IPC::GetFileHandleForProcess(fd, PeerHandle(), false)));
+}
+
+void NaClHostMessageFilter::OnGetNexeFd(
+ int render_view_id,
+ int pp_instance,
+ const nacl::PnaclCacheInfo& cache_info) {
+ if (!cache_info.pexe_url.is_valid()) {
+ LOG(ERROR) << "Bad URL received from GetNexeFd: " <<
+ cache_info.pexe_url.possibly_invalid_spec();
+ BadMessageReceived();
+ return;
+ }
+
+ pnacl::PnaclHost::GetInstance()->GetNexeFd(
+ render_process_id_,
+ render_view_id,
+ pp_instance,
+ off_the_record_,
+ cache_info,
+ base::Bind(&NaClHostMessageFilter::AsyncReturnTemporaryFile,
+ this,
+ pp_instance));
+}
+
+void NaClHostMessageFilter::OnTranslationFinished(int instance, bool success) {
+ pnacl::PnaclHost::GetInstance()->TranslationFinished(
+ render_process_id_, instance, success);
+}
+
+void NaClHostMessageFilter::OnNaClErrorStatus(int render_view_id,
+ int error_id) {
+ nacl::NaClBrowser::GetDelegate()->ShowNaClInfobar(render_process_id_,
+ render_view_id, error_id);
+}
+
+void NaClHostMessageFilter::OnOpenNaClExecutable(int render_view_id,
+ const GURL& file_url,
+ IPC::Message* reply_msg) {
+ nacl_file_host::OpenNaClExecutable(this, render_view_id, file_url,
+ reply_msg);
+}
+#endif
+
+} // namespace nacl
diff --git a/components/nacl/browser/nacl_host_message_filter.h b/components/nacl/browser/nacl_host_message_filter.h
new file mode 100644
index 0000000..01d610f
--- /dev/null
+++ b/components/nacl/browser/nacl_host_message_filter.h
@@ -0,0 +1,86 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_
+#define COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "content/public/browser/browser_message_filter.h"
+
+class GURL;
+
+namespace nacl {
+struct NaClLaunchParams;
+struct PnaclCacheInfo;
+}
+
+namespace net {
+class HostResolver;
+class URLRequestContextGetter;
+}
+
+namespace nacl {
+
+// This class filters out incoming Chrome-specific IPC messages for the renderer
+// process on the IPC thread.
+class NaClHostMessageFilter : public content::BrowserMessageFilter {
+ public:
+ NaClHostMessageFilter(int render_process_id,
+ bool is_off_the_record,
+ const base::FilePath& profile_directory,
+ net::URLRequestContextGetter* request_context);
+
+ // content::BrowserMessageFilter methods:
+ virtual bool OnMessageReceived(const IPC::Message& message,
+ bool* message_was_ok) OVERRIDE;
+ virtual void OnChannelClosing() OVERRIDE;
+
+ int render_process_id() { return render_process_id_; }
+ bool off_the_record() { return off_the_record_; }
+ net::HostResolver* GetHostResolver();
+
+ private:
+ friend class content::BrowserThread;
+ friend class base::DeleteHelper<NaClHostMessageFilter>;
+
+ virtual ~NaClHostMessageFilter();
+
+#if !defined(DISABLE_NACL)
+ void OnLaunchNaCl(const NaClLaunchParams& launch_params,
+ IPC::Message* reply_msg);
+ void OnGetReadonlyPnaclFd(const std::string& filename,
+ IPC::Message* reply_msg);
+ void OnNaClCreateTemporaryFile(IPC::Message* reply_msg);
+ void OnGetNexeFd(int render_view_id,
+ int pp_instance,
+ const PnaclCacheInfo& cache_info);
+ void OnTranslationFinished(int instance, bool success);
+ void OnNaClErrorStatus(int render_view_id, int error_id);
+ void OnOpenNaClExecutable(int render_view_id,
+ const GURL& file_url,
+ IPC::Message* reply_msg);
+ void SyncReturnTemporaryFile(IPC::Message* reply_msg,
+ base::PlatformFile fd);
+ void AsyncReturnTemporaryFile(int pp_instance,
+ base::PlatformFile fd,
+ bool is_hit);
+#endif
+ int render_process_id_;
+
+ // off_the_record_ is copied from the profile partly so that it can be
+ // read on the IO thread.
+ bool off_the_record_;
+ base::FilePath profile_directory_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_;
+
+ base::WeakPtrFactory<NaClHostMessageFilter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClHostMessageFilter);
+};
+
+} // namespace nacl
+
+#endif // COMPONENTS_NACL_BROWSER_NACL_HOST_MESSAGE_FILTER_H_
diff --git a/components/nacl/browser/nacl_process_host.cc b/components/nacl/browser/nacl_process_host.cc
new file mode 100644
index 0000000..565a0d5
--- /dev/null
+++ b/components/nacl/browser/nacl_process_host.cc
@@ -0,0 +1,1026 @@
+// 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 "components/nacl/browser/nacl_process_host.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process_iterator.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "build/build_config.h"
+#include "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/nacl_host_message_filter.h"
+#include "components/nacl/common/nacl_cmd_line.h"
+#include "components/nacl/common/nacl_host_messages.h"
+#include "components/nacl/common/nacl_messages.h"
+#include "components/nacl/common/nacl_process_type.h"
+#include "components/nacl/common/nacl_switches.h"
+#include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/browser_ppapi_host.h"
+#include "content/public/browser/child_process_data.h"
+#include "content/public/common/child_process_host.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/common/process_type.h"
+#include "ipc/ipc_channel.h"
+#include "ipc/ipc_switches.h"
+#include "native_client/src/shared/imc/nacl_imc_c.h"
+#include "net/base/net_util.h"
+#include "net/socket/tcp_listen_socket.h"
+#include "ppapi/host/host_factory.h"
+#include "ppapi/host/ppapi_host.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/ppapi_nacl_channel_args.h"
+
+#if defined(OS_POSIX)
+#include <fcntl.h>
+
+#include "ipc/ipc_channel_posix.h"
+#elif defined(OS_WIN)
+#include <windows.h>
+
+#include "base/threading/thread.h"
+#include "base/win/scoped_handle.h"
+#include "components/nacl/browser/nacl_broker_service_win.h"
+#include "components/nacl/common/nacl_debug_exception_handler_win.h"
+#include "content/public/common/sandbox_init.h"
+#include "content/public/common/sandboxed_process_launcher_delegate.h"
+#endif
+
+using content::BrowserThread;
+using content::ChildProcessData;
+using content::ChildProcessHost;
+using ppapi::proxy::SerializedHandle;
+
+#if defined(OS_WIN)
+
+namespace {
+
+// Looks for the largest contiguous unallocated region of address
+// space and returns it via |*out_addr| and |*out_size|.
+void FindAddressSpace(base::ProcessHandle process,
+ char** out_addr, size_t* out_size) {
+ *out_addr = NULL;
+ *out_size = 0;
+ char* addr = 0;
+ while (true) {
+ MEMORY_BASIC_INFORMATION info;
+ size_t result = VirtualQueryEx(process, static_cast<void*>(addr),
+ &info, sizeof(info));
+ if (result < sizeof(info))
+ break;
+ if (info.State == MEM_FREE && info.RegionSize > *out_size) {
+ *out_addr = addr;
+ *out_size = info.RegionSize;
+ }
+ addr += info.RegionSize;
+ }
+}
+
+} // namespace
+
+namespace nacl {
+
+// Allocates |size| bytes of address space in the given process at a
+// randomised address.
+void* AllocateAddressSpaceASLR(base::ProcessHandle process, size_t size) {
+ char* addr;
+ size_t avail_size;
+ FindAddressSpace(process, &addr, &avail_size);
+ if (avail_size < size)
+ return NULL;
+ size_t offset = base::RandGenerator(avail_size - size);
+ const int kPageSize = 0x10000;
+ void* request_addr =
+ reinterpret_cast<void*>(reinterpret_cast<uint64>(addr + offset)
+ & ~(kPageSize - 1));
+ return VirtualAllocEx(process, request_addr, size,
+ MEM_RESERVE, PAGE_NOACCESS);
+}
+
+} // namespace nacl
+
+#endif // defined(OS_WIN)
+
+namespace {
+
+#if defined(OS_WIN)
+bool RunningOnWOW64() {
+ return (base::win::OSInfo::GetInstance()->wow64_status() ==
+ base::win::OSInfo::WOW64_ENABLED);
+}
+
+// NOTE: changes to this class need to be reviewed by the security team.
+class NaClSandboxedProcessLauncherDelegate
+ : public content::SandboxedProcessLauncherDelegate {
+ public:
+ NaClSandboxedProcessLauncherDelegate() {}
+ virtual ~NaClSandboxedProcessLauncherDelegate() {}
+
+ virtual void PostSpawnTarget(base::ProcessHandle process) {
+ // For Native Client sel_ldr processes on 32-bit Windows, reserve 1 GB of
+ // address space to prevent later failure due to address space fragmentation
+ // from .dll loading. The NaCl process will attempt to locate this space by
+ // scanning the address space using VirtualQuery.
+ // TODO(bbudge) Handle the --no-sandbox case.
+ // http://code.google.com/p/nativeclient/issues/detail?id=2131
+ const SIZE_T kNaClSandboxSize = 1 << 30;
+ if (!nacl::AllocateAddressSpaceASLR(process, kNaClSandboxSize)) {
+ DLOG(WARNING) << "Failed to reserve address space for Native Client";
+ }
+ }
+};
+
+#endif // OS_WIN
+
+void SetCloseOnExec(NaClHandle fd) {
+#if defined(OS_POSIX)
+ int flags = fcntl(fd, F_GETFD);
+ CHECK_NE(flags, -1);
+ int rc = fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ CHECK_EQ(rc, 0);
+#endif
+}
+
+bool ShareHandleToSelLdr(
+ base::ProcessHandle processh,
+ NaClHandle sourceh,
+ bool close_source,
+ std::vector<nacl::FileDescriptor> *handles_for_sel_ldr) {
+#if defined(OS_WIN)
+ HANDLE channel;
+ int flags = DUPLICATE_SAME_ACCESS;
+ if (close_source)
+ flags |= DUPLICATE_CLOSE_SOURCE;
+ if (!DuplicateHandle(GetCurrentProcess(),
+ reinterpret_cast<HANDLE>(sourceh),
+ processh,
+ &channel,
+ 0, // Unused given DUPLICATE_SAME_ACCESS.
+ FALSE,
+ flags)) {
+ LOG(ERROR) << "DuplicateHandle() failed";
+ return false;
+ }
+ handles_for_sel_ldr->push_back(
+ reinterpret_cast<nacl::FileDescriptor>(channel));
+#else
+ nacl::FileDescriptor channel;
+ channel.fd = sourceh;
+ channel.auto_close = close_source;
+ handles_for_sel_ldr->push_back(channel);
+#endif
+ return true;
+}
+
+ppapi::PpapiPermissions GetNaClPermissions(uint32 permission_bits) {
+ // Only allow NaCl plugins to request certain permissions. We don't want
+ // a compromised renderer to be able to start a nacl plugin with e.g. Flash
+ // permissions which may expand the surface area of the sandbox.
+ uint32 masked_bits = permission_bits & ppapi::PERMISSION_DEV;
+ return ppapi::PpapiPermissions::GetForCommandLine(masked_bits);
+}
+
+} // namespace
+
+namespace nacl {
+
+struct NaClProcessHost::NaClInternal {
+ NaClHandle socket_for_renderer;
+ NaClHandle socket_for_sel_ldr;
+
+ NaClInternal()
+ : socket_for_renderer(NACL_INVALID_HANDLE),
+ socket_for_sel_ldr(NACL_INVALID_HANDLE) { }
+};
+
+// -----------------------------------------------------------------------------
+
+NaClProcessHost::PluginListener::PluginListener(NaClProcessHost* host)
+ : host_(host) {
+}
+
+bool NaClProcessHost::PluginListener::OnMessageReceived(
+ const IPC::Message& msg) {
+ return host_->OnUntrustedMessageForwarded(msg);
+}
+
+NaClProcessHost::NaClProcessHost(const GURL& manifest_url,
+ int render_view_id,
+ uint32 permission_bits,
+ bool uses_irt,
+ bool enable_dyncode_syscalls,
+ bool enable_exception_handling,
+ bool enable_crash_throttling,
+ bool off_the_record,
+ const base::FilePath& profile_directory)
+ : manifest_url_(manifest_url),
+ permissions_(GetNaClPermissions(permission_bits)),
+#if defined(OS_WIN)
+ process_launched_by_broker_(false),
+#endif
+ reply_msg_(NULL),
+#if defined(OS_WIN)
+ debug_exception_handler_requested_(false),
+#endif
+ internal_(new NaClInternal()),
+ weak_factory_(this),
+ uses_irt_(uses_irt),
+ enable_debug_stub_(false),
+ enable_dyncode_syscalls_(enable_dyncode_syscalls),
+ enable_exception_handling_(enable_exception_handling),
+ enable_crash_throttling_(enable_crash_throttling),
+ off_the_record_(off_the_record),
+ profile_directory_(profile_directory),
+ ipc_plugin_listener_(this),
+ render_view_id_(render_view_id) {
+ process_.reset(content::BrowserChildProcessHost::Create(
+ PROCESS_TYPE_NACL_LOADER, this));
+
+ // Set the display name so the user knows what plugin the process is running.
+ // We aren't on the UI thread so getting the pref locale for language
+ // formatting isn't possible, so IDN will be lost, but this is probably OK
+ // for this use case.
+ process_->SetName(net::FormatUrl(manifest_url_, std::string()));
+
+ enable_debug_stub_ = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableNaClDebug);
+}
+
+NaClProcessHost::~NaClProcessHost() {
+ // Report exit status only if the process was successfully started.
+ if (process_->GetData().handle != base::kNullProcessHandle) {
+ int exit_code = 0;
+ process_->GetTerminationStatus(false /* known_dead */, &exit_code);
+ std::string message =
+ base::StringPrintf("NaCl process exited with status %i (0x%x)",
+ exit_code, exit_code);
+ if (exit_code == 0) {
+ VLOG(1) << message;
+ } else {
+ LOG(ERROR) << message;
+ }
+ }
+
+ if (internal_->socket_for_renderer != NACL_INVALID_HANDLE) {
+ if (NaClClose(internal_->socket_for_renderer) != 0) {
+ NOTREACHED() << "NaClClose() failed";
+ }
+ }
+
+ if (internal_->socket_for_sel_ldr != NACL_INVALID_HANDLE) {
+ if (NaClClose(internal_->socket_for_sel_ldr) != 0) {
+ NOTREACHED() << "NaClClose() failed";
+ }
+ }
+
+ if (reply_msg_) {
+ // The process failed to launch for some reason.
+ // Don't keep the renderer hanging.
+ reply_msg_->set_reply_error();
+ nacl_host_message_filter_->Send(reply_msg_);
+ }
+#if defined(OS_WIN)
+ if (process_launched_by_broker_) {
+ NaClBrokerService::GetInstance()->OnLoaderDied();
+ }
+#endif
+}
+
+void NaClProcessHost::OnProcessCrashed(int exit_status) {
+ if (enable_crash_throttling_ &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisablePnaclCrashThrottling)) {
+ NaClBrowser::GetInstance()->OnProcessCrashed();
+ }
+}
+
+// This is called at browser startup.
+// static
+void NaClProcessHost::EarlyStartup() {
+ NaClBrowser::GetInstance()->EarlyStartup();
+#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+ // Open the IRT file early to make sure that it isn't replaced out from
+ // under us by autoupdate.
+ NaClBrowser::GetInstance()->EnsureIrtAvailable();
+#endif
+ CommandLine* cmd = CommandLine::ForCurrentProcess();
+ UMA_HISTOGRAM_BOOLEAN(
+ "NaCl.nacl-gdb",
+ !cmd->GetSwitchValuePath(switches::kNaClGdb).empty());
+ UMA_HISTOGRAM_BOOLEAN(
+ "NaCl.nacl-gdb-script",
+ !cmd->GetSwitchValuePath(switches::kNaClGdbScript).empty());
+ UMA_HISTOGRAM_BOOLEAN(
+ "NaCl.enable-nacl-debug",
+ cmd->HasSwitch(switches::kEnableNaClDebug));
+ NaClBrowser::GetDelegate()->SetDebugPatterns(
+ cmd->GetSwitchValueASCII(switches::kNaClDebugMask));
+}
+
+void NaClProcessHost::Launch(
+ NaClHostMessageFilter* nacl_host_message_filter,
+ IPC::Message* reply_msg,
+ const base::FilePath& manifest_path) {
+ nacl_host_message_filter_ = nacl_host_message_filter;
+ reply_msg_ = reply_msg;
+ manifest_path_ = manifest_path;
+
+ // Do not launch the requested NaCl module if NaCl is marked "unstable" due
+ // to too many crashes within a given time period.
+ if (enable_crash_throttling_ &&
+ !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDisablePnaclCrashThrottling) &&
+ NaClBrowser::GetInstance()->IsThrottled()) {
+ SendErrorToRenderer("Process creation was throttled due to excessive"
+ " crashes");
+ delete this;
+ return;
+ }
+
+ const CommandLine* cmd = CommandLine::ForCurrentProcess();
+#if defined(OS_WIN)
+ if (cmd->HasSwitch(switches::kEnableNaClDebug) &&
+ !cmd->HasSwitch(switches::kNoSandbox)) {
+ // We don't switch off sandbox automatically for security reasons.
+ SendErrorToRenderer("NaCl's GDB debug stub requires --no-sandbox flag"
+ " on Windows. See crbug.com/265624.");
+ delete this;
+ return;
+ }
+#endif
+ if (cmd->HasSwitch(switches::kNaClGdb) &&
+ !cmd->HasSwitch(switches::kEnableNaClDebug)) {
+ LOG(WARNING) << "--nacl-gdb flag requires --enable-nacl-debug flag";
+ }
+
+ // Start getting the IRT open asynchronously while we launch the NaCl process.
+ // We'll make sure this actually finished in StartWithLaunchedProcess, below.
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+ nacl_browser->EnsureAllResourcesAvailable();
+ if (!nacl_browser->IsOk()) {
+ SendErrorToRenderer("could not find all the resources needed"
+ " to launch the process");
+ delete this;
+ return;
+ }
+
+ // Rather than creating a socket pair in the renderer, and passing
+ // one side through the browser to sel_ldr, socket pairs are created
+ // in the browser and then passed to the renderer and sel_ldr.
+ //
+ // This is mainly for the benefit of Windows, where sockets cannot
+ // be passed in messages, but are copied via DuplicateHandle().
+ // This means the sandboxed renderer cannot send handles to the
+ // browser process.
+
+ NaClHandle pair[2];
+ // Create a connected socket
+ if (NaClSocketPair(pair) == -1) {
+ SendErrorToRenderer("NaClSocketPair() failed");
+ delete this;
+ return;
+ }
+ internal_->socket_for_renderer = pair[0];
+ internal_->socket_for_sel_ldr = pair[1];
+ SetCloseOnExec(pair[0]);
+ SetCloseOnExec(pair[1]);
+
+ // Launch the process
+ if (!LaunchSelLdr()) {
+ delete this;
+ }
+}
+
+void NaClProcessHost::OnChannelConnected(int32 peer_pid) {
+ if (!CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kNaClGdb).empty()) {
+ LaunchNaClGdb();
+ }
+}
+
+#if defined(OS_WIN)
+void NaClProcessHost::OnProcessLaunchedByBroker(base::ProcessHandle handle) {
+ process_launched_by_broker_ = true;
+ process_->SetHandle(handle);
+ if (!StartWithLaunchedProcess())
+ delete this;
+}
+
+void NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker(bool success) {
+ IPC::Message* reply = attach_debug_exception_handler_reply_msg_.release();
+ NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply, success);
+ Send(reply);
+}
+#endif
+
+// Needed to handle sync messages in OnMessageRecieved.
+bool NaClProcessHost::Send(IPC::Message* msg) {
+ return process_->Send(msg);
+}
+
+bool NaClProcessHost::LaunchNaClGdb() {
+#if defined(OS_WIN)
+ base::FilePath nacl_gdb =
+ CommandLine::ForCurrentProcess()->GetSwitchValuePath(switches::kNaClGdb);
+ CommandLine cmd_line(nacl_gdb);
+#else
+ CommandLine::StringType nacl_gdb =
+ CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switches::kNaClGdb);
+ CommandLine::StringVector argv;
+ // We don't support spaces inside arguments in --nacl-gdb switch.
+ base::SplitString(nacl_gdb, static_cast<CommandLine::CharType>(' '), &argv);
+ CommandLine cmd_line(argv);
+#endif
+ cmd_line.AppendArg("--eval-command");
+ base::FilePath::StringType irt_path(
+ NaClBrowser::GetInstance()->GetIrtFilePath().value());
+ // Avoid back slashes because nacl-gdb uses posix escaping rules on Windows.
+ // See issue https://code.google.com/p/nativeclient/issues/detail?id=3482.
+ std::replace(irt_path.begin(), irt_path.end(), '\\', '/');
+ cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-irt \"") + irt_path +
+ FILE_PATH_LITERAL("\""));
+ if (!manifest_path_.empty()) {
+ cmd_line.AppendArg("--eval-command");
+ base::FilePath::StringType manifest_path_value(manifest_path_.value());
+ std::replace(manifest_path_value.begin(), manifest_path_value.end(),
+ '\\', '/');
+ cmd_line.AppendArgNative(FILE_PATH_LITERAL("nacl-manifest \"") +
+ manifest_path_value + FILE_PATH_LITERAL("\""));
+ }
+ cmd_line.AppendArg("--eval-command");
+ cmd_line.AppendArg("target remote :4014");
+ base::FilePath script = CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ switches::kNaClGdbScript);
+ if (!script.empty()) {
+ cmd_line.AppendArg("--command");
+ cmd_line.AppendArgNative(script.value());
+ }
+ return base::LaunchProcess(cmd_line, base::LaunchOptions(), NULL);
+}
+
+bool NaClProcessHost::LaunchSelLdr() {
+ std::string channel_id = process_->GetHost()->CreateChannel();
+ if (channel_id.empty()) {
+ SendErrorToRenderer("CreateChannel() failed");
+ return false;
+ }
+
+ CommandLine::StringType nacl_loader_prefix;
+#if defined(OS_POSIX)
+ nacl_loader_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueNative(
+ switches::kNaClLoaderCmdPrefix);
+#endif // defined(OS_POSIX)
+
+ // Build command line for nacl.
+
+#if defined(OS_MACOSX)
+ // The Native Client process needs to be able to allocate a 1GB contiguous
+ // region to use as the client environment's virtual address space. ASLR
+ // (PIE) interferes with this by making it possible that no gap large enough
+ // to accomodate this request will exist in the child process' address
+ // space. Disable PIE for NaCl processes. See http://crbug.com/90221 and
+ // http://code.google.com/p/nativeclient/issues/detail?id=2043.
+ int flags = ChildProcessHost::CHILD_NO_PIE;
+#elif defined(OS_LINUX)
+ int flags = nacl_loader_prefix.empty() ? ChildProcessHost::CHILD_ALLOW_SELF :
+ ChildProcessHost::CHILD_NORMAL;
+#else
+ int flags = ChildProcessHost::CHILD_NORMAL;
+#endif
+
+ base::FilePath exe_path = ChildProcessHost::GetChildPath(flags);
+ if (exe_path.empty())
+ return false;
+
+#if defined(OS_WIN)
+ // On Windows 64-bit NaCl loader is called nacl64.exe instead of chrome.exe
+ if (RunningOnWOW64()) {
+ if (!NaClBrowser::GetInstance()->GetNaCl64ExePath(&exe_path)) {
+ SendErrorToRenderer("could not get path to nacl64.exe");
+ return false;
+ }
+ }
+#endif
+
+ scoped_ptr<CommandLine> cmd_line(new CommandLine(exe_path));
+ CopyNaClCommandLineArguments(cmd_line.get());
+
+ cmd_line->AppendSwitchASCII(switches::kProcessType,
+ switches::kNaClLoaderProcess);
+ cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id);
+ if (NaClBrowser::GetDelegate()->DialogsAreSuppressed())
+ cmd_line->AppendSwitch(switches::kNoErrorDialogs);
+
+ if (!nacl_loader_prefix.empty())
+ cmd_line->PrependWrapper(nacl_loader_prefix);
+
+ // On Windows we might need to start the broker process to launch a new loader
+#if defined(OS_WIN)
+ if (RunningOnWOW64()) {
+ if (!NaClBrokerService::GetInstance()->LaunchLoader(
+ weak_factory_.GetWeakPtr(), channel_id)) {
+ SendErrorToRenderer("broker service did not launch process");
+ return false;
+ }
+ } else {
+ process_->Launch(new NaClSandboxedProcessLauncherDelegate,
+ cmd_line.release());
+ }
+#elif defined(OS_POSIX)
+ process_->Launch(nacl_loader_prefix.empty(), // use_zygote
+ base::EnvironmentMap(),
+ cmd_line.release());
+#endif
+
+ return true;
+}
+
+bool NaClProcessHost::OnMessageReceived(const IPC::Message& msg) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg)
+ IPC_MESSAGE_HANDLER(NaClProcessMsg_QueryKnownToValidate,
+ OnQueryKnownToValidate)
+ IPC_MESSAGE_HANDLER(NaClProcessMsg_SetKnownToValidate,
+ OnSetKnownToValidate)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_ResolveFileToken,
+ OnResolveFileToken)
+#if defined(OS_WIN)
+ IPC_MESSAGE_HANDLER_DELAY_REPLY(NaClProcessMsg_AttachDebugExceptionHandler,
+ OnAttachDebugExceptionHandler)
+#endif
+ IPC_MESSAGE_HANDLER(NaClProcessHostMsg_PpapiChannelCreated,
+ OnPpapiChannelCreated)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void NaClProcessHost::OnProcessLaunched() {
+ if (!StartWithLaunchedProcess())
+ delete this;
+}
+
+// Called when the NaClBrowser singleton has been fully initialized.
+void NaClProcessHost::OnResourcesReady() {
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+ if (!nacl_browser->IsReady()) {
+ SendErrorToRenderer("could not acquire shared resources needed by NaCl");
+ delete this;
+ } else if (!SendStart()) {
+ delete this;
+ }
+}
+
+bool NaClProcessHost::ReplyToRenderer(
+ const IPC::ChannelHandle& channel_handle) {
+#if defined(OS_WIN)
+ // If we are on 64-bit Windows, the NaCl process's sandbox is
+ // managed by a different process from the renderer's sandbox. We
+ // need to inform the renderer's sandbox about the NaCl process so
+ // that the renderer can send handles to the NaCl process using
+ // BrokerDuplicateHandle().
+ if (RunningOnWOW64()) {
+ if (!content::BrokerAddTargetPeer(process_->GetData().handle)) {
+ SendErrorToRenderer("BrokerAddTargetPeer() failed");
+ return false;
+ }
+ }
+#endif
+
+ FileDescriptor handle_for_renderer;
+#if defined(OS_WIN)
+ // Copy the handle into the renderer process.
+ HANDLE handle_in_renderer;
+ if (!DuplicateHandle(base::GetCurrentProcessHandle(),
+ reinterpret_cast<HANDLE>(
+ internal_->socket_for_renderer),
+ nacl_host_message_filter_->PeerHandle(),
+ &handle_in_renderer,
+ 0, // Unused given DUPLICATE_SAME_ACCESS.
+ FALSE,
+ DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
+ SendErrorToRenderer("DuplicateHandle() failed");
+ return false;
+ }
+ handle_for_renderer = reinterpret_cast<FileDescriptor>(
+ handle_in_renderer);
+#else
+ // No need to dup the imc_handle - we don't pass it anywhere else so
+ // it cannot be closed.
+ FileDescriptor imc_handle;
+ imc_handle.fd = internal_->socket_for_renderer;
+ imc_handle.auto_close = true;
+ handle_for_renderer = imc_handle;
+#endif
+
+ const ChildProcessData& data = process_->GetData();
+ SendMessageToRenderer(
+ NaClLaunchResult(handle_for_renderer,
+ channel_handle,
+ base::GetProcId(data.handle),
+ data.id),
+ std::string() /* error_message */);
+ internal_->socket_for_renderer = NACL_INVALID_HANDLE;
+ return true;
+}
+
+void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) {
+ LOG(ERROR) << "NaCl process launch failed: " << error_message;
+ SendMessageToRenderer(NaClLaunchResult(), error_message);
+}
+
+void NaClProcessHost::SendMessageToRenderer(
+ const NaClLaunchResult& result,
+ const std::string& error_message) {
+ DCHECK(nacl_host_message_filter_);
+ DCHECK(reply_msg_);
+ if (nacl_host_message_filter_ != NULL && reply_msg_ != NULL) {
+ NaClHostMsg_LaunchNaCl::WriteReplyParams(
+ reply_msg_, result, error_message);
+ nacl_host_message_filter_->Send(reply_msg_);
+ nacl_host_message_filter_ = NULL;
+ reply_msg_ = NULL;
+ }
+}
+
+// TCP port we chose for NaCl debug stub. It can be any other number.
+static const int kDebugStubPort = 4014;
+
+#if defined(OS_POSIX)
+net::SocketDescriptor NaClProcessHost::GetDebugStubSocketHandle() {
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+ net::SocketDescriptor s = net::kInvalidSocket;
+ // We allocate currently unused TCP port for debug stub tests. The port
+ // number is passed to the test via debug stub port listener.
+ if (nacl_browser->HasGdbDebugStubPortListener()) {
+ int port;
+ s = net::TCPListenSocket::CreateAndBindAnyPort("127.0.0.1", &port);
+ if (s != net::kInvalidSocket) {
+ nacl_browser->FireGdbDebugStubPortOpened(port);
+ }
+ } else {
+ s = net::TCPListenSocket::CreateAndBind("127.0.0.1", kDebugStubPort);
+ }
+ if (s == net::kInvalidSocket) {
+ LOG(ERROR) << "failed to open socket for debug stub";
+ return net::kInvalidSocket;
+ }
+ if (listen(s, 1)) {
+ LOG(ERROR) << "listen() failed on debug stub socket";
+ if (HANDLE_EINTR(close(s)) < 0)
+ PLOG(ERROR) << "failed to close debug stub socket";
+ return net::kInvalidSocket;
+ }
+ return s;
+}
+#endif
+
+bool NaClProcessHost::StartNaClExecution() {
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+
+ NaClStartParams params;
+ params.validation_cache_enabled = nacl_browser->ValidationCacheIsEnabled();
+ params.validation_cache_key = nacl_browser->GetValidationCacheKey();
+ params.version = NaClBrowser::GetDelegate()->GetVersionString();
+ params.enable_exception_handling = enable_exception_handling_;
+ params.enable_debug_stub = enable_debug_stub_ &&
+ NaClBrowser::GetDelegate()->URLMatchesDebugPatterns(manifest_url_);
+ // Enable PPAPI proxy channel creation only for renderer processes.
+ params.enable_ipc_proxy = enable_ppapi_proxy();
+ params.uses_irt = uses_irt_;
+ params.enable_dyncode_syscalls = enable_dyncode_syscalls_;
+
+ const ChildProcessData& data = process_->GetData();
+ if (!ShareHandleToSelLdr(data.handle,
+ internal_->socket_for_sel_ldr, true,
+ &params.handles)) {
+ return false;
+ }
+
+ if (params.uses_irt) {
+ base::PlatformFile irt_file = nacl_browser->IrtFile();
+ CHECK_NE(irt_file, base::kInvalidPlatformFileValue);
+ // Send over the IRT file handle. We don't close our own copy!
+ if (!ShareHandleToSelLdr(data.handle, irt_file, false, &params.handles))
+ return false;
+ }
+
+#if defined(OS_MACOSX)
+ // For dynamic loading support, NaCl requires a file descriptor that
+ // was created in /tmp, since those created with shm_open() are not
+ // mappable with PROT_EXEC. Rather than requiring an extra IPC
+ // round trip out of the sandbox, we create an FD here.
+ base::SharedMemory memory_buffer;
+ base::SharedMemoryCreateOptions options;
+ options.size = 1;
+ options.executable = true;
+ if (!memory_buffer.Create(options)) {
+ DLOG(ERROR) << "Failed to allocate memory buffer";
+ return false;
+ }
+ FileDescriptor memory_fd;
+ memory_fd.fd = dup(memory_buffer.handle().fd);
+ if (memory_fd.fd < 0) {
+ DLOG(ERROR) << "Failed to dup() a file descriptor";
+ return false;
+ }
+ memory_fd.auto_close = true;
+ params.handles.push_back(memory_fd);
+#endif
+
+#if defined(OS_POSIX)
+ if (params.enable_debug_stub) {
+ net::SocketDescriptor server_bound_socket = GetDebugStubSocketHandle();
+ if (server_bound_socket != net::kInvalidSocket) {
+ params.debug_stub_server_bound_socket =
+ FileDescriptor(server_bound_socket, true);
+ }
+ }
+#endif
+
+ process_->Send(new NaClProcessMsg_Start(params));
+
+ internal_->socket_for_sel_ldr = NACL_INVALID_HANDLE;
+ return true;
+}
+
+bool NaClProcessHost::SendStart() {
+ if (!enable_ppapi_proxy()) {
+ if (!ReplyToRenderer(IPC::ChannelHandle()))
+ return false;
+ }
+ return StartNaClExecution();
+}
+
+// This method is called when NaClProcessHostMsg_PpapiChannelCreated is
+// received or PpapiHostMsg_ChannelCreated is forwarded by our plugin
+// listener.
+void NaClProcessHost::OnPpapiChannelCreated(
+ const IPC::ChannelHandle& channel_handle) {
+ // Only renderer processes should create a channel.
+ DCHECK(enable_ppapi_proxy());
+ // If the proxy channel is null, this must be the initial NaCl-Browser IPC
+ // channel.
+ if (!ipc_proxy_channel_.get()) {
+ DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type);
+
+ ipc_proxy_channel_.reset(
+ new IPC::ChannelProxy(channel_handle,
+ IPC::Channel::MODE_CLIENT,
+ &ipc_plugin_listener_,
+ base::MessageLoopProxy::current().get()));
+ // Create the browser ppapi host and enable PPAPI message dispatching to the
+ // browser process.
+ ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess(
+ ipc_proxy_channel_.get(), // sender
+ permissions_,
+ process_->GetData().handle,
+ ipc_proxy_channel_.get(),
+ nacl_host_message_filter_->render_process_id(),
+ render_view_id_,
+ profile_directory_));
+
+ ppapi::PpapiNaClChannelArgs args;
+ args.off_the_record = nacl_host_message_filter_->off_the_record();
+ args.permissions = permissions_;
+ CommandLine* cmdline = CommandLine::ForCurrentProcess();
+ DCHECK(cmdline);
+ std::string flag_whitelist[] = {switches::kV, switches::kVModule};
+ for (size_t i = 0; i < arraysize(flag_whitelist); ++i) {
+ std::string value = cmdline->GetSwitchValueASCII(flag_whitelist[i]);
+ if (!value.empty()) {
+ args.switch_names.push_back(flag_whitelist[i]);
+ args.switch_values.push_back(value);
+ }
+ }
+
+ ppapi_host_->GetPpapiHost()->AddHostFactoryFilter(
+ scoped_ptr<ppapi::host::HostFactory>(
+ NaClBrowser::GetDelegate()->CreatePpapiHostFactory(
+ ppapi_host_.get())));
+
+ // Send a message to create the NaCl-Renderer channel. The handle is just
+ // a place holder.
+ ipc_proxy_channel_->Send(
+ new PpapiMsg_CreateNaClChannel(
+ nacl_host_message_filter_->render_process_id(),
+ args,
+ SerializedHandle(SerializedHandle::CHANNEL_HANDLE,
+ IPC::InvalidPlatformFileForTransit())));
+ } else if (reply_msg_) {
+ // Otherwise, this must be a renderer channel.
+ ReplyToRenderer(channel_handle);
+ } else {
+ // Attempt to open more than 1 renderer channel is not supported.
+ // Shut down the NaCl process.
+ process_->GetHost()->ForceShutdown();
+ }
+}
+
+bool NaClProcessHost::OnUntrustedMessageForwarded(const IPC::Message& msg) {
+ // Handle messages that have been forwarded from our PluginListener.
+ // These messages come from untrusted code so should be handled with care.
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(NaClProcessHost, msg)
+ IPC_MESSAGE_HANDLER(PpapiHostMsg_ChannelCreated,
+ OnPpapiChannelCreated)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+bool NaClProcessHost::StartWithLaunchedProcess() {
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+
+ if (nacl_browser->IsReady()) {
+ return SendStart();
+ } else if (nacl_browser->IsOk()) {
+ nacl_browser->WaitForResources(
+ base::Bind(&NaClProcessHost::OnResourcesReady,
+ weak_factory_.GetWeakPtr()));
+ return true;
+ } else {
+ SendErrorToRenderer("previously failed to acquire shared resources");
+ return false;
+ }
+}
+
+void NaClProcessHost::OnQueryKnownToValidate(const std::string& signature,
+ bool* result) {
+ NaClBrowser* nacl_browser = NaClBrowser::GetInstance();
+ *result = nacl_browser->QueryKnownToValidate(signature, off_the_record_);
+}
+
+void NaClProcessHost::OnSetKnownToValidate(const std::string& signature) {
+ NaClBrowser::GetInstance()->SetKnownToValidate(
+ signature, off_the_record_);
+}
+
+void NaClProcessHost::FileResolved(
+ base::PlatformFile* file,
+ const base::FilePath& file_path,
+ IPC::Message* reply_msg) {
+ if (*file != base::kInvalidPlatformFileValue) {
+ IPC::PlatformFileForTransit handle = IPC::GetFileHandleForProcess(
+ *file,
+ process_->GetData().handle,
+ true /* close_source */);
+ NaClProcessMsg_ResolveFileToken::WriteReplyParams(
+ reply_msg,
+ handle,
+ file_path);
+ } else {
+ NaClProcessMsg_ResolveFileToken::WriteReplyParams(
+ reply_msg,
+ IPC::InvalidPlatformFileForTransit(),
+ base::FilePath(FILE_PATH_LITERAL("")));
+ }
+ Send(reply_msg);
+}
+
+void NaClProcessHost::OnResolveFileToken(uint64 file_token_lo,
+ uint64 file_token_hi,
+ IPC::Message* reply_msg) {
+ // Was the file registered?
+ //
+ // Note that the file path cache is of bounded size, and old entries can get
+ // evicted. If a large number of NaCl modules are being launched at once,
+ // resolving the file_token may fail because the path cache was thrashed
+ // while the file_token was in flight. In this case the query fails, and we
+ // need to fall back to the slower path.
+ //
+ // However: each NaCl process will consume 2-3 entries as it starts up, this
+ // means that eviction will not happen unless you start up 33+ NaCl processes
+ // at the same time, and this still requires worst-case timing. As a
+ // practical matter, no entries should be evicted prematurely.
+ // The cache itself should take ~ (150 characters * 2 bytes/char + ~60 bytes
+ // data structure overhead) * 100 = 35k when full, so making it bigger should
+ // not be a problem, if needed.
+ //
+ // Each NaCl process will consume 2-3 entries because the manifest and main
+ // nexe are currently not resolved. Shared libraries will be resolved. They
+ // will be loaded sequentially, so they will only consume a single entry
+ // while the load is in flight.
+ //
+ // TODO(ncbray): track behavior with UMA. If entries are getting evicted or
+ // bogus keys are getting queried, this would be good to know.
+ base::FilePath file_path;
+ if (!NaClBrowser::GetInstance()->GetFilePath(
+ file_token_lo, file_token_hi, &file_path)) {
+ NaClProcessMsg_ResolveFileToken::WriteReplyParams(
+ reply_msg,
+ IPC::InvalidPlatformFileForTransit(),
+ base::FilePath(FILE_PATH_LITERAL("")));
+ Send(reply_msg);
+ return;
+ }
+
+ // Scratch space to share between the callbacks.
+ base::PlatformFile* data = new base::PlatformFile();
+
+ // Open the file.
+ if (!content::BrowserThread::PostBlockingPoolTaskAndReply(
+ FROM_HERE,
+ base::Bind(OpenNaClExecutableImpl,
+ file_path, data),
+ base::Bind(&NaClProcessHost::FileResolved,
+ weak_factory_.GetWeakPtr(),
+ base::Owned(data),
+ file_path,
+ reply_msg))) {
+ NaClProcessMsg_ResolveFileToken::WriteReplyParams(
+ reply_msg,
+ IPC::InvalidPlatformFileForTransit(),
+ base::FilePath(FILE_PATH_LITERAL("")));
+ Send(reply_msg);
+ }
+}
+
+#if defined(OS_WIN)
+void NaClProcessHost::OnAttachDebugExceptionHandler(const std::string& info,
+ IPC::Message* reply_msg) {
+ if (!AttachDebugExceptionHandler(info, reply_msg)) {
+ // Send failure message.
+ NaClProcessMsg_AttachDebugExceptionHandler::WriteReplyParams(reply_msg,
+ false);
+ Send(reply_msg);
+ }
+}
+
+bool NaClProcessHost::AttachDebugExceptionHandler(const std::string& info,
+ IPC::Message* reply_msg) {
+ if (!enable_exception_handling_ && !enable_debug_stub_) {
+ DLOG(ERROR) <<
+ "Debug exception handler requested by NaCl process when not enabled";
+ return false;
+ }
+ if (debug_exception_handler_requested_) {
+ // The NaCl process should not request this multiple times.
+ DLOG(ERROR) << "Multiple AttachDebugExceptionHandler requests received";
+ return false;
+ }
+ debug_exception_handler_requested_ = true;
+
+ base::ProcessId nacl_pid = base::GetProcId(process_->GetData().handle);
+ base::win::ScopedHandle process_handle;
+ // We cannot use process_->GetData().handle because it does not have
+ // the necessary access rights. We open the new handle here rather
+ // than in the NaCl broker process in case the NaCl loader process
+ // dies before the NaCl broker process receives the message we send.
+ // The debug exception handler uses DebugActiveProcess() to attach,
+ // but this takes a PID. We need to prevent the NaCl loader's PID
+ // from being reused before DebugActiveProcess() is called, and
+ // holding a process handle open achieves this.
+ if (!base::OpenProcessHandleWithAccess(
+ nacl_pid,
+ base::kProcessAccessQueryInformation |
+ base::kProcessAccessSuspendResume |
+ base::kProcessAccessTerminate |
+ base::kProcessAccessVMOperation |
+ base::kProcessAccessVMRead |
+ base::kProcessAccessVMWrite |
+ base::kProcessAccessDuplicateHandle |
+ base::kProcessAccessWaitForTermination,
+ process_handle.Receive())) {
+ LOG(ERROR) << "Failed to get process handle";
+ return false;
+ }
+
+ attach_debug_exception_handler_reply_msg_.reset(reply_msg);
+ // If the NaCl loader is 64-bit, the process running its debug
+ // exception handler must be 64-bit too, so we use the 64-bit NaCl
+ // broker process for this. Otherwise, on a 32-bit system, we use
+ // the 32-bit browser process to run the debug exception handler.
+ if (RunningOnWOW64()) {
+ return NaClBrokerService::GetInstance()->LaunchDebugExceptionHandler(
+ weak_factory_.GetWeakPtr(), nacl_pid, process_handle, info);
+ } else {
+ NaClStartDebugExceptionHandlerThread(
+ process_handle.Take(), info,
+ base::MessageLoopProxy::current(),
+ base::Bind(&NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker,
+ weak_factory_.GetWeakPtr()));
+ return true;
+ }
+}
+#endif
+
+} // namespace nacl
diff --git a/components/nacl/browser/nacl_process_host.h b/components/nacl/browser/nacl_process_host.h
new file mode 100644
index 0000000..135fb63
--- /dev/null
+++ b/components/nacl/browser/nacl_process_host.h
@@ -0,0 +1,241 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_
+#define COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_
+
+#include "build/build_config.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/process/process.h"
+#include "components/nacl/common/nacl_types.h"
+#include "content/public/browser/browser_child_process_host_delegate.h"
+#include "content/public/browser/browser_child_process_host_iterator.h"
+#include "ipc/ipc_channel_handle.h"
+#include "net/socket/socket_descriptor.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"
+#include "url/gurl.h"
+
+class CommandLine;
+
+namespace content {
+class BrowserChildProcessHost;
+class BrowserPpapiHost;
+}
+
+namespace IPC {
+class ChannelProxy;
+}
+
+namespace nacl {
+
+class NaClHostMessageFilter;
+void* AllocateAddressSpaceASLR(base::ProcessHandle process, size_t size);
+
+// Represents the browser side of the browser <--> NaCl communication
+// channel. There will be one NaClProcessHost per NaCl process
+// The browser is responsible for starting the NaCl process
+// when requested by the renderer.
+// After that, most of the communication is directly between NaCl plugin
+// running in the renderer and NaCl processes.
+class NaClProcessHost : public content::BrowserChildProcessHostDelegate {
+ public:
+ // manifest_url: the URL of the manifest of the Native Client plugin being
+ // executed.
+ // render_view_id: RenderView routing id, to control access to private APIs.
+ // permission_bits: controls which interfaces the NaCl plugin can use.
+ // uses_irt: whether the launched process should use the IRT.
+ // enable_dyncode_syscalls: whether the launched process should allow dyncode
+ // and mmap with PROT_EXEC.
+ // enable_exception_handling: whether the launched process should allow
+ // hardware exception handling.
+ // enable_crash_throttling: whether a crash of this process contributes
+ // to the crash throttling statistics, and also
+ // whether this process should not start when too
+ // many crashes have been observed.
+ // off_the_record: was the process launched from an incognito renderer?
+ // profile_directory: is the path of current profile directory.
+ NaClProcessHost(const GURL& manifest_url,
+ int render_view_id,
+ uint32 permission_bits,
+ bool uses_irt,
+ bool enable_dyncode_syscalls,
+ bool enable_exception_handling,
+ bool enable_crash_throttling,
+ bool off_the_record,
+ const base::FilePath& profile_directory);
+ virtual ~NaClProcessHost();
+
+ virtual void OnProcessCrashed(int exit_status) OVERRIDE;
+
+ // Do any minimal work that must be done at browser startup.
+ static void EarlyStartup();
+
+ // Initialize the new NaCl process. Result is returned by sending ipc
+ // message reply_msg.
+ void Launch(NaClHostMessageFilter* nacl_host_message_filter,
+ IPC::Message* reply_msg,
+ const base::FilePath& manifest_path);
+
+ virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
+
+#if defined(OS_WIN)
+ void OnProcessLaunchedByBroker(base::ProcessHandle handle);
+ void OnDebugExceptionHandlerLaunchedByBroker(bool success);
+#endif
+
+ bool Send(IPC::Message* msg);
+
+ content::BrowserChildProcessHost* process() { return process_.get(); }
+ content::BrowserPpapiHost* browser_ppapi_host() { return ppapi_host_.get(); }
+
+ private:
+ friend class PluginListener;
+
+ // Internal class that holds the NaClHandle objecs so that
+ // nacl_process_host.h doesn't include NaCl headers. Needed since it's
+ // included by src\content, which can't depend on the NaCl gyp file because it
+ // depends on chrome.gyp (circular dependency).
+ struct NaClInternal;
+
+ // PluginListener that forwards any messages from untrusted code that aren't
+ // handled by the PepperMessageFilter to us.
+ class PluginListener : public IPC::Listener {
+ public:
+ explicit PluginListener(NaClProcessHost* host);
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ private:
+ // Non-owning pointer so we can forward messages to the host.
+ NaClProcessHost* host_;
+ };
+
+ bool LaunchNaClGdb();
+
+#if defined(OS_POSIX)
+ // Create bound TCP socket in the browser process so that the NaCl GDB debug
+ // stub can use it to accept incoming connections even when the Chrome sandbox
+ // is enabled.
+ net::SocketDescriptor GetDebugStubSocketHandle();
+#endif
+ bool LaunchSelLdr();
+
+ // BrowserChildProcessHostDelegate implementation:
+ virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE;
+ virtual void OnProcessLaunched() OVERRIDE;
+
+ void OnResourcesReady();
+
+ // Enable the PPAPI proxy only for NaCl processes corresponding to a renderer.
+ bool enable_ppapi_proxy() { return render_view_id_ != 0; }
+
+ // Sends the reply message to the renderer who is waiting for the plugin
+ // to load. Returns true on success.
+ bool ReplyToRenderer(const IPC::ChannelHandle& channel_handle);
+
+ // Sends the reply with error message to the renderer.
+ void SendErrorToRenderer(const std::string& error_message);
+
+ // Sends the reply message to the renderer. Either result or
+ // error message must be empty.
+ void SendMessageToRenderer(const NaClLaunchResult& result,
+ const std::string& error_message);
+
+ // Sends the message to the NaCl process to load the plugin. Returns true
+ // on success.
+ bool StartNaClExecution();
+
+ // Called once all initialization is complete and the NaCl process is
+ // ready to go. Returns true on success.
+ bool SendStart();
+
+ // Does post-process-launching tasks for starting the NaCl process once
+ // we have a connection.
+ //
+ // Returns false on failure.
+ bool StartWithLaunchedProcess();
+
+ // Message handlers for validation caching.
+ void OnQueryKnownToValidate(const std::string& signature, bool* result);
+ void OnSetKnownToValidate(const std::string& signature);
+ void OnResolveFileToken(uint64 file_token_lo, uint64 file_token_hi,
+ IPC::Message* reply_msg);
+ void FileResolved(base::PlatformFile* file, const base::FilePath& file_path,
+ IPC::Message* reply_msg);
+
+#if defined(OS_WIN)
+ // Message handler for Windows hardware exception handling.
+ void OnAttachDebugExceptionHandler(const std::string& info,
+ IPC::Message* reply_msg);
+ bool AttachDebugExceptionHandler(const std::string& info,
+ IPC::Message* reply_msg);
+#endif
+
+ // Called when a PPAPI IPC channel has been created.
+ void OnPpapiChannelCreated(const IPC::ChannelHandle& channel_handle);
+ // Called by PluginListener, so messages from the untrusted side of
+ // the IPC proxy can be handled.
+ bool OnUntrustedMessageForwarded(const IPC::Message& msg);
+
+ GURL manifest_url_;
+ ppapi::PpapiPermissions permissions_;
+
+#if defined(OS_WIN)
+ // This field becomes true when the broker successfully launched
+ // the NaCl loader.
+ bool process_launched_by_broker_;
+#endif
+ // The NaClHostMessageFilter that requested this NaCl process. We use
+ // this for sending the reply once the process has started.
+ scoped_refptr<NaClHostMessageFilter> nacl_host_message_filter_;
+
+ // The reply message to send. We must always send this message when the
+ // sub-process either succeeds or fails to unblock the renderer waiting for
+ // the reply. NULL when there is no reply to send.
+ IPC::Message* reply_msg_;
+#if defined(OS_WIN)
+ bool debug_exception_handler_requested_;
+ scoped_ptr<IPC::Message> attach_debug_exception_handler_reply_msg_;
+#endif
+
+ // The file path to the manifest is passed to nacl-gdb when it is used to
+ // debug the NaCl loader.
+ base::FilePath manifest_path_;
+
+ // Socket pairs for the NaCl process and renderer.
+ scoped_ptr<NaClInternal> internal_;
+
+ base::WeakPtrFactory<NaClProcessHost> weak_factory_;
+
+ scoped_ptr<content::BrowserChildProcessHost> process_;
+
+ bool uses_irt_;
+
+ bool enable_debug_stub_;
+ bool enable_dyncode_syscalls_;
+ bool enable_exception_handling_;
+ bool enable_crash_throttling_;
+
+ bool off_the_record_;
+
+ const base::FilePath profile_directory_;
+
+ // Channel proxy to terminate the NaCl-Browser PPAPI channel.
+ scoped_ptr<IPC::ChannelProxy> ipc_proxy_channel_;
+ // Plugin listener, to forward browser channel messages to us.
+ PluginListener ipc_plugin_listener_;
+ // Browser host for plugin process.
+ scoped_ptr<content::BrowserPpapiHost> ppapi_host_;
+
+ int render_view_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(NaClProcessHost);
+};
+
+} // namespace nacl
+
+#endif // COMPONENTS_NACL_BROWSER_NACL_PROCESS_HOST_H_
diff --git a/components/nacl/browser/nacl_process_host_unittest.cc b/components/nacl/browser/nacl_process_host_unittest.cc
new file mode 100644
index 0000000..0d97ed6
--- /dev/null
+++ b/components/nacl/browser/nacl_process_host_unittest.cc
@@ -0,0 +1,15 @@
+// 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 "components/nacl/browser/nacl_process_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+TEST(NaClProcessHostTest, AddressSpaceAllocation) {
+ size_t size = 1 << 20; // 1 MB
+ void* addr = nacl::AllocateAddressSpaceASLR(GetCurrentProcess(), size);
+ bool success = VirtualFree(addr, 0, MEM_RELEASE);
+ ASSERT_TRUE(success);
+}
+#endif
diff --git a/components/nacl/browser/pnacl_host.cc b/components/nacl/browser/pnacl_host.cc
new file mode 100644
index 0000000..2ed4d5d
--- /dev/null
+++ b/components/nacl/browser/pnacl_host.cc
@@ -0,0 +1,634 @@
+// 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 "components/nacl/browser/pnacl_host.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.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 "components/nacl/browser/nacl_browser.h"
+#include "components/nacl/browser/pnacl_translation_cache.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+using content::BrowserThread;
+
+namespace {
+static const base::FilePath::CharType kTranslationCacheDirectoryName[] =
+ FILE_PATH_LITERAL("PnaclTranslationCache");
+// Delay to wait for initialization of the cache backend
+static const int kTranslationCacheInitializationDelayMs = 20;
+}
+
+namespace pnacl {
+
+PnaclHost::PnaclHost()
+ : pending_backend_operations_(0),
+ cache_state_(CacheUninitialized),
+ weak_factory_(this) {}
+
+PnaclHost::~PnaclHost() {
+ // When PnaclHost is destroyed, it's too late to post anything to the cache
+ // thread (it will hang shutdown). So just leak the cache backend.
+ pnacl::PnaclTranslationCache* cache = disk_cache_.release();
+ (void)cache;
+}
+
+PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); }
+
+PnaclHost::PendingTranslation::PendingTranslation()
+ : process_handle(base::kNullProcessHandle),
+ render_view_id(0),
+ nexe_fd(base::kInvalidPlatformFileValue),
+ got_nexe_fd(false),
+ got_cache_reply(false),
+ got_cache_hit(false),
+ is_incognito(false),
+ callback(NexeFdCallback()),
+ cache_info(nacl::PnaclCacheInfo()) {}
+PnaclHost::PendingTranslation::~PendingTranslation() {}
+
+bool PnaclHost::TranslationMayBeCached(
+ const PendingTranslationMap::iterator& entry) {
+ return !entry->second.is_incognito &&
+ !entry->second.cache_info.has_no_store_header;
+}
+
+/////////////////////////////////////// Initialization
+
+static base::FilePath GetCachePath() {
+ NaClBrowserDelegate* browser_delegate = nacl::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 net_error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // If the cache was cleared before the load completed, ignore.
+ if (cache_state_ == CacheReady)
+ return;
+ if (net_error != net::OK) {
+ // This will cause the cache to attempt to re-init on the next call to
+ // GetNexeFd.
+ cache_state_ = CacheUninitialized;
+ } else {
+ cache_state_ = CacheReady;
+ }
+}
+
+void PnaclHost::Init() {
+ // Extra check that we're on the real IO thread since this version of
+ // Init isn't used in unit tests.
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ 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;
+ int rv = disk_cache_->InitOnDisk(
+ cache_path,
+ base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING)
+ OnCacheInitialized(rv);
+}
+
+// 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;
+ int rv = disk_cache_->InitInMemory(
+ base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
+ if (rv != net::ERR_IO_PENDING)
+ OnCacheInitialized(rv);
+}
+
+///////////////////////////////////////// Temp files
+
+// Create a temporary file on the blocking pool
+// static
+void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
+ TempFileCallback cb) {
+ DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+
+ base::FilePath file_path;
+ base::PlatformFile file_handle(base::kInvalidPlatformFileValue);
+ bool rv = temp_dir.empty()
+ ? file_util::CreateTemporaryFile(&file_path)
+ : file_util::CreateTemporaryFileInDir(temp_dir, &file_path);
+ if (!rv) {
+ PLOG(ERROR) << "Temp file creation failed.";
+ } else {
+ base::PlatformFileError error;
+ 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) {
+ PLOG(ERROR) << "Temp file open failed: " << error;
+ file_handle = base::kInvalidPlatformFileValue;
+ }
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle));
+}
+
+void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
+ if (!BrowserThread::PostBlockingPoolSequencedTask(
+ "PnaclHostCreateTempFile",
+ FROM_HERE,
+ base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ cb.Run(base::kInvalidPlatformFileValue);
+ }
+}
+
+///////////////////////////////////////// GetNexeFd implementation
+////////////////////// Common steps
+
+void PnaclHost::GetNexeFd(int render_process_id,
+ int render_view_id,
+ int pp_instance,
+ bool is_incognito,
+ const nacl::PnaclCacheInfo& cache_info,
+ const NexeFdCallback& cb) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cache_state_ == CacheUninitialized) {
+ Init();
+ }
+ if (cache_state_ != CacheReady) {
+ // If the backend hasn't yet initialized, try the request again later.
+ BrowserThread::PostDelayedTask(BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&PnaclHost::GetNexeFd,
+ weak_factory_.GetWeakPtr(),
+ render_process_id,
+ render_view_id,
+ pp_instance,
+ is_incognito,
+ cache_info,
+ cb),
+ base::TimeDelta::FromMilliseconds(
+ kTranslationCacheInitializationDelayMs));
+ return;
+ }
+
+ TranslationID id(render_process_id, pp_instance);
+ PendingTranslationMap::iterator entry = pending_translations_.find(id);
+ if (entry != pending_translations_.end()) {
+ // Existing translation must have been abandonded. Clean it up.
+ LOG(ERROR) << "GetNexeFd for already-pending translation";
+ pending_translations_.erase(entry);
+ }
+
+ std::string cache_key(disk_cache_->GetKey(cache_info));
+ if (cache_key.empty()) {
+ LOG(ERROR) << "GetNexeFd: Invalid cache info";
+ cb.Run(base::kInvalidPlatformFileValue, false);
+ return;
+ }
+
+ PendingTranslation pt;
+ pt.render_view_id = render_view_id;
+ pt.callback = cb;
+ pt.cache_info = cache_info;
+ pt.cache_key = cache_key;
+ pt.is_incognito = is_incognito;
+ pending_translations_[id] = pt;
+ SendCacheQueryAndTempFileRequest(cache_key, id);
+}
+
+// Dispatch the cache read request and the temp file creation request
+// simultaneously; currently we need a temp file regardless of whether the
+// request hits.
+void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
+ const TranslationID& id) {
+ pending_backend_operations_++;
+ disk_cache_->GetNexe(
+ cache_key,
+ base::Bind(
+ &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id));
+
+ CreateTemporaryFile(
+ base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id));
+}
+
+// Callback from the translation cache query. |id| is bound from
+// SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
+// our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
+// by PnaclTranslationCache and now belongs to PnaclHost.
+// (Bound callbacks must re-lookup the TranslationID because the translation
+// could be cancelled before they get called).
+void PnaclHost::OnCacheQueryReturn(
+ const TranslationID& id,
+ int net_error,
+ scoped_refptr<net::DrainableIOBuffer> buffer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ pending_backend_operations_--;
+ PendingTranslationMap::iterator entry(pending_translations_.find(id));
+ if (entry == pending_translations_.end()) {
+ LOG(ERROR) << "OnCacheQueryReturn: id not found";
+ DeInitIfSafe();
+ return;
+ }
+ PendingTranslation* pt = &entry->second;
+ pt->got_cache_reply = true;
+ pt->got_cache_hit = (net_error == net::OK);
+ if (pt->got_cache_hit)
+ pt->nexe_read_buffer = buffer;
+ CheckCacheQueryReady(entry);
+}
+
+// Callback from temp file creation. |id| is bound from
+// SendCacheQueryAndTempFileRequest, and fd is the created file descriptor.
+// If there was an error, fd is kInvalidPlatformFileValue.
+// (Bound callbacks must re-lookup the TranslationID because the translation
+// could be cancelled before they get called).
+void PnaclHost::OnTempFileReturn(const TranslationID& id,
+ base::PlatformFile fd) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ PendingTranslationMap::iterator entry(pending_translations_.find(id));
+ if (entry == pending_translations_.end()) {
+ // The renderer may have signaled an error or closed while the temp
+ // file was being created.
+ LOG(ERROR) << "OnTempFileReturn: id not found";
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
+ return;
+ }
+ if (fd == base::kInvalidPlatformFileValue) {
+ // This translation will fail, but we need to retry any translation
+ // waiting for its result.
+ LOG(ERROR) << "OnTempFileReturn: temp file creation failed";
+ std::string key(entry->second.cache_key);
+ entry->second.callback.Run(fd, false);
+ bool may_be_cached = TranslationMayBeCached(entry);
+ pending_translations_.erase(entry);
+ // No translations will be waiting for entries that will not be stored.
+ if (may_be_cached)
+ RequeryMatchingTranslations(key);
+ return;
+ }
+ PendingTranslation* pt = &entry->second;
+ pt->got_nexe_fd = true;
+ pt->nexe_fd = fd;
+ CheckCacheQueryReady(entry);
+}
+
+// Check whether both the cache query and the temp file have returned, and check
+// whether we actually got a hit or not.
+void PnaclHost::CheckCacheQueryReady(
+ const PendingTranslationMap::iterator& entry) {
+ PendingTranslation* pt = &entry->second;
+ if (!(pt->got_cache_reply && pt->got_nexe_fd))
+ return;
+ if (!pt->got_cache_hit) {
+ // Check if there is already a pending translation for this file. If there
+ // is, we will wait for it to come back, to avoid redundant translations.
+ for (PendingTranslationMap::iterator it = pending_translations_.begin();
+ it != pending_translations_.end();
+ ++it) {
+ // Another translation matches if it's a request for the same file,
+ if (it->second.cache_key == entry->second.cache_key &&
+ // and it's not this translation,
+ it->first != entry->first &&
+ // and it can be stored in the cache,
+ TranslationMayBeCached(it) &&
+ // and it's already gotten past this check and returned the miss.
+ it->second.got_cache_reply &&
+ it->second.got_nexe_fd) {
+ return;
+ }
+ }
+ ReturnMiss(entry);
+ return;
+ }
+
+ if (!base::PostTaskAndReplyWithResult(
+ BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(
+ &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer),
+ base::Bind(&PnaclHost::OnBufferCopiedToTempFile,
+ weak_factory_.GetWeakPtr(),
+ entry->first))) {
+ pt->callback.Run(base::kInvalidPlatformFileValue, false);
+ }
+}
+
+//////////////////// GetNexeFd miss path
+// Return the temp fd to the renderer, reporting a miss.
+void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
+ // Return the fd
+ PendingTranslation* pt = &entry->second;
+ NexeFdCallback cb(pt->callback);
+ if (pt->nexe_fd == base::kInvalidPlatformFileValue) {
+ // Bad FD is unrecoverable, so clear out the entry
+ pending_translations_.erase(entry);
+ }
+ cb.Run(pt->nexe_fd, false);
+}
+
+// On error, just return a null refptr.
+// static
+scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
+ base::PlatformFile fd) {
+ base::PlatformFileInfo info;
+ scoped_refptr<net::DrainableIOBuffer> buffer;
+ bool error = false;
+ if (!base::GetPlatformFileInfo(fd, &info) ||
+ info.size >= std::numeric_limits<int>::max()) {
+ PLOG(ERROR) << "GetPlatformFileInfo failed";
+ error = true;
+ } else {
+ buffer = new net::DrainableIOBuffer(
+ new net::IOBuffer(static_cast<int>(info.size)), info.size);
+ if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) !=
+ info.size) {
+ PLOG(ERROR) << "CopyFileToBuffer file read failed";
+ error = true;
+ }
+ }
+ if (error) {
+ buffer = NULL;
+ }
+ base::ClosePlatformFile(fd);
+ return buffer;
+}
+
+// Called by the renderer in the miss path to report a finished translation
+void PnaclHost::TranslationFinished(int render_process_id,
+ int pp_instance,
+ bool success) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cache_state_ != CacheReady)
+ return;
+ TranslationID id(render_process_id, pp_instance);
+ PendingTranslationMap::iterator entry(pending_translations_.find(id));
+ if (entry == pending_translations_.end()) {
+ LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id
+ << "," << pp_instance << " not found.";
+ return;
+ }
+ bool store_nexe = true;
+ // If this is a premature response (i.e. we haven't returned a temp file
+ // yet) or if it's an unsuccessful translation, or if we are incognito,
+ // don't store in the cache.
+ // TODO(dschuff): use a separate in-memory cache for incognito
+ // translations.
+ if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
+ !success || !TranslationMayBeCached(entry)) {
+ store_nexe = false;
+ } else if (!base::PostTaskAndReplyWithResult(
+ BrowserThread::GetBlockingPool(),
+ FROM_HERE,
+ base::Bind(&PnaclHost::CopyFileToBuffer,
+ entry->second.nexe_fd),
+ base::Bind(&PnaclHost::StoreTranslatedNexe,
+ weak_factory_.GetWeakPtr(),
+ id))) {
+ store_nexe = false;
+ }
+
+ if (!store_nexe) {
+ // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
+ if (entry->second.got_nexe_fd) {
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(base::ClosePlatformFile),
+ entry->second.nexe_fd));
+ }
+ pending_translations_.erase(entry);
+ }
+}
+
+// Store the translated nexe in the translation cache. Called back with the
+// TranslationID from the host and the result of CopyFileToBuffer.
+// (Bound callbacks must re-lookup the TranslationID because the translation
+// could be cancelled before they get called).
+void PnaclHost::StoreTranslatedNexe(
+ TranslationID id,
+ scoped_refptr<net::DrainableIOBuffer> buffer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cache_state_ != CacheReady)
+ return;
+ PendingTranslationMap::iterator it(pending_translations_.find(id));
+ if (it == pending_translations_.end()) {
+ LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << ","
+ << id.second << " not found.";
+ return;
+ }
+
+ if (buffer.get() == NULL) {
+ LOG(ERROR) << "Error reading translated nexe";
+ return;
+ }
+ pending_backend_operations_++;
+ disk_cache_->StoreNexe(it->second.cache_key,
+ buffer,
+ base::Bind(&PnaclHost::OnTranslatedNexeStored,
+ weak_factory_.GetWeakPtr(),
+ it->first));
+}
+
+// After we know the nexe has been stored, we can clean up, and unblock any
+// outstanding requests for the same file.
+// (Bound callbacks must re-lookup the TranslationID because the translation
+// could be cancelled before they get called).
+void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) {
+ PendingTranslationMap::iterator entry(pending_translations_.find(id));
+ pending_backend_operations_--;
+ if (entry == pending_translations_.end()) {
+ // If the renderer closed while we were storing the nexe, we land here.
+ // Make sure we try to de-init.
+ DeInitIfSafe();
+ return;
+ }
+ std::string key(entry->second.cache_key);
+ pending_translations_.erase(entry);
+ RequeryMatchingTranslations(key);
+}
+
+// Check if any pending translations match |key|. If so, re-issue the cache
+// query. In the overlapped miss case, we expect a hit this time, but a miss
+// is also possible in case of an error.
+void PnaclHost::RequeryMatchingTranslations(const std::string& key) {
+ // Check for outstanding misses to this same file
+ for (PendingTranslationMap::iterator it = pending_translations_.begin();
+ it != pending_translations_.end();
+ ++it) {
+ if (it->second.cache_key == key) {
+ // Re-send the cache read request. This time we expect a hit, but if
+ // something goes wrong, it will just handle it like a miss.
+ it->second.got_cache_reply = false;
+ pending_backend_operations_++;
+ disk_cache_->GetNexe(key,
+ base::Bind(&PnaclHost::OnCacheQueryReturn,
+ weak_factory_.GetWeakPtr(),
+ it->first));
+ }
+ }
+}
+
+//////////////////// GetNexeFd hit path
+
+// static
+int PnaclHost::CopyBufferToFile(base::PlatformFile fd,
+ scoped_refptr<net::DrainableIOBuffer> buffer) {
+ int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size());
+ if (rv == -1)
+ PLOG(ERROR) << "CopyBufferToFile write error";
+ return rv;
+}
+
+void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
+ int file_error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ PendingTranslationMap::iterator entry(pending_translations_.find(id));
+ if (entry == pending_translations_.end()) {
+ return;
+ }
+ if (file_error == -1) {
+ // Write error on the temp file. Request a new file and start over.
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(base::ClosePlatformFile),
+ entry->second.nexe_fd));
+ entry->second.got_nexe_fd = false;
+ CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn,
+ weak_factory_.GetWeakPtr(),
+ entry->first));
+ return;
+ }
+ base::PlatformFile fd = entry->second.nexe_fd;
+ entry->second.callback.Run(fd, true);
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
+ pending_translations_.erase(entry);
+}
+
+///////////////////
+
+void PnaclHost::RendererClosing(int render_process_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cache_state_ != CacheReady)
+ return;
+ 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.
+ BrowserThread::PostBlockingPoolTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(base::ClosePlatformFile),
+ to_erase->second.nexe_fd));
+ std::string key(to_erase->second.cache_key);
+ bool may_be_cached = TranslationMayBeCached(to_erase);
+ pending_translations_.erase(to_erase);
+ // No translations will be waiting for entries that will not be stored.
+ if (may_be_cached)
+ RequeryMatchingTranslations(key);
+ }
+ }
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
+}
+
+////////////////// Cache data removal
+void PnaclHost::ClearTranslationCacheEntriesBetween(
+ base::Time initial_time,
+ base::Time end_time,
+ const base::Closure& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (cache_state_ == CacheUninitialized) {
+ Init();
+ }
+ if (cache_state_ == CacheInitializing) {
+ // If the backend hasn't yet initialized, try the request again later.
+ BrowserThread::PostDelayedTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween,
+ weak_factory_.GetWeakPtr(),
+ initial_time,
+ end_time,
+ callback),
+ base::TimeDelta::FromMilliseconds(
+ kTranslationCacheInitializationDelayMs));
+ return;
+ }
+ pending_backend_operations_++;
+ int rv = disk_cache_->DoomEntriesBetween(
+ initial_time,
+ end_time,
+ base::Bind(
+ &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback));
+ if (rv != net::ERR_IO_PENDING)
+ OnEntriesDoomed(callback, rv);
+}
+
+void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback);
+ pending_backend_operations_--;
+ // When clearing the cache, the UI is blocked on all the cache-clearing
+ // operations, and freeing the backend actually blocks the IO thread. So
+ // instead of calling DeInitIfSafe directly, post it for later.
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
+}
+
+// Destroying the cache backend causes it to post tasks to the cache thread to
+// flush to disk. Because PnaclHost is a singleton, it does not get destroyed
+// until all the browser threads have gone away and it's too late to post
+// anything (attempting to do so hangs shutdown). So we make sure to destroy it
+// when we no longer have any outstanding operations that need it. These include
+// pending translations, cache clear requests, and requests to read or write
+// translated nexes. We check when renderers close, when cache clear requests
+// finish, and when backend operations complete.
+
+// It is not safe to delete the backend while it is initializing, nor if it has
+// outstanding entry open requests; it is in theory safe to delete it with
+// outstanding read/write requests, but because that distinction is hidden
+// inside PnaclTranslationCache, we do not delete the backend if there are any
+// backend requests in flight. As a last resort in the destructor, we just leak
+// the backend to avoid hanging shutdown.
+void PnaclHost::DeInitIfSafe() {
+ DCHECK(pending_backend_operations_ >= 0);
+ if (pending_translations_.empty() && pending_backend_operations_ <= 0) {
+ cache_state_ = CacheUninitialized;
+ disk_cache_.reset();
+ }
+}
+
+} // namespace pnacl
diff --git a/components/nacl/browser/pnacl_host.h b/components/nacl/browser/pnacl_host.h
new file mode 100644
index 0000000..9111603
--- /dev/null
+++ b/components/nacl/browser/pnacl_host.h
@@ -0,0 +1,178 @@
+// 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.
+
+#ifndef COMPONENTS_NACL_BROWSER_PNACL_HOST_H_
+#define COMPONENTS_NACL_BROWSER_PNACL_HOST_H_
+
+#include <map>
+
+#include "base/callback_forward.h"
+#include "base/memory/singleton.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/nacl/browser/nacl_file_host.h"
+#include "components/nacl/common/pnacl_types.h"
+#include "ipc/ipc_platform_file.h"
+
+namespace net {
+class DrainableIOBuffer;
+}
+
+namespace pnacl {
+
+class PnaclHostTest;
+class PnaclTranslationCache;
+
+// Shared state (translation cache) and common utilities (temp file creation)
+// for all PNaCl translations. Unless otherwise specified, all methods should be
+// called on the IO thread.
+class PnaclHost {
+ public:
+ typedef base::Callback<void(base::PlatformFile)> TempFileCallback;
+ typedef base::Callback<void(base::PlatformFile, bool is_hit)> NexeFdCallback;
+
+ static PnaclHost* GetInstance();
+
+ PnaclHost();
+ ~PnaclHost();
+
+ // Initialize cache backend. GetNexeFd will also initialize the backend if
+ // necessary, but calling Init ahead of time will minimize the latency.
+ void Init();
+
+ // Creates a temporary file that will be deleted when the last handle
+ // is closed, or earlier. Returns a PlatformFile handle.
+ void CreateTemporaryFile(TempFileCallback cb);
+
+ // Create a temporary file, which will be deleted by the time the last
+ // handle is closed (or earlier on POSIX systems), to use for the nexe
+ // with the cache information given in |cache_info|. The specific instance
+ // is identified by the combination of |render_process_id| and |pp_instance|.
+ // Returns by calling |cb| with a PlatformFile handle.
+ // If the nexe is already present
+ // in the cache, |is_hit| is set to true and the contents of the nexe
+ // have been copied into the temporary file. Otherwise |is_hit| is set to
+ // false and the temporary file will be writeable.
+ // Currently the implementation is a stub, which always sets is_hit to false
+ // and calls the implementation of CreateTemporaryFile.
+ // If the cache request was a miss, the caller is expected to call
+ // TranslationFinished after it finishes translation to allow the nexe to be
+ // stored in the cache.
+ // The returned temp fd may be closed at any time by PnaclHost, so it should
+ // be duplicated (e.g. with IPC::GetFileHandleForProcess) before the callback
+ // returns.
+ // If |is_incognito| is true, the nexe will not be stored
+ // in the cache, but the renderer is still expected to call
+ // TranslationFinished.
+ void GetNexeFd(int render_process_id,
+ int render_view_id,
+ int pp_instance,
+ bool is_incognito,
+ const nacl::PnaclCacheInfo& cache_info,
+ const NexeFdCallback& cb);
+
+ // Called after the translation of a pexe instance identified by
+ // |render_process_id| and |pp_instance| finishes. If |success| is true,
+ // store the nexe translated for the instance in the cache.
+ void TranslationFinished(int render_process_id,
+ int pp_instance,
+ bool success);
+
+ // Called when the renderer identified by |render_process_id| is closing.
+ // Clean up any outstanding translations for that renderer. If there are no
+ // more pending translations, the backend is freed, allowing it to flush.
+ void RendererClosing(int render_process_id);
+
+ // Doom all entries between |initial_time| and |end_time|. Like disk_cache_,
+ // PnaclHost supports supports unbounded deletes in either direction by using
+ // null Time values for either argument. |callback| will be called on the UI
+ // thread when finished.
+ void ClearTranslationCacheEntriesBetween(base::Time initial_time,
+ base::Time end_time,
+ const base::Closure& callback);
+
+ // Return the number of tracked translations or FD requests currently pending.
+ size_t pending_translations() { return pending_translations_.size(); }
+
+ private:
+ // PnaclHost is a singleton because there is only one translation cache, and
+ // so that the BrowsingDataRemover can clear it even if no translation has
+ // ever been started.
+ friend struct DefaultSingletonTraits<PnaclHost>;
+ friend class pnacl::PnaclHostTest;
+ enum CacheState {
+ CacheUninitialized,
+ CacheInitializing,
+ CacheReady
+ };
+ class PendingTranslation {
+ public:
+ PendingTranslation();
+ ~PendingTranslation();
+ base::ProcessHandle process_handle;
+ int render_view_id;
+ base::PlatformFile nexe_fd;
+ bool got_nexe_fd;
+ bool got_cache_reply;
+ bool got_cache_hit;
+ bool is_incognito;
+ scoped_refptr<net::DrainableIOBuffer> nexe_read_buffer;
+ NexeFdCallback callback;
+ std::string cache_key;
+ nacl::PnaclCacheInfo cache_info;
+ };
+
+ typedef std::pair<int, int> TranslationID;
+ typedef std::map<TranslationID, PendingTranslation> PendingTranslationMap;
+ static bool TranslationMayBeCached(
+ const PendingTranslationMap::iterator& entry);
+
+ void InitForTest(base::FilePath temp_dir);
+ void OnCacheInitialized(int net_error);
+
+ static void DoCreateTemporaryFile(base::FilePath temp_dir_,
+ TempFileCallback cb);
+
+ // GetNexeFd common steps
+ void SendCacheQueryAndTempFileRequest(const std::string& key,
+ const TranslationID& id);
+ void OnCacheQueryReturn(const TranslationID& id,
+ int net_error,
+ scoped_refptr<net::DrainableIOBuffer> buffer);
+ void OnTempFileReturn(const TranslationID& id, base::PlatformFile fd);
+ void CheckCacheQueryReady(const PendingTranslationMap::iterator& entry);
+
+ // GetNexeFd miss path
+ void ReturnMiss(const PendingTranslationMap::iterator& entry);
+ static scoped_refptr<net::DrainableIOBuffer> CopyFileToBuffer(
+ base::PlatformFile fd);
+ void StoreTranslatedNexe(TranslationID id,
+ scoped_refptr<net::DrainableIOBuffer>);
+ void OnTranslatedNexeStored(const TranslationID& id, int net_error);
+ void RequeryMatchingTranslations(const std::string& key);
+
+ // GetNexeFd hit path
+ static int CopyBufferToFile(base::PlatformFile fd,
+ scoped_refptr<net::DrainableIOBuffer> buffer);
+ void OnBufferCopiedToTempFile(const TranslationID& id, int file_error);
+
+ void OnEntriesDoomed(const base::Closure& callback, int net_error);
+
+ void DeInitIfSafe();
+
+ // Operations which are pending with the cache backend, which we should
+ // wait for before destroying it (see comment on DeInitIfSafe).
+ int pending_backend_operations_;
+ CacheState cache_state_;
+ base::FilePath temp_dir_;
+ scoped_ptr<pnacl::PnaclTranslationCache> disk_cache_;
+ PendingTranslationMap pending_translations_;
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<PnaclHost> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(PnaclHost);
+};
+
+} // namespace pnacl
+
+#endif // COMPONENTS_NACL_BROWSER_PNACL_HOST_H_
diff --git a/components/nacl/browser/pnacl_host_unittest.cc b/components/nacl/browser/pnacl_host_unittest.cc
new file mode 100644
index 0000000..d9119da
--- /dev/null
+++ b/components/nacl/browser/pnacl_host_unittest.cc
@@ -0,0 +1,432 @@
+// 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 "components/nacl/browser/pnacl_host.h"
+
+#include <stdio.h>
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/nacl/browser/pnacl_translation_cache.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#define snprintf _snprintf
+#endif
+
+namespace pnacl {
+
+class PnaclHostTest : public testing::Test {
+ protected:
+ PnaclHostTest()
+ : host_(NULL),
+ temp_callback_count_(0),
+ write_callback_count_(0),
+ thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
+ virtual void SetUp() {
+ host_ = new PnaclHost();
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ host_->InitForTest(temp_dir_.path());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
+ }
+ virtual void TearDown() {
+ EXPECT_EQ(0U, host_->pending_translations());
+ // Give the host a chance to de-init the backend, and then delete it.
+ host_->RendererClosing(0);
+ FlushQueues();
+ EXPECT_EQ(PnaclHost::CacheUninitialized, host_->cache_state_);
+ delete host_;
+ }
+ // Flush the blocking pool first, then any tasks it posted to the IO thread.
+ // Do 2 rounds of flushing, because some operations require 2 trips back and
+ // forth between the threads.
+ void FlushQueues() {
+ content::BrowserThread::GetBlockingPool()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ content::BrowserThread::GetBlockingPool()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+ int GetCacheSize() { return host_->disk_cache_->Size(); }
+ int CacheIsInitialized() {
+ return host_->cache_state_ == PnaclHost::CacheReady;
+ }
+ void ReInitBackend() {
+ host_->InitForTest(temp_dir_.path());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(PnaclHost::CacheReady, host_->cache_state_);
+ }
+
+ public: // Required for derived classes to bind this method
+ // Callbacks used by tests which call GetNexeFd.
+ // CallbackExpectMiss checks that the fd is valid and a miss is reported,
+ // and also writes some data into the file, which is read back by
+ // CallbackExpectHit
+ void CallbackExpectMiss(base::PlatformFile fd, bool is_hit) {
+ EXPECT_FALSE(is_hit);
+ ASSERT_FALSE(fd == base::kInvalidPlatformFileValue);
+ base::PlatformFileInfo info;
+ EXPECT_TRUE(base::GetPlatformFileInfo(fd, &info));
+ EXPECT_FALSE(info.is_directory);
+ EXPECT_EQ(0LL, info.size);
+ char str[16];
+ memset(str, 0x0, 16);
+ snprintf(str, 16, "testdata%d", ++write_callback_count_);
+ EXPECT_EQ(16, base::WritePlatformFile(fd, 0, str, 16));
+ temp_callback_count_++;
+ }
+ void CallbackExpectHit(base::PlatformFile fd, bool is_hit) {
+ EXPECT_TRUE(is_hit);
+ ASSERT_FALSE(fd == base::kInvalidPlatformFileValue);
+ base::PlatformFileInfo info;
+ EXPECT_TRUE(base::GetPlatformFileInfo(fd, &info));
+ EXPECT_FALSE(info.is_directory);
+ EXPECT_EQ(16LL, info.size);
+ char data[16];
+ memset(data, 0x0, 16);
+ char str[16];
+ memset(str, 0x0, 16);
+ snprintf(str, 16, "testdata%d", write_callback_count_);
+ EXPECT_EQ(16, base::ReadPlatformFile(fd, 0, data, 16));
+ EXPECT_STREQ(str, data);
+ temp_callback_count_++;
+ }
+
+ protected:
+ PnaclHost* host_;
+ int temp_callback_count_;
+ int write_callback_count_;
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+};
+
+static nacl::PnaclCacheInfo GetTestCacheInfo() {
+ nacl::PnaclCacheInfo info;
+ info.pexe_url = GURL("http://www.google.com");
+ info.abi_version = 0;
+ info.opt_level = 0;
+ info.has_no_store_header = false;
+ return info;
+}
+
+#define GET_NEXE_FD(renderer, instance, incognito, info, expect_hit) \
+ do { \
+ SCOPED_TRACE(""); \
+ host_->GetNexeFd( \
+ renderer, \
+ 0, /* ignore render_view_id for now */ \
+ instance, \
+ incognito, \
+ info, \
+ base::Bind(expect_hit ? &PnaclHostTest::CallbackExpectHit \
+ : &PnaclHostTest::CallbackExpectMiss, \
+ base::Unretained(this))); \
+ } while (0)
+
+TEST_F(PnaclHostTest, BasicMiss) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ // Test cold miss.
+ GET_NEXE_FD(0, 0, false, info, false);
+ EXPECT_EQ(1U, host_->pending_translations());
+ FlushQueues();
+ EXPECT_EQ(1U, host_->pending_translations());
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ // Test that a different cache info field also misses.
+ info.etag = std::string("something else");
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(1U, host_->pending_translations());
+ host_->RendererClosing(0);
+ FlushQueues();
+ // Check that the cache has de-initialized after the last renderer goes away.
+ EXPECT_FALSE(CacheIsInitialized());
+}
+
+TEST_F(PnaclHostTest, BadArguments) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ EXPECT_EQ(1U, host_->pending_translations());
+ host_->TranslationFinished(0, 1, true); // nonexistent translation
+ EXPECT_EQ(1U, host_->pending_translations());
+ host_->RendererClosing(1); // nonexistent renderer
+ EXPECT_EQ(1U, host_->pending_translations());
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->RendererClosing(0); // close without finishing
+}
+
+TEST_F(PnaclHostTest, BasicHit) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ GET_NEXE_FD(0, 1, false, info, true);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, TranslationErrors) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ // Early abort, before temp file request returns
+ host_->TranslationFinished(0, 0, false);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ EXPECT_EQ(0, temp_callback_count_);
+ // The backend will have been freed when the query comes back and there
+ // are no pending translations.
+ EXPECT_FALSE(CacheIsInitialized());
+ ReInitBackend();
+ // Check that another request for the same info misses successfully.
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+
+ // Now try sending the error after the temp file request returns
+ info.abi_version = 222;
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ host_->TranslationFinished(0, 0, false);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ // Check another successful miss
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+ host_->TranslationFinished(0, 0, false);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, OverlappedMissesAfterTempReturn) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ EXPECT_EQ(1U, host_->pending_translations());
+ // Test that a second request for the same nexe while the first one is still
+ // outstanding eventually hits.
+ GET_NEXE_FD(0, 1, false, info, true);
+ FlushQueues();
+ EXPECT_EQ(2U, host_->pending_translations());
+ // The temp file should not be returned to the second request until after the
+ // first is finished translating.
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, OverlappedMissesBeforeTempReturn) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ // Send the 2nd fd request before the first one returns a temp file.
+ GET_NEXE_FD(0, 1, false, info, true);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ EXPECT_EQ(2U, host_->pending_translations());
+ FlushQueues();
+ EXPECT_EQ(2U, host_->pending_translations());
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, OverlappedHitsBeforeTempReturn) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ // Store one in the cache and complete it.
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ GET_NEXE_FD(0, 0, false, info, true);
+ // Request the second before the first temp file returns.
+ GET_NEXE_FD(0, 1, false, info, true);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, OverlappedHitsAfterTempReturn) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ // Store one in the cache and complete it.
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ GET_NEXE_FD(0, 0, false, info, true);
+ FlushQueues();
+ GET_NEXE_FD(0, 1, false, info, true);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+TEST_F(PnaclHostTest, OverlappedMissesRendererClosing) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ // Send the 2nd fd request from a different renderer.
+ // Test that it eventually gets an fd after the first renderer closes.
+ GET_NEXE_FD(1, 1, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ EXPECT_EQ(2U, host_->pending_translations());
+ FlushQueues();
+ EXPECT_EQ(2U, host_->pending_translations());
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->RendererClosing(0);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(1U, host_->pending_translations());
+ host_->RendererClosing(1);
+}
+
+TEST_F(PnaclHostTest, Incognito) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, true, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ // Check that an incognito translation is not stored in the cache
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ // Check that an incognito translation can hit from a normal one.
+ GET_NEXE_FD(0, 0, true, info, true);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+}
+
+TEST_F(PnaclHostTest, IncognitoOverlappedMiss) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, true, info, false);
+ GET_NEXE_FD(0, 1, false, info, false);
+ FlushQueues();
+ // Check that both translations have returned misses, (i.e. that the
+ // second one has not blocked on the incognito one)
+ EXPECT_EQ(2, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ host_->TranslationFinished(0, 1, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+
+ // Same test, but issue the 2nd request after the first has returned a miss.
+ info.abi_version = 222;
+ GET_NEXE_FD(0, 0, true, info, false);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+ GET_NEXE_FD(0, 1, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(4, temp_callback_count_);
+ host_->RendererClosing(0);
+}
+
+TEST_F(PnaclHostTest, IncognitoSecondOverlappedMiss) {
+ // If the non-incognito request comes first, it should
+ // behave exactly like OverlappedMissBeforeTempReturn
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ GET_NEXE_FD(0, 0, false, info, false);
+ // Send the 2nd fd request before the first one returns a temp file.
+ GET_NEXE_FD(0, 1, true, info, true);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ EXPECT_EQ(2U, host_->pending_translations());
+ FlushQueues();
+ EXPECT_EQ(2U, host_->pending_translations());
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ EXPECT_EQ(0U, host_->pending_translations());
+}
+
+// Test that pexes with the no-store header do not get cached.
+TEST_F(PnaclHostTest, CacheControlNoStore) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ info.has_no_store_header = true;
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(1, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ EXPECT_EQ(0, GetCacheSize());
+}
+
+// Test that no-store pexes do not wait, but do duplicate translations
+TEST_F(PnaclHostTest, NoStoreOverlappedMiss) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ info.has_no_store_header = true;
+ GET_NEXE_FD(0, 0, false, info, false);
+ GET_NEXE_FD(0, 1, false, info, false);
+ FlushQueues();
+ // Check that both translations have returned misses, (i.e. that the
+ // second one has not blocked on the first one)
+ EXPECT_EQ(2, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ host_->TranslationFinished(0, 1, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+
+ // Same test, but issue the 2nd request after the first has returned a miss.
+ info.abi_version = 222;
+ GET_NEXE_FD(0, 0, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(3, temp_callback_count_);
+ GET_NEXE_FD(0, 1, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(4, temp_callback_count_);
+ host_->RendererClosing(0);
+}
+
+TEST_F(PnaclHostTest, ClearTranslationCache) {
+ nacl::PnaclCacheInfo info = GetTestCacheInfo();
+ // Add 2 entries in the cache
+ GET_NEXE_FD(0, 0, false, info, false);
+ info.abi_version = 222;
+ GET_NEXE_FD(0, 1, false, info, false);
+ FlushQueues();
+ EXPECT_EQ(2, temp_callback_count_);
+ host_->TranslationFinished(0, 0, true);
+ host_->TranslationFinished(0, 1, true);
+ FlushQueues();
+ EXPECT_EQ(0U, host_->pending_translations());
+ EXPECT_EQ(2, GetCacheSize());
+ net::TestCompletionCallback cb;
+ // Since we are using a memory backend, the clear should happen immediately.
+ host_->ClearTranslationCacheEntriesBetween(
+ base::Time(), base::Time(), base::Bind(cb.callback(), 0));
+ // Check that the translation cache has been cleared before flushing the
+ // queues, because the backend will be freed once it is.
+ EXPECT_EQ(0, GetCacheSize());
+ EXPECT_EQ(0, cb.GetResult(net::ERR_IO_PENDING));
+ // Now check that the backend has been freed.
+ EXPECT_FALSE(CacheIsInitialized());
+}
+
+} // namespace pnacl
diff --git a/components/nacl/common/nacl_host_messages.cc b/components/nacl/common/nacl_host_messages.cc
new file mode 100644
index 0000000..1b47272
--- /dev/null
+++ b/components/nacl/common/nacl_host_messages.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 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.
+
+// Get basic type definitions.
+#define IPC_MESSAGE_IMPL
+#include "components/nacl/common/nacl_host_messages.h"
+
+// Generate constructors.
+#include "ipc/struct_constructor_macros.h"
+#include "components/nacl/common/nacl_host_messages.h"
+
+// Generate destructors.
+#include "ipc/struct_destructor_macros.h"
+#include "components/nacl/common/nacl_host_messages.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#include "components/nacl/common/nacl_host_messages.h"
+} // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#include "components/nacl/common/nacl_host_messages.h"
+} // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#include "components/nacl/common/nacl_host_messages.h"
+} // namespace IPC
+
diff --git a/components/nacl/common/nacl_switches.cc b/components/nacl/common/nacl_switches.cc
index 0dfdc94..928b214 100644
--- a/components/nacl/common/nacl_switches.cc
+++ b/components/nacl/common/nacl_switches.cc
@@ -36,4 +36,10 @@ const char kNaClLoaderCmdPrefix[] = "nacl-loader-cmd-prefix";
// Causes the process to run as a NativeClient loader.
const char kNaClLoaderProcess[] = "nacl-loader";
+// Disables crash throttling for Portable Native Client.
+const char kDisablePnaclCrashThrottling[] = "disable-pnacl-crash-throttling";
+
+// Disables the installation of Portable Native Client.
+const char kDisablePnaclInstall[] = "disable-pnacl-install";
+
} // namespace switches
diff --git a/components/nacl/common/nacl_switches.h b/components/nacl/common/nacl_switches.h
index 9bc1bcb..8791d47 100644
--- a/components/nacl/common/nacl_switches.h
+++ b/components/nacl/common/nacl_switches.h
@@ -18,6 +18,8 @@ extern const char kNaClGdb[];
extern const char kNaClGdbScript[];
extern const char kNaClLoaderCmdPrefix[];
extern const char kNaClLoaderProcess[];
+extern const char kDisablePnaclCrashThrottling[];
+extern const char kDisablePnaclInstall[];
} // namespace switches
diff --git a/components/nacl_common.gyp b/components/nacl_common.gyp
index 960eb1c..6865869 100644
--- a/components/nacl_common.gyp
+++ b/components/nacl_common.gyp
@@ -21,8 +21,12 @@
'sources': [
'nacl/common/nacl_cmd_line.cc',
'nacl/common/nacl_cmd_line.h',
+ 'nacl/common/nacl_host_messages.h',
+ 'nacl/common/nacl_host_messages.cc',
'nacl/common/nacl_messages.cc',
'nacl/common/nacl_messages.h',
+ 'nacl/common/nacl_process_type.h',
+ 'nacl/common/nacl_sandbox_type_mac.h',
'nacl/common/nacl_types.cc',
'nacl/common/nacl_types.h',
'nacl/common/pnacl_types.cc',