Updated to arc-runtime-41.4410.204.0
diff --git a/src/common/config.py b/src/common/config.py
index 705f76e..fc89c5b 100755
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -8,7 +8,6 @@
 
 import build_common
 import ninja_generator
-import open_source
 from build_options import OPTIONS
 
 
@@ -49,14 +48,6 @@
       implicit_add=['src/build/android_static_libraries.py'])
 
 
-def _get_fake_posix_translation_cc(ninja):
-  return _get_generated_file(
-      ninja,
-      out_name='fake_posix_translation.cc',
-      script_name='gen_fake_posix_translation_cc.py',
-      implicit_add=['src/build/wrapped_functions.py'])
-
-
 def _get_real_syscall_aliases_s(ninja):
   return _get_generated_file(
       ninja,
@@ -72,17 +63,6 @@
 
 # Generate libcommon.a, the library that should be linked into everything.
 def generate_ninjas():
-  if open_source.is_open_source_repo():
-    # The open source version of ARC does not provide libposix_translation.so
-    # but most of the open-sourced DSOs depend on it. Build a fake POSIX
-    # translation library which exports the same set of __wrap_* symbols as
-    # the real one.
-    n = ninja_generator.SharedObjectNinjaGenerator('libposix_translation',
-                                                   is_system_library=True,
-                                                   enable_clang=True)
-    n.add_notice_sources(['src/NOTICE'])
-    n.build_default([_get_fake_posix_translation_cc(n)]).link()
-
   n = ninja_generator.ArchiveNinjaGenerator(
       'libcommon_test_main',
       base_path='src/common/tests',
diff --git a/src/posix_translation/OWNERS b/src/posix_translation/OWNERS
new file mode 100644
index 0000000..fb5a7c5
--- /dev/null
+++ b/src/posix_translation/OWNERS
@@ -0,0 +1,15 @@
+# POSIX Translation
+set noparent
+hamaji@google.com
+satorux@google.com
+yusukes@google.com
+
+# Socket-related files
+per-file local_socket.* = hidehiko@google.com
+per-file local_socket.* = jhorwich@google.com
+per-file socket* = hidehiko@google.com
+per-file socket* = jhorwich@google.com
+per-file tcp* = hidehiko@google.com
+per-file tcp* = jhorwich@google.com
+per-file udp* = hidehiko@google.com
+per-file udp* = jhorwich@google.com
diff --git a/src/posix_translation/address_util.cc b/src/posix_translation/address_util.cc
new file mode 100644
index 0000000..d532e94
--- /dev/null
+++ b/src/posix_translation/address_util.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 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 "posix_translation/address_util.h"
+
+#include <unistd.h>
+
+namespace posix_translation {
+namespace util {
+
+size_t GetPageSize() {
+  // sysconf(_SC_PAGESIZE) is cheap as it does not call into IRT.
+  return sysconf(_SC_PAGESIZE);
+}
+
+uint32_t GetPageSizeAsNumBits() {
+  return CountTrailingZeros(GetPageSize());
+}
+
+size_t RoundToPageSize(size_t length) {
+  const size_t page_size = GetPageSize();
+  return (length + page_size - 1) & ~(page_size - 1);
+}
+
+bool IsPageAligned(const void* addr) {
+  return !(reinterpret_cast<uintptr_t>(addr) & (GetPageSize() - 1));
+}
+
+uint32_t CountTrailingZeros(uint32_t value) {
+  if (!value)
+    return 32;  // __builtin_ctz() returns undefined result for 0.
+  return __builtin_ctz(value);
+}
+
+}  // namespace util
+}  // namespace posix_translation
diff --git a/src/posix_translation/address_util.h b/src/posix_translation/address_util.h
new file mode 100644
index 0000000..15127fa
--- /dev/null
+++ b/src/posix_translation/address_util.h
@@ -0,0 +1,33 @@
+// Copyright 2014 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 POSIX_TRANSLATION_ADDRESS_UTIL_H_
+#define POSIX_TRANSLATION_ADDRESS_UTIL_H_
+
+#include "base/basictypes.h"
+
+namespace posix_translation {
+namespace util {
+
+// Returns the page size of the running operating system.
+size_t GetPageSize();
+
+// Returns the page size as the number of bits (ex. this function returns 12
+// if the page size is 4096 because 2^12 = 4096).
+uint32_t GetPageSizeAsNumBits();
+
+// Rounds up |length| to nearest multiple of the page size.
+size_t RoundToPageSize(size_t length);
+
+// Returns true if the given address is page-aligned.
+bool IsPageAligned(const void* addr);
+
+// Counts trailing zeros in the given 32-bit value. Returns 32 if the value
+// is 0.
+uint32_t CountTrailingZeros(uint32_t value);
+
+}  // namespace util
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_ADDRESS_UTIL_H_
diff --git a/src/posix_translation/address_util_test.cc b/src/posix_translation/address_util_test.cc
new file mode 100644
index 0000000..bc0f88e
--- /dev/null
+++ b/src/posix_translation/address_util_test.cc
@@ -0,0 +1,59 @@
+// Copyright 2014 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 "posix_translation/address_util.h"
+
+#include "gtest/gtest.h"
+
+namespace posix_translation {
+namespace util {
+
+TEST(AddressUtilTest, GetPageSize) {
+  EXPECT_LT(0U, GetPageSize());
+}
+
+TEST(AddressUtilTest, GetPageSizeAsNumBits) {
+  EXPECT_EQ(GetPageSize(), 1U << GetPageSizeAsNumBits());
+}
+
+TEST(AddressUtilTest, RoundToPageSize) {
+  const size_t pagesize = GetPageSize();
+  EXPECT_EQ(0U, RoundToPageSize(0));
+  EXPECT_EQ(pagesize, RoundToPageSize(1));
+  EXPECT_EQ(pagesize, RoundToPageSize(pagesize - 1));
+  EXPECT_EQ(pagesize, RoundToPageSize(pagesize));
+  EXPECT_EQ(pagesize * 2, RoundToPageSize(pagesize + 1));
+}
+
+TEST(AddressUtilTest, IsPageAligned) {
+  const size_t pagesize = GetPageSize();
+  uintptr_t ptr = 0x0;
+  EXPECT_TRUE(IsPageAligned(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_FALSE(IsPageAligned(reinterpret_cast<void*>(ptr)));
+  ptr = pagesize - 1;
+  EXPECT_FALSE(IsPageAligned(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_TRUE(IsPageAligned(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_FALSE(IsPageAligned(reinterpret_cast<void*>(ptr)));
+}
+
+TEST(AddressUtilTest, TestCountTrailingZeros) {
+  static const size_t kBits = 32;
+  EXPECT_EQ(kBits, CountTrailingZeros(0));
+
+  uint32_t x = 1;
+  for (size_t i = 0; i < kBits; ++i, x <<= 1) {
+    EXPECT_EQ(i, CountTrailingZeros(x));
+  }
+
+  x = 0xffffffff;
+  for (size_t i = 0; i < kBits; ++i, x <<= 1) {
+    EXPECT_EQ(i, CountTrailingZeros(x));
+  }
+}
+
+}  // namespace util
+}  // namespace posix_translation
diff --git a/src/posix_translation/config.py b/src/posix_translation/config.py
new file mode 100755
index 0000000..deb1ae3
--- /dev/null
+++ b/src/posix_translation/config.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+
+# Copyright 2014 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.
+
+import os
+import sys
+
+import build_common
+import ninja_generator
+import ninja_generator_runner
+from build_options import OPTIONS
+from ninja_generator import ArchiveNinjaGenerator
+from ninja_generator import SharedObjectNinjaGenerator
+
+
+# Use the real mods directory instead of the one in staging directory because
+# this file is used on ChromeOS or Perf bots where the staging directory is not
+# created. It is no problem not to use the staging path because config.py, for
+# which this path is added, is our file.
+sys.path.insert(0, 'mods/android/external/stlport/')
+from config import get_libstlport_static_defines
+
+
+_CREATE_READONLY_FS_IMAGE_SCRIPT = (
+    'src/posix_translation/scripts/create_readonly_fs_image.py')
+
+
+# Mount points for directories.
+_EMPTY_DIRECTORIES = ['/cache',
+                      '/data',
+                      '/storage',
+                      '/sys/devices/system/cpu',
+                      '/system/lib',
+                      '/usr/lib',
+                      '/vendor/chromium/crx']
+
+
+# Mount points used for files.
+_EMPTY_FILES = ['/dev/ashmem',
+                '/dev/log/events',
+                '/dev/log/main',
+                '/dev/log/radio',
+                '/dev/log/system',
+                '/dev/null',
+                '/dev/random',
+                '/dev/urandom',
+                '/dev/zero',
+                '/proc/cpuinfo',
+                '/sys/kernel/debug/tracing/trace_marker',
+                '/system/bin/sh']
+
+
+# A map of symlinks that'll be created in the read-only file system
+# image, where keys are symlink paths and values are target paths. For
+# example,
+#
+#   '/etc': '/system/etc'
+#
+# means that /etc is a symlink to /system/etc.
+_SYMLINK_MAP = {
+    '/d': '/sys/kernel/debug',
+    '/etc': '/system/etc',
+    # Android has /mnt/sdcard, /sdcard, and /storage/sdcard0 all as symlinks
+    # to /storage/emulated/legacy.  For ARC we have /sdcard and /mnt/sdcard
+    # as symlinks to /storage/sdcard.  This is compatible with Android
+    # emulator as of KitKat.
+    # TODO(crbug.com/196442): When we properly support migration between
+    # different Android versions, we should move these files for all apps
+    # into the same real location that Android currently uses and make the
+    # rest of the files be symlinks.
+    '/sdcard': '/storage/sdcard',
+    '/mnt/sdcard': '/storage/sdcard',
+    # On real devices, the link usually points to a Dalvik's executable.
+    # However, since such a binary is not available on our system, we use
+    # runnable-ld.so (which is ET_EXEC) instead. We prefer runnable-ld.so
+    # over main.nexe since some apps crash if the /proc file points to a huge
+    # binary like main.nexe which does not fit into the NaCl's small virtual
+    # address space. Also note that /proc/self/exe does not point to the
+    # loader on Linux unless you explicitly invoke glibc's like this:
+    # $ /lib64/ld-linux-x86-64.so.2 ./a.out
+    '/proc/self/exe': '/system/lib/runnable-ld.so'
+}
+
+
+# Generate posix_translation. This library adds functions for converting POSIX
+# API calls into PPAPI calls.
+def _generate_libposix_translation():
+  compiler_flags = get_libstlport_static_defines() + [
+      '-Werror', '-fvisibility=hidden', '-fvisibility-inlines-hidden']
+
+  n = ArchiveNinjaGenerator('libposix_translation_static', enable_clang=True)
+  n.add_compiler_flags(*compiler_flags)
+  if OPTIONS.is_posix_translation_debug():
+    n.add_defines('DEBUG_POSIX_TRANSLATION')
+  if OPTIONS.use_verbose_memory_viewer():
+    n.add_defines('USE_VERBOSE_MEMORY_VIEWER')
+  # For functions in chromium_org/base/ and private headers in ppapi/.
+  # TODO(crbug.com/234789): Use public API so that we can depend on
+  # nacl_pepper_path instead.
+  n.add_ppapi_compile_flags()
+  n.add_libchromium_base_compile_flags()
+  all_files = build_common.find_all_files(['src/posix_translation'],
+                                          ['.cc'])
+  all_files.remove('src/posix_translation/libc_dispatch_layer.cc')
+  n.build_default(all_files).archive()
+
+  n = SharedObjectNinjaGenerator('libposix_translation',
+                                 is_system_library=True, enable_clang=True)
+  n.add_library_deps('libc.so', 'libm.so', 'libdl.so')
+  n.add_whole_archive_deps('libposix_translation_static.a')
+  # Statically link libchromium_base.a so that we can use unwrapped version of
+  # the library.
+  # TODO(crbug.com/423063): Statically link libcommon.a into the DSO too for
+  # more safety.
+  n.add_library_deps('libchromium_base.a', 'libstlport_static.a')
+  n.add_compiler_flags(*compiler_flags)
+  n.add_ppapi_link_flags()
+  n.build_default(['src/posix_translation/libc_dispatch_layer.cc']).link()
+
+
+def _generate_libposix_files():
+  n = ninja_generator.NinjaGenerator('libposix_files')
+  install_files = [
+      ('proc', 'src/posix_translation/proc/cmdline'),
+      ('proc', 'src/posix_translation/proc/loadavg'),
+      ('proc', 'src/posix_translation/proc/meminfo'),
+      ('proc/net', 'src/posix_translation/proc/net/tcp'),
+      ('proc/net', 'src/posix_translation/proc/net/tcp6'),
+      ('proc/net', 'src/posix_translation/proc/net/udp'),
+      ('proc/net', 'src/posix_translation/proc/net/udp6'),
+      ('proc/self', 'src/posix_translation/proc/self/cmdline'),
+      ('proc/self', 'src/posix_translation/proc/self/maps'),
+      ('proc/self', 'src/posix_translation/proc/self/auxv'),
+      ('proc', 'src/posix_translation/proc/stat'),
+      ('proc', 'src/posix_translation/proc/version')]
+  for dst, src in install_files:
+    file_name = os.path.basename(src)
+    dst = os.path.join(dst, file_name)
+    n.install_to_root_dir(dst, src)
+
+
+def generate_ninjas():
+  ninja_generator_runner.request_run_in_parallel(
+      _generate_libposix_files,
+      _generate_libposix_translation)
+
+
+def generate_test_ninjas():
+  n = ninja_generator.PpapiTestNinjaGenerator(
+      'posix_translation_test',
+      base_path='src/posix_translation',
+      enable_clang=True)
+  # Build a rootfs image for tests.
+  rule_name = 'gen_test_fs_image'
+  script_path = 'src/posix_translation/scripts/create_test_fs_image.py'
+
+  gen_prod_image = (
+      build_common.get_posix_translation_readonly_fs_image_file_path())
+
+  # This is a little odd, but we use the documented path to the production image
+  # to also store a test image in the same location for simplicity.
+  out_path = os.path.dirname(gen_prod_image)
+  gen_test_image = os.path.join(out_path, 'test_readonly_fs_image.img')
+
+  n.rule(rule_name,
+         command=script_path + ' $out_path',
+         description=rule_name + ' $in_real_path')
+  n.add_ppapi_compile_flags()
+  n.build([gen_test_image], rule_name,
+          variables={'out_path': out_path},
+          # The script calls create_readonly_fs_image.py.
+          implicit=[script_path,
+                    _CREATE_READONLY_FS_IMAGE_SCRIPT,
+                    ])
+  all_files = n.find_all_contained_test_sources()
+
+  n.build_default(all_files, base_path=None)
+  if OPTIONS.enable_art():
+    n.add_defines('ENABLE_ART=1')
+  n.add_compiler_flags('-Werror')
+  n.add_library_deps('libposix_translation_static.a',
+                     'libchromium_base.a',
+                     'libcommon.a')
+  n.run(n.link(), implicit=[gen_test_image, gen_prod_image])
+
+  # To be able to refer mock implementation from outside of posix_translation.
+  # Setting instance count is zero because usage count verifier doesn't check
+  # the reference from test executable. See verify_usage_counts in
+  # ninja_generator.py
+  n = ArchiveNinjaGenerator('mock_posix_translation', instances=0)
+  n.add_libchromium_base_compile_flags()
+  n.add_compiler_flags('-Werror')
+  all_files = ['src/posix_translation/test_util/mock_virtual_file_system.cc']
+  n.build_default(all_files).archive()
+
+
+def generate_binaries_depending_ninjas(root_dir_install_all_targets):
+  n = ninja_generator.NinjaGenerator('readonly_fs_image')
+  rule_name = 'gen_readonly_fs_image'
+  encoded_symlink_map = ','.join([x + ':' + y for x, y in
+                                  _SYMLINK_MAP.iteritems()])
+  encoded_empty_dirs = ','.join(_EMPTY_DIRECTORIES)
+  encoded_empty_files = ','.join(_EMPTY_FILES)
+
+  n.rule(rule_name,
+         command=_CREATE_READONLY_FS_IMAGE_SCRIPT + ' -o $out ' +
+         '-s "' + encoded_symlink_map + '" '
+         '-d "' + encoded_empty_dirs + '" '
+         '-f "' + encoded_empty_files + '" '
+         '$in', description=rule_name)
+  gen_img = build_common.get_posix_translation_readonly_fs_image_file_path()
+
+  my_dependencies = sorted(root_dir_install_all_targets)
+  # The configure options file is a dependency as symlinks in the read-only
+  # file system image changes per the configure options.
+  implicit = [_CREATE_READONLY_FS_IMAGE_SCRIPT,
+              OPTIONS.get_configure_options_file()]
+  n.build([gen_img], rule_name, my_dependencies,
+          implicit=implicit)
diff --git a/src/posix_translation/cpu_file.cc b/src/posix_translation/cpu_file.cc
new file mode 100644
index 0000000..930c505
--- /dev/null
+++ b/src/posix_translation/cpu_file.cc
@@ -0,0 +1,258 @@
+// Copyright 2014 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 "posix_translation/cpu_file.h"
+
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/readonly_memory_file.h"
+#include "posix_translation/statfs.h"
+
+namespace posix_translation {
+
+namespace {
+
+// An interface for providing file content to CpuFile.
+class CpuFileContent {
+ public:
+  CpuFileContent() {}
+  virtual ~CpuFileContent() {}
+  virtual const ReadonlyMemoryFile::Content& GetContent() = 0;
+
+ protected:
+  // Creates the content of a CPU file from |min| and |max| and stores it in
+  // |content_|. |min| must be smaller than or equial to |max|. Both |min| and
+  // |max| must not be negative.
+  void UpdateContent(int min, int max) {
+    ALOG_ASSERT(min >= 0 && max >= 0 && max >= min,
+                "min: %d, max: %d", min, max);
+    const std::string s = (min == max) ? base::StringPrintf("%d\n", min)
+        : base::StringPrintf("%d-%d\n", min, max);
+    content_.assign(s.begin(), s.end());
+  }
+
+  ReadonlyMemoryFile::Content content_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CpuFileContent);
+};
+
+// A class for "kernel_max". The file contains one number which is equal to
+// (kNrCpus - 1) followed by "\n".
+class KernelMaxFileContent : public CpuFileContent {
+ public:
+  KernelMaxFileContent() {
+    UpdateContent(kNrCpus - 1, kNrCpus - 1);
+  }
+  virtual ~KernelMaxFileContent() {}
+
+  virtual const ReadonlyMemoryFile::Content& GetContent() OVERRIDE {
+    ALOG_ASSERT(!content_.empty());
+    return content_;
+  }
+
+ private:
+  // A constant equivalent to NR_CPUS in the Linux kernel config.
+  static const int kNrCpus = 64;
+
+  DISALLOW_COPY_AND_ASSIGN(KernelMaxFileContent);
+};
+
+// A class for "offline". The file contains only "\n" when all CPUs are online.
+// Otherwise, the file contains CPU numbers that are offline. For example, when
+// the last 2 CPUs out of 8 are offline, the content is "6-7".
+class OfflineFileContent : public CpuFileContent {
+ public:
+  explicit OfflineFileContent(int num_processors)
+      : num_processors_(num_processors) {
+    ALOG_ASSERT(num_processors_ > 0);
+  }
+  virtual ~OfflineFileContent() {}
+
+  virtual const ReadonlyMemoryFile::Content& GetContent() OVERRIDE {
+    const int num_online_processors = sysconf(_SC_NPROCESSORS_ONLN);
+    ALOG_ASSERT(num_online_processors > 0);
+    const int num_offline_processors = num_processors_ - num_online_processors;
+    if (num_offline_processors == 0)
+      content_.assign(1, '\n');  // no offline CPUs.
+    else
+      UpdateContent(num_online_processors, num_processors_ - 1);
+    ALOG_ASSERT(num_processors_ >= num_online_processors);  // sanity check.
+    ALOG_ASSERT(!content_.empty());
+    return content_;
+  }
+
+ private:
+  const int num_processors_;
+
+  DISALLOW_COPY_AND_ASSIGN(OfflineFileContent);
+};
+
+// A class for "online". The file contains CPU numbers that are onine. For
+// example, when 2 CPUs out of 2 are online, the content is "0-1". When
+// 1 out of 1 is, the content is "0".
+class OnlineFileContent : public CpuFileContent {
+ public:
+  OnlineFileContent() {}
+  virtual ~OnlineFileContent() {}
+
+  virtual const ReadonlyMemoryFile::Content& GetContent() OVERRIDE {
+    const int num_online_processors = sysconf(_SC_NPROCESSORS_ONLN);
+    ALOG_ASSERT(num_online_processors > 0);
+    UpdateContent(0, num_online_processors - 1);
+    ALOG_ASSERT(!content_.empty());
+    return content_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OnlineFileContent);
+};
+
+// A class for "present" and "possible". The file contains CPU numbers that are
+// configured (i.e. physically available). For example, when 2 CPUs are
+// configured, the content is "0-1". When 1 out of 1 is, the content is "0".
+class PresentFileContent : public CpuFileContent {
+ public:
+  explicit PresentFileContent(int num_processors) {
+    ALOG_ASSERT(num_processors > 0);
+    UpdateContent(0, num_processors - 1);
+  }
+  virtual ~PresentFileContent() {}
+
+  virtual const ReadonlyMemoryFile::Content& GetContent() OVERRIDE {
+    ALOG_ASSERT(!content_.empty());
+    return content_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PresentFileContent);
+};
+
+// A file stream for readonly files managed by CpuFileHandler.
+class CpuFile : public ReadonlyMemoryFile {
+ public:
+  // Creates a ReadonlyMemoryFile stream with |content|. The object takes
+  // ownership of |content|.
+  CpuFile(const std::string& pathname, CpuFileContent* content)
+      : ReadonlyMemoryFile(pathname, EIO /* no mmap support */, time(NULL)),
+        content_(content) {
+    ALOG_ASSERT(content_.get());
+  }
+
+ protected:
+  virtual ~CpuFile() {}
+
+ private:
+  // ReadonlyMemoryFile overrides:
+  virtual const Content& GetContent() OVERRIDE {
+    ALOG_ASSERT(content_.get());
+    return content_->GetContent();
+  }
+
+  scoped_ptr<CpuFileContent> content_;
+
+  DISALLOW_COPY_AND_ASSIGN(CpuFile);
+};
+
+}  // namespace
+
+CpuFileHandler::CpuFileHandler() : FileSystemHandler("CpuFileHandler"),
+                                   is_initialized_(false), num_processors_(-1) {
+}
+
+CpuFileHandler::~CpuFileHandler() {
+}
+
+bool CpuFileHandler::IsInitialized() const {
+  return is_initialized_;
+}
+
+void CpuFileHandler::Initialize() {
+  ALOG_ASSERT(!path_.empty());
+  directory_manager_.MakeDirectories(path_);
+
+  num_processors_ = sysconf(_SC_NPROCESSORS_CONF);
+  ALOG_ASSERT(num_processors_ > 0);
+  ALOGI("Number of processors: %d", num_processors_);
+
+  for (int i = 0; i < num_processors_; ++i) {
+    directory_manager_.MakeDirectories(
+        base::StringPrintf("%scpu%d", path_.c_str(), i));
+  }
+
+  static const char* kFiles[] =
+    { "kernel_max", "offline", "online", "possible", "present" };
+  for (size_t i = 0; i < arraysize(kFiles); ++i) {
+    bool result = directory_manager_.AddFile(path_ + kFiles[i]);
+    ALOG_ASSERT(result);
+  }
+
+  is_initialized_ = true;
+}
+
+Dir* CpuFileHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  return directory_manager_.OpenDirectory(name);
+}
+
+void CpuFileHandler::OnMounted(const std::string& path) {
+  ALOG_ASSERT(util::EndsWithSlash(path));
+  path_ = path;
+}
+
+scoped_refptr<FileStream> CpuFileHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if ((oflag & O_ACCMODE) != O_RDONLY) {
+    errno = EACCES;
+    return NULL;
+  }
+  if (directory_manager_.StatDirectory(pathname))
+    return new DirectoryFileStream("cpu", pathname, this);
+  if (!directory_manager_.StatFile(pathname)) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  // Emulate Linux kernel's behavior as much as possible. See
+  // https://www.kernel.org/doc/Documentation/cputopology.txt
+
+  CpuFileContent* content = NULL;
+  if (EndsWith(pathname, "kernel_max", true)) {
+    content = new KernelMaxFileContent;
+  } else if (EndsWith(pathname, "offline", true)) {
+    content = new OfflineFileContent(num_processors_);
+  } else if (EndsWith(pathname, "online", true)) {
+    content = new OnlineFileContent;
+  } else if (EndsWith(pathname, "possible", true) ||
+             EndsWith(pathname, "present", true)) {
+    content = new PresentFileContent(num_processors_);
+  }
+  ALOG_ASSERT(content, "Unhandled path: %s", pathname.c_str());
+
+  return new CpuFile(pathname, content);
+}
+
+int CpuFileHandler::stat(const std::string& pathname, struct stat* out) {
+  scoped_refptr<FileStream> file = this->open(-1, pathname, O_RDONLY, 0);
+  if (!file) {
+    errno = ENOENT;
+    return -1;
+  }
+  return file->fstat(out);
+}
+
+int CpuFileHandler::statfs(const std::string& pathname, struct statfs* out) {
+  return DoStatFsForSys(out);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/cpu_file.h b/src/posix_translation/cpu_file.h
new file mode 100644
index 0000000..0e363f5
--- /dev/null
+++ b/src/posix_translation/cpu_file.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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 POSIX_TRANSLATION_CPU_FILE_H_
+#define POSIX_TRANSLATION_CPU_FILE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+// A handler for /sys/devices/system/cpu. This handler returns dummy directory
+// entries like "cpu0", "cpu1", etc. based on the actual processor count, when
+// directory contents of /sys/devices/system/cpu is requested. It also handles
+// some special files like /sys/devices/system/cpu/{possible,present}. We need
+// this because some apps check the number of processors by checking these
+// files and directories.
+class ARC_EXPORT CpuFileHandler : public FileSystemHandler {
+ public:
+  CpuFileHandler();
+  virtual ~CpuFileHandler();
+
+  // FileSystemHandler overrides:
+  virtual bool IsInitialized() const OVERRIDE;
+  virtual void Initialize() OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual void OnMounted(const std::string& path) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+
+ private:
+  bool is_initialized_;
+  int num_processors_;
+  std::string path_;
+  DirectoryManager directory_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(CpuFileHandler);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_CPU_FILE_H_
diff --git a/src/posix_translation/cpu_file_test.cc b/src/posix_translation/cpu_file_test.cc
new file mode 100644
index 0000000..f13bc24
--- /dev/null
+++ b/src/posix_translation/cpu_file_test.cc
@@ -0,0 +1,246 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "posix_translation/cpu_file.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/test_util/sysconf_util.h"
+
+namespace posix_translation {
+
+namespace {
+
+// The number of physical/online CPUs in this test. Note that each test can
+// change the number of online CPUs by creating another instance of
+// ScopedNumProcessorsConfiguredSetting in the test.
+static int kNumConfigured = 4;
+static int kNumOnline = 2;
+
+// Must be the same as the one in CpuFileHandler::Initialize.
+static const char* kFiles[] =
+  { "kernel_max", "offline", "online", "possible", "present" };
+
+class CpuFileHandlerTest : public FileSystemTestCommon {
+ protected:
+  CpuFileHandlerTest()
+      : num_configured_(kNumConfigured), num_online_(kNumOnline),
+        handler_(new CpuFileHandler) {
+  }
+
+  ScopedNumProcessorsConfiguredSetting num_configured_;
+  ScopedNumProcessorsOnlineSetting num_online_;
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    handler_->OnMounted("/foo/");
+    handler_->Initialize();
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(CpuFileHandlerTest);
+};
+
+}  // namespace
+
+TEST_F(CpuFileHandlerTest, TestInit) {
+  EXPECT_TRUE(handler_->IsInitialized());
+}
+
+TEST_F(CpuFileHandlerTest, TestStat) {
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/foo", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(0, handler_->stat("/foo/", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(-1, handler_->stat("/foo/cpu", &st));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(0, handler_->stat("/foo/cpu0", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(0, handler_->stat("/foo/cpu0/", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(-1, handler_->stat("/foo/cpu0_", &st));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->stat("/foo/cpu0/_", &st));
+  EXPECT_EQ(ENOENT, errno);
+
+  // |kNumConfigured| CPUs are available.
+  EXPECT_EQ(0, handler_->stat("/foo/cpu1", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(0, handler_->stat("/foo/cpu2", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(0, handler_->stat("/foo/cpu3", &st));
+  EXPECT_TRUE(S_ISDIR(st.st_mode));
+  EXPECT_EQ(-1, handler_->stat("/foo/cpu4", &st));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(CpuFileHandlerTest, TestOpen) {
+  EXPECT_TRUE(handler_->open(-1, "/foo", O_RDONLY, 0));
+  EXPECT_TRUE(handler_->open(-1, "/foo/", O_RDONLY, 0));
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_TRUE(handler_->open(-1, "/foo/cpu0", O_RDONLY, 0));
+  EXPECT_TRUE(handler_->open(-1, "/foo/cpu0/", O_RDONLY, 0));
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu0_", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu0/_", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+
+  // |kNumConfigured| CPUs are available.
+  EXPECT_TRUE(handler_->open(-1, "/foo/cpu1", O_RDONLY, 0));
+  EXPECT_TRUE(handler_->open(-1, "/foo/cpu2", O_RDONLY, 0));
+  EXPECT_TRUE(handler_->open(-1, "/foo/cpu3", O_RDONLY, 0));
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu4", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(CpuFileHandlerTest, TestOpenWritable) {
+  EXPECT_TRUE(!handler_->open(-1, "/foo", O_WRONLY, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_TRUE(!handler_->open(-1, "/foo", O_RDWR, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu0", O_WRONLY, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_TRUE(!handler_->open(-1, "/foo/cpu0", O_RDWR, 0));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(CpuFileHandlerTest, TestStatFile) {
+  struct stat st = {};
+  for (size_t i = 0; i < arraysize(kFiles); ++i) {
+    SCOPED_TRACE(kFiles[i]);
+    EXPECT_EQ(0, handler_->stat("/foo/" + std::string(kFiles[i]), &st));
+    EXPECT_TRUE(S_ISREG(st.st_mode));
+    // Retry without the correct prefix, "/foo". This should fail.
+    EXPECT_EQ(-1, handler_->stat("/" + std::string(kFiles[i]), &st));
+    EXPECT_EQ(ENOENT, errno);
+  }
+}
+
+TEST_F(CpuFileHandlerTest, TestOpenFile) {
+  for (size_t i = 0; i < arraysize(kFiles); ++i) {
+    SCOPED_TRACE(kFiles[i]);
+    scoped_refptr<FileStream> stream = handler_->open(
+        -1, "/foo/" + std::string(kFiles[i]), O_RDONLY, 0);
+    ASSERT_TRUE(stream);
+    // Confirm that mmap() is NOT suppored.
+    EXPECT_EQ(MAP_FAILED, stream->mmap(NULL, 1, PROT_READ, MAP_PRIVATE, 0));
+    EXPECT_EQ(EIO, errno);
+    // Retry without the correct prefix, "/foo". This should fail.
+    EXPECT_FALSE(handler_->open(-1, std::string(kFiles[i]), O_RDONLY, 0));
+    EXPECT_EQ(ENOENT, errno);
+  }
+}
+
+TEST_F(CpuFileHandlerTest, TestOpenFileWritable) {
+  EXPECT_TRUE(!handler_->open(-1, "/foo/online", O_WRONLY, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_TRUE(!handler_->open(-1, "/foo/online", O_RDWR, 0));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(CpuFileHandlerTest, TestKernelMaxFile) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/foo/kernel_max", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  ASSERT_EQ(3, stream->read(buf, sizeof(buf)));
+  EXPECT_STREQ("63\n", buf);
+}
+
+TEST_F(CpuFileHandlerTest, TestOnline) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/foo/online", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(base::StringPrintf("0-%d\n", kNumOnline - 1), buf);
+
+  // Test the case where only one CPU is online.
+  {
+    ScopedNumProcessorsOnlineSetting num_online(1);
+    memset(buf, 0, sizeof(buf));
+    EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+    EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+    EXPECT_STREQ("0\n", buf);
+  }
+}
+
+TEST_F(CpuFileHandlerTest, TestOffline) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/foo/offline", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(base::StringPrintf(
+      "%d-%d\n", kNumOnline, kNumConfigured - 1), buf);
+
+  // Test the case where all CPUs except one are online.
+  {
+    ScopedNumProcessorsOnlineSetting num_online(kNumConfigured - 1);
+    memset(buf, 0, sizeof(buf));
+    EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+    EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+    EXPECT_EQ(base::StringPrintf("%d\n", kNumConfigured - 1), buf);
+  }
+
+  // Test the case where all CPUs are online.
+  {
+    ScopedNumProcessorsOnlineSetting num_online(kNumConfigured);
+    memset(buf, 0, sizeof(buf));
+    EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+    EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+    EXPECT_STREQ("\n", buf);
+  }
+}
+
+TEST_F(CpuFileHandlerTest, TestPossible) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/foo/possible", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(base::StringPrintf("0-%d\n", kNumConfigured - 1), buf);
+}
+
+TEST_F(CpuFileHandlerTest, TestPresent) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/foo/present", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(base::StringPrintf("0-%d\n", kNumConfigured - 1), buf);
+}
+
+TEST_F(CpuFileHandlerTest, TestDirectoryEntries) {
+  scoped_ptr<Dir> dir(handler_->OnDirectoryContentsNeeded("/foo"));
+  ASSERT_TRUE(dir.get());
+
+  // Add 2 for "." and ".." entries.
+  const int kNumDirectories = kNumConfigured + 2;
+  const int kNumFiles = arraysize(kFiles);
+
+  int num_directories_found = 0;
+  int num_files_found = 0;
+  while (true) {
+    dirent ent;
+    if (!dir->GetNext(&ent))
+      break;
+    if (ent.d_type == DT_DIR)
+      ++num_directories_found;
+    else if (ent.d_type == DT_REG)
+      ++num_files_found;
+  }
+
+  EXPECT_EQ(kNumDirectories, num_directories_found);
+  EXPECT_EQ(kNumFiles, num_files_found);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/cpu_info_file.cc b/src/posix_translation/cpu_info_file.cc
new file mode 100644
index 0000000..d46f06f
--- /dev/null
+++ b/src/posix_translation/cpu_info_file.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 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 "posix_translation/cpu_info_file.h"
+
+#include <time.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "posix_translation/readonly_memory_file.h"
+#include "posix_translation/statfs.h"
+
+namespace posix_translation {
+
+namespace {
+
+// A file stream for readonly files managed by CpuInfoFileHandler.
+class CpuInfoFile : public ReadonlyMemoryFile {
+ public:
+  // Creates a ReadonlyMemoryFile stream with |header| and |footer|.
+  // See CpuInfoFileHandler for more details.
+  CpuInfoFile(const std::string& pathname,
+              const std::string& header,
+              const std::string& body,
+              const std::string& footer)
+      // Pass the UNIX epoch. GetContent() below updates this later.
+      : ReadonlyMemoryFile(pathname, EIO, 0),
+        num_online_processors_(-1),
+        header_(header), body_(body), footer_(footer) {}
+
+ protected:
+  virtual ~CpuInfoFile() {}
+
+ private:
+  virtual const Content& GetContent() OVERRIDE {
+    UpdateContent();
+    ALOG_ASSERT(num_online_processors_ > 0);
+    ALOG_ASSERT(!content_.empty());
+    set_mtime(time(NULL));
+    return content_;
+  }
+
+  // Updates |content_| when needed.
+  void UpdateContent() {
+    // The cpuinfo file should be generated based on the number of online
+    // CPUs, rather than the number of configured CPUs.
+    const int num_online_processors = sysconf(_SC_NPROCESSORS_ONLN);
+    ALOG_ASSERT(num_online_processors > 0);
+
+    // We should not update the content when it is unnecessary to not slow down
+    // a series of short CpuInfoFile::read() operations to read through the
+    // file. Otherwise, in the worst case, they can touch content_.size()
+    // squared bytes of memory in total, which can be very slow.
+    // TODO(crbug.com/368344): Once _SC_NPROCESSORS_ONLN is fully implemented
+    // for Bare Metal ARM, we should check how often the ARM Linux kernel
+    // (especially the one for Pit/Pi ARM Chromebooks) changes the number of
+    // CPUs in practice.
+    if (num_online_processors_ == num_online_processors)
+      return;
+    num_online_processors_ = num_online_processors;
+
+    std::string s = header_;
+    for (int i = 0; i < num_online_processors; ++i) {
+      std::vector<std::string> subst;
+      subst.push_back(base::StringPrintf("%d", i));
+      s += ReplaceStringPlaceholders(body_, subst, NULL);
+    }
+    s += footer_;
+    content_.assign(s.begin(), s.end());
+  }
+
+  int num_online_processors_;
+  const std::string header_;
+  const std::string body_;
+  const std::string footer_;
+  Content content_;
+
+  DISALLOW_COPY_AND_ASSIGN(CpuInfoFile);
+};
+
+}  // namespace
+
+CpuInfoFileHandler::CpuInfoFileHandler(const std::string& header,
+                                       const std::string& body,
+                                       const std::string& footer)
+    : FileSystemHandler("CpuInfoFileHandler"),
+      header_(header), body_(body), footer_(footer) {
+  // |body| must contain (exactly) one placeholder, "$1".
+  ALOG_ASSERT(body_.find("$1") != std::string::npos);
+  ALOG_ASSERT(body_.find("$2") == std::string::npos);
+}
+
+CpuInfoFileHandler::~CpuInfoFileHandler() {
+}
+
+Dir* CpuInfoFileHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  return NULL;
+}
+
+scoped_refptr<FileStream> CpuInfoFileHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (!EndsWith(pathname, "/cpuinfo", true)) {
+    ALOGE("Unknown path: %s. CpuInfoFileHandler might not be mounted properly.",
+          pathname.c_str());
+    errno = ENOENT;
+    return NULL;
+  }
+  return new CpuInfoFile(pathname, header_, body_, footer_);
+}
+
+int CpuInfoFileHandler::stat(const std::string& pathname, struct stat* out) {
+  scoped_refptr<FileStream> file = this->open(-1, pathname, O_RDONLY, 0);
+  if (!file) {
+    ALOGE("Unknown path: %s. CpuInfoFileHandler might not be mounted properly.",
+          pathname.c_str());
+    errno = ENOENT;
+    return -1;
+  }
+  return file->fstat(out);
+}
+
+int CpuInfoFileHandler::statfs(const std::string& pathname,
+                               struct statfs* out) {
+  return DoStatFsForProc(out);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/cpu_info_file.h b/src/posix_translation/cpu_info_file.h
new file mode 100644
index 0000000..686d587
--- /dev/null
+++ b/src/posix_translation/cpu_info_file.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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 POSIX_TRANSLATION_CPU_INFO_FILE_H_
+#define POSIX_TRANSLATION_CPU_INFO_FILE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+// A handler for /proc/cpuinfo. This handler returns a file based on the actual
+// online processor count.
+class ARC_EXPORT CpuInfoFileHandler : public FileSystemHandler {
+ public:
+  // |header|, |body|, and |footer| are used for generating the content of the
+  // cpuinfo file. |body| must contain "$1" and is repeated N times (where N is
+  // the number of CPUs online). Both |header| and |footer| can be empty when
+  // they are not needed.
+  // Example:
+  // When N is 2, |header| is "H", |body| is "B$1", and |footer| is "F", the
+  // content of the file will be "HB0B1F".
+  CpuInfoFileHandler(const std::string& header,
+                     const std::string& body,
+                     const std::string& footer);
+  virtual ~CpuInfoFileHandler();
+
+  // FileSystemHandler overrides:
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+
+ private:
+  const std::string header_;
+  const std::string body_;
+  const std::string footer_;
+
+  DISALLOW_COPY_AND_ASSIGN(CpuInfoFileHandler);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_CPU_INFO_FILE_H_
diff --git a/src/posix_translation/cpu_info_file_test.cc b/src/posix_translation/cpu_info_file_test.cc
new file mode 100644
index 0000000..ab4232d
--- /dev/null
+++ b/src/posix_translation/cpu_info_file_test.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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 <time.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "gtest/gtest.h"
+#include "posix_translation/cpu_info_file.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/test_util/sysconf_util.h"
+
+namespace posix_translation {
+
+namespace {
+
+const char kHeader[] = "HHH";
+const char kBody[] = "!$1!";
+const char kFooter[] = "FFF";
+
+// The number of physical/online CPUs in this test. See also:
+// cpu_info_test.cc.
+static int kNumConfigured = 4;
+static int kNumOnline = 2;
+
+class CpuInfoFileHandlerTest : public FileSystemTestCommon {
+ protected:
+  CpuInfoFileHandlerTest()
+      : num_configured_(kNumConfigured), num_online_(kNumOnline),
+        handler_(new CpuInfoFileHandler(kHeader, kBody, kFooter)) {
+  }
+
+  ScopedNumProcessorsConfiguredSetting num_configured_;
+  ScopedNumProcessorsOnlineSetting num_online_;
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CpuInfoFileHandlerTest);
+};
+
+}  // namespace
+
+TEST_F(CpuInfoFileHandlerTest, TestInit) {
+}
+
+TEST_F(CpuInfoFileHandlerTest, TestStatFile) {
+  struct stat st;
+  EXPECT_EQ(0, handler_->stat("/cpuinfo", &st));
+  EXPECT_TRUE(S_ISREG(st.st_mode));
+
+  // Confirm that stat() always fills the current time in st_mtime.
+  EXPECT_NE(0, static_cast<time_t>(st.st_mtime));
+  EXPECT_NEAR(time(NULL), st.st_mtime, 60.0 /* seconds */);
+
+  EXPECT_EQ(-1, handler_->stat("/cpuinf", &st));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->stat("/cpuinf0", &st));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(CpuInfoFileHandlerTest, TestOpenFile) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/cpuinfo", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  // Confirm that mmap() is NOT suppored.
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, 1, PROT_READ, MAP_PRIVATE, 0));
+  EXPECT_EQ(EIO, errno);
+
+  EXPECT_FALSE(handler_->open(-1, "/cpuinf", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_FALSE(handler_->open(-1, "/cpuinf0", O_RDONLY, 0));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(CpuInfoFileHandlerTest, TestRead) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/cpuinfo", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char buf[128] = {};  // for easier \0 termination.
+  EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+  EXPECT_STREQ("HHH!0!!1!FFF", buf);
+
+  // Test the case where only one CPU is online.
+  {
+    ScopedNumProcessorsOnlineSetting num_online(1);
+    memset(buf, 0, sizeof(buf));
+    EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+    EXPECT_LT(0, stream->read(buf, sizeof(buf)));
+    EXPECT_STREQ("HHH!0!FFF", buf);
+  }
+}
+
+TEST_F(CpuInfoFileHandlerTest, TestFstat) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, "/cpuinfo", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_TRUE(S_ISREG(st.st_mode));
+
+  // Confirm that fstat() always fills the current time in st_mtime too.
+  EXPECT_NE(0, static_cast<time_t>(st.st_mtime));
+  EXPECT_NEAR(time(NULL), st.st_mtime, 60.0 /* seconds */);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/crx_file.cc b/src/posix_translation/crx_file.cc
new file mode 100644
index 0000000..2b1da15
--- /dev/null
+++ b/src/posix_translation/crx_file.cc
@@ -0,0 +1,109 @@
+// Copyright 2014 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 "posix_translation/crx_file.h"
+
+#include <stdarg.h>
+
+#include "base/strings/utf_string_conversions.h"
+#include "common/alog.h"
+#include "common/trace_event.h"
+#include "ppapi/cpp/private/ext_crx_file_system_private.h"
+
+namespace posix_translation {
+
+namespace {
+
+int Fcntl(scoped_refptr<FileStream> stream, int cmd, ...) {
+  va_list ap;
+  va_start(ap, cmd);
+  const int result = stream->fcntl(cmd, ap);
+  va_end(ap);
+  return result;
+}
+
+}  // namespace
+
+CrxFileHandler::CrxFileHandler()
+    : PepperFileHandler("CrxFileHandler", 16 /* cache size */),
+      factory_(this) {
+}
+
+CrxFileHandler::~CrxFileHandler() {
+  for (std::map<StreamCacheKey, scoped_refptr<FileStream> >::iterator it =
+           stream_cache_.begin(); it != stream_cache_.end(); ++it) {
+    it->second->ReleaseFileRef();
+  }
+}
+
+scoped_refptr<FileStream> CrxFileHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t mode) {
+  // Check the |stream_cache_| first. Caching a FileStream object for the CRX
+  // filesystem is safe since the filesystem is always readonly. However,
+  // associating two independent FDs to a single FileStream is not safe. If we
+  // do that, the unrelated two FDs will share the same file offset (held in
+  // the native FD in FileIOWrapper) which is not what we want.
+  const StreamCacheKey key = std::make_pair(pathname, oflag);
+  std::map<StreamCacheKey, scoped_refptr<FileStream> >::const_iterator it =
+      stream_cache_.find(key);
+  if (it != stream_cache_.end()) {
+    if (it->second->HasOneRef()) {
+      // Reset the status of the native FD,
+      int result = it->second->lseek(SEEK_SET, 0);
+      ALOG_ASSERT(result == 0, "lseek: %s", pathname.c_str());
+      result = Fcntl(it->second, F_SETFL, oflag);
+      ALOG_ASSERT(result == 0, "fcntl: %s", pathname.c_str());
+      // ..and then return the cached stream.
+      ARC_STRACE_REPORT("CrxFileHandler::open: Reuse cached stream: %s",
+                          pathname.c_str());
+      return it->second;
+    }
+    ARC_STRACE_REPORT("CrxFileHandler::open: Cached stream in use: %s",
+                        pathname.c_str());
+  } else {
+    ARC_STRACE_REPORT("CrxFileHandler::open: Cached stream not found: %s",
+                        pathname.c_str());
+  }
+
+  // If it is not cached, or the cached stream is in use, fall back to the
+  // default open() implementation in the parent class which issues an IPC.
+  scoped_refptr<FileStream> new_stream =
+      PepperFileHandler::open(fd, pathname, oflag, mode);
+  if (!new_stream)
+    return  NULL;
+
+  // Always overwrite the map with the new stream.
+  if (it != stream_cache_.end())
+    it->second->ReleaseFileRef();
+  stream_cache_[key] = new_stream;
+  // Add a file ref so that the stream never goes into the "closed" state
+  // even if close() is called against the stream.
+  new_stream->AddFileRef();
+  return new_stream;
+}
+
+void CrxFileHandler::OpenPepperFileSystem(pp::Instance* instance) {
+  pp::ExtCrxFileSystemPrivate crxfs_res(instance);
+  pp::CompletionCallbackWithOutput<pp::FileSystem> callback =
+      factory_.NewCallbackWithOutput(&CrxFileHandler::OnFileSystemOpen);
+  TRACE_EVENT_ASYNC_BEGIN0(ARC_TRACE_CATEGORY,
+                           "CrxFileHandler::OpenPepperFileSystem",
+                           this);
+  const int result = crxfs_res.Open(callback);
+  ALOG_ASSERT(
+      result == PP_OK_COMPLETIONPENDING,
+      "Failed to create pp::ExtCrxFileSystemPrivate, error: %d", result);
+}
+
+void CrxFileHandler::OnFileSystemOpen(int32_t result,
+                                      const pp::FileSystem& file_system) {
+  TRACE_EVENT_ASYNC_END1(ARC_TRACE_CATEGORY,
+                         "CrxFileHandler::OpenPepperFileSystem",
+                         this, "result", result);
+  if (result != PP_OK)
+    LOG_FATAL("Failed to open pp::ExtCrxFileSystemPrivate, error: %d", result);
+  SetPepperFileSystem(new pp::FileSystem(file_system), "/", "/");
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/crx_file.h b/src/posix_translation/crx_file.h
new file mode 100644
index 0000000..ed18976
--- /dev/null
+++ b/src/posix_translation/crx_file.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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 POSIX_TRANSLATION_CRX_FILE_H_
+#define POSIX_TRANSLATION_CRX_FILE_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/pepper_file.h"
+#include "ppapi/cpp/file_system.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace posix_translation {
+
+// A handler which handles read-only files in a CRX archive.
+// TODO(crbug.com/274451): This handler does not support accessing files in an
+// imported CRX specified by "import" section of manifest.json for the main CRX.
+class ARC_EXPORT CrxFileHandler : public PepperFileHandler {
+ public:
+  CrxFileHandler();
+  virtual ~CrxFileHandler();
+
+  // Overrides PepperFileHandler's so that the function can return a cached
+  // FileStream object.
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t mode) OVERRIDE;
+
+  // Overrides PepperFileHandler's so that the function initializes a CRX
+  // filesystem instead of the LOCALPERSISTENT HTML5 filesystem.
+  virtual void OpenPepperFileSystem(pp::Instance* instance) OVERRIDE;
+
+ private:
+  void OnFileSystemOpen(int32_t result,
+                        const pp::FileSystem& file_system);
+  pp::CompletionCallbackFactory<CrxFileHandler> factory_;
+
+  typedef std::pair<std::string, int> StreamCacheKey;
+  std::map<StreamCacheKey, scoped_refptr<FileStream> > stream_cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrxFileHandler);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_CRX_FILE_H_
diff --git a/src/posix_translation/crx_file_test.cc b/src/posix_translation/crx_file_test.cc
new file mode 100644
index 0000000..e377d9f
--- /dev/null
+++ b/src/posix_translation/crx_file_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 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 "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "posix_translation/crx_file.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+class CrxFileTest : public FileSystemTestCommon {
+ public:
+  CrxFileTest() {}
+  virtual void SetUp() OVERRIDE;
+
+ private:
+  scoped_ptr<PepperFileHandler> handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrxFileTest);
+};
+
+void CrxFileTest::SetUp() {
+  FileSystemTestCommon::SetUp();
+  SetUpCrxFileSystemConstructExpectations(kInstanceNumber);
+  handler_.reset(new CrxFileHandler);
+  handler_->OpenPepperFileSystem(instance_.get());
+  {
+    // CrxFileHandler::OnFileSystemOpen tries to acquire the mutex.
+    base::AutoUnlock unlock(file_system_->mutex());
+    RunCompletionCallbacks();
+  }
+}
+
+TEST_F(CrxFileTest, TestConstructDestruct) {
+  // Execute this empty test to let Valgrind examine the object construction
+  // code.
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_ashmem.cc b/src/posix_translation/dev_ashmem.cc
new file mode 100644
index 0000000..35d2c37
--- /dev/null
+++ b/src/posix_translation/dev_ashmem.cc
@@ -0,0 +1,414 @@
+// Copyright 2014 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 "posix_translation/dev_ashmem.h"
+
+#include <fcntl.h>
+#include <linux/ashmem.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+
+#include <algorithm>
+
+#include "base/strings/string_util.h"  // strlcpy
+#include "posix_translation/dir.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+int DoStatLocked(const std::string& pathname, struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = S_IFCHR | 0666;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  // st_uid, st_gid, st_size, st_blocks should be zero.
+
+  // TODO(crbug.com/242337): Fill st_dev if needed.
+  out->st_rdev = DeviceHandler::GetDeviceId(pathname);
+  return 0;
+}
+
+}  // namespace
+
+DevAshmemHandler::DevAshmemHandler() : DeviceHandler("DevAshmemHandler") {
+}
+
+DevAshmemHandler::~DevAshmemHandler() {
+}
+
+scoped_refptr<FileStream> DevAshmemHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  return new DevAshmem(fd, pathname, oflag);
+}
+
+int DevAshmemHandler::stat(const std::string& pathname, struct stat* out) {
+  return DoStatLocked(pathname, out);
+}
+
+DevAshmem::DevAshmem(int fd, const std::string& pathname, int oflag)
+    : DeviceStream(oflag, pathname),
+      fd_(fd),
+      size_(0),
+      content_(static_cast<uint8_t*>(MAP_FAILED)),
+      mmap_length_(0),
+      offset_(0),
+      has_private_mapping_(false),
+      state_(STATE_INITIAL) {
+}
+
+DevAshmem::~DevAshmem() {
+  if (state_ == STATE_UNMAP_DELAYED)
+    ::munmap(content_, mmap_length_);
+}
+
+int DevAshmem::fstat(struct stat* out) {
+  return DoStatLocked(pathname(), out);
+}
+
+int DevAshmem::ioctl(int request, va_list ap) {
+  const unsigned int urequest = static_cast<unsigned int>(request);
+
+  if (urequest == ASHMEM_SET_NAME) {
+    return IoctlSetName(request, ap);
+  } else if (urequest == ASHMEM_GET_NAME) {
+    return IoctlGetName(request, ap);
+  } else if (urequest == ASHMEM_SET_SIZE) {
+    return IoctlSetSize(request, ap);
+  } else if (urequest == ASHMEM_GET_SIZE) {
+    return IoctlGetSize(request, ap);
+  } else if (urequest == ASHMEM_SET_PROT_MASK) {
+    return IoctlSetProtMask(request, ap);
+  } else if (urequest == ASHMEM_PIN) {
+    return IoctlPin(request, ap);
+  } else if (urequest == ASHMEM_UNPIN) {
+    return IoctlUnpin(request, ap);
+  }
+  ALOGE("ioctl command %u is not supported", urequest);
+  errno = EINVAL;
+  return -1;
+}
+
+off64_t DevAshmem::lseek(off64_t offset, int whence) {
+  if (!size_) {
+    // ASHMEM_SET_SIZE has not been called yet. Return EINVAL.
+    // This behavior is compatible with the Linux kernel.
+    errno = EINVAL;
+    return -1;
+  }
+  if (state_ == STATE_INITIAL && !has_private_mapping_) {
+    // This behavior is compatible with the Linux kernel too.
+    errno = EBADF;
+    return -1;
+  }
+
+  switch (whence) {
+    case SEEK_SET:
+      offset_ = offset;
+      break;
+    case SEEK_CUR:
+      offset_ += offset;
+      break;
+    case SEEK_END:
+      offset_ = size_ + offset;
+      break;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+  return offset_;
+}
+
+// Note: [addr, addr+length) should be valid even if a part of original mmaped
+// region is released partially by munmap(). MemoryRegion manages the memory
+// layout, and calls each madvise implementation so that [addr, addr+length)
+// is always valid for each FileStream instance.
+int DevAshmem::madvise(void* addr, size_t length, int advice) {
+  if (advice != MADV_DONTNEED)
+    return FileStream::madvise(addr, length, advice);
+
+  // TODO(crbug.com/427417): Since MemoryRegion handles memory layout
+  // information by FileStream unit basis, we do not have page by page prot
+  // information that can be updated by subsequent mmap and mprotect.
+  // Use the relaxed protection mode (R/W) here.
+  void* result = ::mmap(addr, length, PROT_READ | PROT_WRITE,
+                        MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
+  if (result == addr)
+    return 0;
+  ALOGE("An internal mmap call for DevAshmem::madvise returns an unexpected "
+        "address %p for expected address %p", result, addr);
+  // Return 1 for an unrecoverable error to go LOG_ALWAYS_FATAL.
+  return 1;
+}
+
+void* DevAshmem::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  if (!size_) {
+    // This behavior is compatible with the Linux kernel.
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  const int fixed_flag = (flags & MAP_FIXED);
+  if (!(flags & MAP_SHARED)) {
+    // Handling MAP_PRIVATE is simple. We can just emulate it with
+    // MAP_ANONYMOUS. We should NOT share the content with a previously
+    // mapped MAP_SHARED region even when it exists. We can also ignore
+    // the offset value as long as it is multiples of the page size (which
+    // has already been checked in VFS). The stream class does not remember
+    // the returned address.
+    void* result = ::mmap(
+        addr, length, prot, MAP_ANONYMOUS | MAP_PRIVATE | fixed_flag, -1, 0);
+    if (result != MAP_FAILED)
+      has_private_mapping_ = true;
+    return result;
+  }
+
+  if (offset) {
+    // For simplicity, reject MAP_SHARED mmaps with non-zero offset. Linux
+    // kernel supports it though.
+    ALOGE("Non-zero offset with MAP_SHARED is currently not supported: %ld",
+          offset);
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  if (content_ == MAP_FAILED) {
+    ALOG_ASSERT(state_ == STATE_INITIAL);
+    // TODO(crbug.com/427417): Since subsequent mmap calls may reuse the
+    // address, use the relaxed protection mode (R/W).
+    content_ = static_cast<uint8_t*>(
+        ::mmap(addr, length, PROT_READ | PROT_WRITE,
+               MAP_ANONYMOUS | MAP_PRIVATE | fixed_flag, -1, 0));
+    mmap_length_ = length;
+    ARC_STRACE_REPORT("MAP_ANONYMOUS returned %p (name_=%s)",
+                      content_, name_.c_str());
+    state_ = STATE_MAPPED;
+    return content_;
+  }
+
+  // mmap(MAP_SHARED) is called twice (or more).
+  ALOG_ASSERT(state_ != STATE_INITIAL);
+
+  if (state_ == STATE_PARTIALLY_UNMAPPED) {
+    ALOGE("The second mmap was called after munmap partially unmmapped the "
+          "region");
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  if (length != mmap_length_) {
+    ALOGE("The second mmap was called with a different length (%zu) than "
+          "the first one (%zu)", length, mmap_length_);
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  if (fixed_flag && static_cast<uint8_t*>(addr) != content_) {
+    ALOGE("The second mmap was called with MAP_FIXED (addr=%p, content_=%p)",
+          addr, content_);
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  if (state_ == STATE_UNMAP_DELAYED)
+    state_ = STATE_MAPPED;
+
+  return content_;
+}
+
+int DevAshmem::munmap(void* addr_vp, size_t length) {
+  ARC_STRACE_REPORT("munmap(%p, %zu) is called for fd_=%d, name_=%s",
+                    addr_vp, length, fd_, name_.c_str());
+
+  uint8_t* addr = static_cast<uint8_t*>(addr_vp);
+  if (!IsMapShared(addr)) {
+    // The munmap request is against one of the MAP_PRIVATE regions. Just call
+    // ::munmap.
+    const int result = ::munmap(addr, length);
+    ALOG_ASSERT(!result);
+    return 0;
+  }
+
+  if ((state_ == STATE_MAPPED) &&
+      (addr == content_) && (length == mmap_length_)) {
+    // Full unmap of the MAP_SHARED region. Do not call unmap yet so that
+    // subsequent read() calls can read the content. Note that we do support
+    // "mmap, full-munmap, then read" cases, but do not support "mmap,
+    // partial-munmap, then read" ones. This is because the latter is uncommon
+    // and CTS does not require it.
+    state_ = STATE_UNMAP_DELAYED;
+    return 0;
+  }
+
+  if (state_ == STATE_UNMAP_DELAYED) {
+    ALOGE("munmap(%p, %zu) is called against a memory region which has already "
+          "been unmapped. Ignore the call.", addr, length);
+    return 0;
+  }
+
+  state_ = STATE_PARTIALLY_UNMAPPED;
+  const int result = ::munmap(addr, length);
+  ALOG_ASSERT(!result);
+  return 0;
+}
+
+ssize_t DevAshmem::pread(void* buf, size_t count, off64_t offset) {
+  if ((oflag() & O_ACCMODE) == O_WRONLY) {
+    errno = EBADF;
+    return -1;
+  }
+  if (!size_) {
+    // ASHMEM_SET_SIZE has not been called yet. Return EOF to make one CTS test
+    //  cts.CtsOsTestCases:android.os.cts.ParcelFileDescriptorTest#testFromData
+    // happy.
+    return 0;
+  }
+  if (state_ == STATE_INITIAL && !has_private_mapping_) {
+    // This behavior is compatible with the Linux kernel.
+    errno = EBADF;
+    return -1;
+  }
+
+  const ssize_t read_max = size_ - offset;
+  if (read_max <= 0)
+    return 0;
+
+  if (state_ == STATE_PARTIALLY_UNMAPPED) {
+    ALOG_ASSERT(content_ != MAP_FAILED);
+    // Calling memcpy is not safe since |content_| might be pointing
+    // to a unmmapped page.
+    errno = EBADF;
+    return -1;
+  }
+
+  // If there is a MAP_SHARED region, copy the content from there. If not, just
+  // fill zeros.
+  const size_t read_size = std::min<int64_t>(count, read_max);
+  if (content_ != MAP_FAILED)
+    memcpy(buf, content_ + offset, read_size);
+  else
+    memset(buf, 0, read_size);
+
+  return read_size;
+}
+
+ssize_t DevAshmem::read(void* buf, size_t count) {
+  ssize_t result = this->pread(buf, count, offset_);
+  if (result > 0)
+    offset_ += result;
+  return result;
+}
+
+ssize_t DevAshmem::write(const void* buf, size_t count) {
+  // This behavior is compatible with the Linux kernel.
+  errno = EINVAL;
+  return -1;
+}
+
+bool DevAshmem::ReturnsSameAddressForMultipleMmaps() const {
+  return true;
+}
+
+void DevAshmem::OnUnmapByOverwritingMmap(void* addr_vp, size_t length) {
+  uint8_t* addr = static_cast<uint8_t*>(addr_vp);
+  if (!IsMapShared(addr))
+    return;
+  // This object no longer owns [addr, addr + length). Change the |state_| so
+  // that subsequent read and pread calls will fail. Do not transit to
+  // STATE_UNMAP_DELAYED even when |length| is equal to |mmap_length_| since
+  // the object no longer ownes the memory region.
+  if (state_ == STATE_MAPPED)
+    state_ = STATE_PARTIALLY_UNMAPPED;
+}
+
+const char* DevAshmem::GetStreamType() const {
+  return "ashmem";
+}
+
+size_t DevAshmem::GetSize() const {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  return size_;
+}
+
+std::string DevAshmem::GetAuxInfo() const {
+  return name_;
+}
+
+bool DevAshmem::IsMapShared(uint8_t* addr) const {
+  return (content_ != MAP_FAILED) &&
+      (content_ <= addr) && (addr < content_ + mmap_length_);
+}
+
+int DevAshmem::IoctlSetName(int request, va_list ap) {
+  if (state_ != STATE_INITIAL || has_private_mapping_) {
+    // This behavior is compatible with the Linux kernel.
+    errno = EINVAL;
+    return -1;
+  }
+  const char* name = va_arg(ap, const char*);
+  ALOG_ASSERT(name);
+  ARC_STRACE_REPORT("ASHMEM_SET_NAME: %s", name);
+  name_ = name;
+  return 0;
+}
+
+int DevAshmem::IoctlGetName(int request, va_list ap) {
+  char* name = va_arg(ap, char*);
+  ALOG_ASSERT(name);
+  base::strlcpy(name, name_.c_str(), ASHMEM_NAME_LEN);
+  ARC_STRACE_REPORT("ASHMEM_GET_NAME: %s", name);
+  return 0;
+}
+
+int DevAshmem::IoctlSetSize(int request, va_list ap) {
+  if (state_ != STATE_INITIAL || has_private_mapping_) {
+    // This behavior is compatible with the Linux kernel.
+    errno = EINVAL;
+    return -1;
+  }
+  size_ = va_arg(ap, size_t);
+  // Note: cts.CtsOsTestCases:android.os.cts.MemoryFileTest#testLength
+  // calls this with INT_MIN.
+  ARC_STRACE_REPORT("ASHMEM_SET_SIZE: %zu (%zuMB)", size_, size_ / 1024 / 1024);
+  return 0;
+}
+
+int DevAshmem::IoctlGetSize(int request, va_list ap) {
+  return size_;
+}
+
+int DevAshmem::IoctlPin(int request, va_list ap) {
+  // TODO(crbug.com/379838): Implement this once a new IRT for handling real
+  // shared memory is added. For now, return the same value as ashmem-host.c
+  // as a safe fallback.
+  ALOGW("ASHMEM_PIN: not implemented: fd=%d", fd_);
+  return ASHMEM_NOT_PURGED;
+}
+
+int DevAshmem::IoctlUnpin(int request, va_list ap) {
+  // TODO(crbug.com/379838): Implement this too.
+  ALOGW("ASHMEM_UNPIN: not implemented: fd=%d", fd_);
+  return ASHMEM_IS_UNPINNED;
+}
+
+int DevAshmem::IoctlSetProtMask(int request, va_list ap) {
+  // TODO(crbug.com/379838): Implement this too.
+  int prot = va_arg(ap, int);
+  ALOGW("ASHMEM_SET_PROT_MASK: not implemented: fd=%d, prot=%d", fd_, prot);
+  return 0;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_ashmem.h b/src/posix_translation/dev_ashmem.h
new file mode 100644
index 0000000..3b3a2d3
--- /dev/null
+++ b/src/posix_translation/dev_ashmem.h
@@ -0,0 +1,121 @@
+// Copyright 2014 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 POSIX_TRANSLATION_DEV_ASHMEM_H_
+#define POSIX_TRANSLATION_DEV_ASHMEM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/synchronization/lock.h"
+#include "common/export.h"
+#include "posix_translation/device_file.h"
+
+namespace posix_translation {
+
+// This handler is for emulating Ashmem. AshMem is short for Anonymous Shared
+// Memory, and Android has the special device in its specially-configured
+// Linux kernel. User space code in Android uses the device like this:
+//
+// fd = open(“/dev/ashmem”, O_RDWR);
+// ioctl(fd, ASHMEM_SET_NAME, name);
+// ioctl(fd, ASHMEM_SET_SIZE, size);
+// p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+// /* read/write the memory region |p|. */
+// …
+// /* Pass the |fd| to another process via Binder. */
+class ARC_EXPORT DevAshmemHandler : public DeviceHandler {
+ public:
+  DevAshmemHandler();
+  virtual ~DevAshmemHandler();
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevAshmemHandler);
+};
+
+class DevAshmem : public DeviceStream {
+ public:
+  DevAshmem(int fd, const std::string& pathname, int oflag);
+
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual int madvise(void* addr, size_t length, int advice) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t pread(void* buf, size_t count, off64_t offset) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual bool ReturnsSameAddressForMultipleMmaps() const OVERRIDE;
+  virtual void OnUnmapByOverwritingMmap(void* addr, size_t length) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+  virtual size_t GetSize() const OVERRIDE;
+  virtual std::string GetAuxInfo() const OVERRIDE;
+
+ protected:
+  virtual ~DevAshmem();
+
+ private:
+  // Returns true if |addr| is in [content_, content_ + mmap_length_).
+  bool IsMapShared(uint8_t* addr) const;
+
+  int IoctlSetName(int request, va_list ap);
+  int IoctlGetName(int request, va_list ap);
+  int IoctlSetSize(int request, va_list ap);
+  int IoctlGetSize(int request, va_list ap);
+  int IoctlPin(int request, va_list ap);
+  int IoctlUnpin(int request, va_list ap);
+  int IoctlSetProtMask(int request, va_list ap);
+
+  int fd_;  // our VFS's FD, not NaCl's. This is for debug prints.
+  size_t size_;  // passed via ioctl. Might not be multiples of the page size.
+  std::string name_;  // passed via ioctl.
+
+  uint8_t* content_;  // the MAP_ANONOYMOUS region for emulating MAP_SHARED.
+  size_t mmap_length_;  // the length of the |content_|.
+  off_t offset_;  // the file offset for read, pread, write, and lseek.
+
+  // True if mmap with MAP_PRIVATE has succeeded at least once.
+  bool has_private_mapping_;
+
+  // The current status of the |content_|. The possible transition of the
+  // state is as follows:
+  //
+  //         +--------+
+  //         v        +
+  // 0+----->1+------>2        3
+  //         +                 ^
+  //         +-----------------+
+  //
+  // The transition from 2 to 1 happens if mmap is called after full-munmap.
+  // Note that neither ioctl nor mmap with MAP_PRIVATE affects the |state_|.
+  enum {
+    // mmap with MAP_SHARED has not been called yet. |content_| is MAP_FAILED.
+    STATE_INITIAL = 0,
+    // mmap with MAP_SHARED has been called and munmap has not. |content_|
+    // points to a memory region returned from the mmap.
+    STATE_MAPPED = 1,
+    // The region has been fully unmapped by VFS::munmap, but the actual munmap
+    // IRT has not been called yet to allow VFS::read and pread to read the
+    // |content_|. Some CTS tests fail without this trick.
+    STATE_UNMAP_DELAYED = 2,
+    // The region has been partially unmapped by VFS::munmap, and the actual
+    // munmap IRT has also been called. Subsequent read, pread and mmap calls
+    // will fail.
+    STATE_PARTIALLY_UNMAPPED = 3,
+  } state_;
+
+  DISALLOW_COPY_AND_ASSIGN(DevAshmem);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DEV_ASHMEM_H_
diff --git a/src/posix_translation/dev_ashmem_test.cc b/src/posix_translation/dev_ashmem_test.cc
new file mode 100644
index 0000000..b39f2c0
--- /dev/null
+++ b/src/posix_translation/dev_ashmem_test.cc
@@ -0,0 +1,580 @@
+// Copyright 2014 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 <asm/types.h>  // for __u32 used in linux/ashmem.h
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <linux/ashmem.h>
+#include <sys/sysmacros.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dev_ashmem.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+// We use random numbers for this test.
+const int kNullMajorId = 42;
+const int kNullMinorId = 43;
+
+namespace posix_translation {
+
+class DevAshmemTest : public FileSystemTestCommon {
+ protected:
+  DevAshmemTest() : handler_(new DevAshmemHandler) {
+  }
+
+  int CallIoctl(scoped_refptr<FileStream> stream, int request, ...) {
+    va_list ap;
+    va_start(ap, request);
+    const int ret = stream->ioctl(request, ap);
+    va_end(ap);
+    return ret;
+  }
+
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    DeviceHandler::AddDeviceId("/dev/ashmem", kNullMajorId, kNullMinorId);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(DevAshmemTest);
+};
+
+TEST_F(DevAshmemTest, TestInit) {
+}
+
+TEST_F(DevAshmemTest, TestStat) {
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/dev/ashmem", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+}
+
+TEST_F(DevAshmemTest, TestOpenClose) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+}
+
+TEST_F(DevAshmemTest, TestOpenFail) {
+  errno = 0;
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY | O_DIRECTORY, 0);
+  EXPECT_TRUE(stream == NULL);
+  EXPECT_EQ(ENOTDIR, errno);
+}
+
+TEST_F(DevAshmemTest, TestFstat) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st = {};
+  stream->fstat(&st);
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kNullMajorId, kNullMinorId), st.st_rdev);
+}
+
+TEST_F(DevAshmemTest, TestIoctl) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  EXPECT_EQ(0, CallIoctl(stream, ASHMEM_SET_SIZE, 123));
+  EXPECT_EQ(123, CallIoctl(stream, ASHMEM_GET_SIZE, 123));
+
+  EXPECT_EQ(0, CallIoctl(stream, ASHMEM_SET_NAME, "123"));
+  char buf[ASHMEM_NAME_LEN];
+  ASSERT_EQ(0, CallIoctl(stream, ASHMEM_GET_NAME, buf));
+  EXPECT_STREQ("123", buf);
+
+  // Confirm that ASHMEM_SET_SIZE/NAME fails once the device is mapped.
+  void* mapped = stream->mmap(NULL, 0x10000, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  EXPECT_EQ(0, stream->munmap(mapped, 0x10000));
+  errno = 0;
+  EXPECT_EQ(-1, CallIoctl(stream, ASHMEM_SET_SIZE, 1234));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, CallIoctl(stream, ASHMEM_SET_NAME, "willfail"));
+  EXPECT_EQ(EINVAL, errno);
+
+  int dummy_prot = 0;
+  EXPECT_EQ(0, CallIoctl(stream, ASHMEM_SET_PROT_MASK, dummy_prot));
+  EXPECT_EQ(ASHMEM_NOT_PURGED, CallIoctl(stream, ASHMEM_PIN));
+  EXPECT_EQ(ASHMEM_IS_UNPINNED, CallIoctl(stream, ASHMEM_UNPIN));
+}
+
+TEST_F(DevAshmemTest, TestLseek) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  errno = 0;
+  // When the size is not set, it should return EINVAL.
+  EXPECT_EQ(-1, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(EINVAL, errno);
+  CallIoctl(stream, ASHMEM_SET_SIZE, 1);
+
+  // After setting the size, read() returns EBADF if the device is not yet
+  // mapped to the memory.
+  EXPECT_EQ(-1, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(EBADF, errno);
+
+  // Once it is mapped (even if the mapping is private), lseek succeeds.
+  void* mapped = stream->mmap(NULL, 0x10000, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  EXPECT_EQ(0, stream->munmap(mapped, 0x10000));
+  EXPECT_EQ(123456, stream->lseek(123456, SEEK_SET));
+}
+
+// Confirm read() behavior with and without mmap().
+TEST_F(DevAshmemTest, TestRead) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  // read() should return EOF if ASHMEM_SET_SIZE has not been called.
+  char c;
+  EXPECT_EQ(0, stream->read(&c, 1));  // EOF
+  CallIoctl(stream, ASHMEM_SET_SIZE, 1);
+
+  // After setting the size, read() returns EBADF if the device is not yet
+  // mapped to the memory.
+  errno = 0;
+  EXPECT_EQ(-1, stream->read(&c, 1));
+  EXPECT_EQ(EBADF, errno);
+
+  // Once it is mapped (even if the mapping is private), read succeeds.
+  void* mapped = stream->mmap(NULL, 0x10000, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  EXPECT_EQ(0, stream->munmap(mapped, 0x10000));
+  c = 42;
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ(0, c);
+}
+
+TEST_F(DevAshmemTest, TestWrite) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+  // Write to the device should always fail.
+  errno = 0;
+  char c = 1;
+  EXPECT_EQ(-1, stream->write(&c, 1));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DevAshmemTest, TestAshmemMapSizeUnknown) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  // mmap fails with EINVAL is ASHMEM_SET_SIZE has not been called yet.
+  errno = 0;
+  void* mapped =
+      stream->mmap(NULL, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  EXPECT_EQ(MAP_FAILED, mapped);
+  EXPECT_EQ(EINVAL, errno);
+}
+
+// Test the usual mmap then fully munmap case.
+TEST_F(DevAshmemTest, TestAshmemMapUnmap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  int result = CallIoctl(stream, ASHMEM_SET_NAME, "arc-ashmem-abc");
+  EXPECT_EQ(0, result);
+  const size_t size = 0x200000;  // 2MB
+  result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  // Touch the memory
+  static_cast<uint8_t*>(mapped)[0] = 0x1;
+  static_cast<uint8_t*>(mapped)[size - 1] = 0x1;
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Confirm that read() still succeeds even after full munmap.
+// Both Binder and CTS require this.
+TEST_F(DevAshmemTest, TestAshmemReadAfterUnmap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x10000;  // 64kb
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  // Touch the memory, then full-unmap.
+  static_cast<uint8_t*>(mapped)[0] = 0x1;
+  static_cast<uint8_t*>(mapped)[size - 1] = 0x1;
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+
+  // Confirm that read still succeeds.
+  char buf[0x10000] = {};
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(buf)),
+            stream->read(buf, sizeof(buf) * 2));
+  EXPECT_EQ(0x1, buf[0]);
+  EXPECT_EQ(0x0, buf[1]);
+  EXPECT_EQ(0x0, buf[size - 2]);
+  EXPECT_EQ(0x1, buf[size - 1]);
+  EXPECT_EQ(0, stream->read(buf, 1));
+}
+
+// Confirm that read() fails after partial unmap. This is just a
+// limitation of the current DevAshmem implementation. The real
+// /dev/ashmem in the kernel does not have the limitation.
+TEST_F(DevAshmemTest, TestAshmemReadAfterPartialUnmap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128kb
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  EXPECT_EQ(0, stream->munmap(mapped, size / 2));  // partial unmap
+
+  // Confirm that read fails.
+  char buf[0x10000] = {};
+  errno = 0;
+  EXPECT_EQ(-1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(EBADF, errno);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(static_cast<uint8_t*>(mapped) + (size / 2),
+                              size / 2));
+}
+
+// Test mmap-write-munmap-mmap-read case.
+TEST_F(DevAshmemTest, TestAshmemMapUnmapMap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x10000;  // 64kb
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+
+  void* mapped = stream->mmap(NULL, size, PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  // Touch the memory, then full-unmap.
+  static_cast<uint8_t*>(mapped)[0] = 0x1;
+  static_cast<uint8_t*>(mapped)[size - 1] = 0x1;
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+
+  mapped = stream->mmap(NULL, size, PROT_READ, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  // Confirm that the 0x1 writes are still visible.
+  EXPECT_EQ(0x1, static_cast<uint8_t*>(mapped)[0]);
+  EXPECT_EQ(0x1, static_cast<uint8_t*>(mapped)[size - 1]);
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Confirm mmap with two partial munmap calls works.
+TEST_F(DevAshmemTest, TestAshmemMapUnmapTwice) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  int result = CallIoctl(stream, ASHMEM_SET_NAME, "arc-ashmem");
+  EXPECT_EQ(0, result);
+  const size_t size = 0x200000;  // 2MB
+  result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  // Touch the memory
+  static_cast<uint8_t*>(mapped)[0] = 0x1;
+  static_cast<uint8_t*>(mapped)[size - 1] = 0x1;
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size / 2));
+  // Touch the memory again to confirm that the last 1MB region has not been
+  // unmapped yet
+  static_cast<uint8_t*>(mapped)[size - 1] = 0x1;
+  EXPECT_EQ(0, stream->munmap(
+      static_cast<uint8_t*>(mapped) + (size / 2), size / 2));
+}
+
+// Confirm that two memory regions returned from two mmap(MAP_SHARED) calls
+// actually point to the same physical memory.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapTwice) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  int result = CallIoctl(stream, ASHMEM_SET_NAME, "arc-ashmem");
+  EXPECT_EQ(0, result);
+  const size_t size = 0x200000;  // 2MB
+  result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  static_cast<uint8_t*>(mapped)[0] = 0x1;  // write to |mapped|
+
+  // Call mmap again.
+  void* mapped2 =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped2);
+
+  // Two mmap calls must not return the same address.
+  // Note: Currently this test fails because stream->mmap() returns the same
+  //       address twice which is not POSIX compatible.
+  // EXPECT_NE(mapped, mapped2);
+
+  // Two memory regions should share the content.
+  EXPECT_EQ(0x1U, static_cast<uint8_t*>(mapped2)[0]);  // read from mapped2
+  static_cast<uint8_t*>(mapped2)[1] = 0xf;  // write to mapped2
+  EXPECT_EQ(0xfU, static_cast<uint8_t*>(mapped)[1]);  // read from mapped
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped2, size));
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Confirm that two memory regions returned from two mmap(MAP_PRIVATE) calls
+// do NOT point to the same physical memory.
+TEST_F(DevAshmemTest, TestAshmemPrivateMmapTwice) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x10000;  // 64k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  static_cast<uint8_t*>(mapped)[0] = 0x1;  // write to |mapped|
+
+  // Call mmap again.
+  void* mapped2 =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped2);
+
+  EXPECT_NE(mapped, mapped2);
+
+  // Two memory regions should NOT share the content.
+  EXPECT_EQ(0x0U, static_cast<uint8_t*>(mapped2)[0]);  // read from mapped2
+  static_cast<uint8_t*>(mapped2)[1] = 0xf;  // write to mapped2
+  EXPECT_EQ(0x0U, static_cast<uint8_t*>(mapped)[1]);  // read from mapped
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped2, size));
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Test the case where mmap(MAP_SHARED) and mmap(MAP_PRIVATE) are mixed.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapAndPrivateMmapMixed) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x10000;  // 64k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  static_cast<uint8_t*>(mapped)[0] = 0x1;  // write to |mapped|
+
+  // Call mmap again. The 0x1 write should NOT be visible to the private
+  // mapping to emulate the kernel.
+  void* mapped2 =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped2);
+
+  EXPECT_NE(mapped, mapped2);
+
+  // Two memory regions should NOT share the content.
+  EXPECT_EQ(0x0U, static_cast<uint8_t*>(mapped2)[0]);  // read from mapped2
+  static_cast<uint8_t*>(mapped2)[1] = 0xf;  // write to mapped2
+  EXPECT_EQ(0x0U, static_cast<uint8_t*>(mapped)[1]);  // read from mapped
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped2, size));
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Confirm that mmap(MAP_PRIVATE) with a page-aligned offset succeeds.
+TEST_F(DevAshmemTest, TestAshmemSPrivateMmapWithOffset) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, size / 2);
+  ASSERT_NE(MAP_FAILED, mapped);
+  static_cast<uint8_t*>(mapped)[0] = 0x1;  // write to |mapped|
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size / 2));
+}
+
+// Confirm that mmap(MAP_SHARED) with a page-aligned offset fails. This is just
+// a limitation of the current DevAshmem implementation. The real /dev/ashmem
+// in the kernel does not have the limitation.
+TEST_F(DevAshmemTest, TestAshmemSSharedMmapWithOffset) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, size / 2);
+  EXPECT_EQ(MAP_FAILED, mapped);
+}
+
+// Call mmap(MAP_PRIVATE) twice with different sizes.
+TEST_F(DevAshmemTest, TestAshmemPrivateMmapTwiceDifferentSizes) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+
+  // Call mmap again.
+  void* mapped2 =
+      stream->mmap(NULL, size / 2, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, mapped2);
+
+  EXPECT_NE(mapped, mapped2);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped2, size / 2));
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Call mmap(MAP_SHARED) twice with different sizes. This fails, but this is
+// just a limitation of the current DevAshmem implementation. The real
+// /dev/ashmem in the kernel does not have the limitation.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapTwiceDifferentSizes) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+
+  // Call mmap again.
+  void* mapped2 =
+      stream->mmap(NULL, size / 2, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  EXPECT_EQ(MAP_FAILED, mapped2);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Call mmap(MAP_SHARED) twice with and without MAP_FIXED.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapTwiceWithAndWithoutMapFixed) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+
+  // Call mmap again with MAP_FIXED and with the same address, |mapped|.
+  void* mapped2 = stream->mmap(
+      mapped, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, 0);
+  ASSERT_NE(MAP_FAILED, mapped2);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Call mmap(MAP_SHARED) twice with and without MAP_FIXED. This time,
+// use a different address with MAP_FIXED. This fails, but this is
+// just a limitation of the current DevAshmem implementation. The real
+// /dev/ashmem in the kernel does not have the limitation.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapTwiceWithAndWithoutMapFixed2) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+
+  // Call mmap again with MAP_FIXED and with the same address, |mapped|.
+  void* mapped2 = stream->mmap(
+      static_cast<uint8_t*>(mapped) + 0x10000,
+      size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, 0);
+  ASSERT_EQ(MAP_FAILED, mapped2);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(mapped, size));
+}
+
+// Call mmap(MAP_SHARED) after partial munmap. This fails, but this is
+// just a limitation of the current DevAshmem implementation. The real
+// /dev/ashmem in the kernel does not have the limitation.
+TEST_F(DevAshmemTest, TestAshmemSharedMmapAfterPartialMunmap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/ashmem", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+
+  const size_t size = 0x20000;  // 128k
+  int result = CallIoctl(stream, ASHMEM_SET_SIZE, size);
+  EXPECT_EQ(0, result);
+  void* mapped =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_NE(MAP_FAILED, mapped);
+  EXPECT_EQ(0, stream->munmap(mapped, size / 2));
+
+  // Call mmap again.
+  void* mapped2 =
+      stream->mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+  ASSERT_EQ(MAP_FAILED, mapped2);
+
+  // Clean up.
+  EXPECT_EQ(0, stream->munmap(static_cast<uint8_t*>(mapped) + (size / 2),
+                              size / 2));
+}
+
+// Other MAP_FIXED cases are tested in
+// src/integration_tests/posix_translation_ndk/jni/mmap_tests.cc.
+// because we need to test the interaction between memory_region.cc and
+// FileStreams too.
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_logger.cc b/src/posix_translation/dev_logger.cc
new file mode 100644
index 0000000..16b4314
--- /dev/null
+++ b/src/posix_translation/dev_logger.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/dev_logger.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "common/logger.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+using arc::Logger;
+
+bool GetLogIdFromPath(const std::string& pathname, arc_log_id_t* log_id) {
+  if (pathname == "/dev/log/events") {
+    *log_id = ARC_LOG_ID_EVENTS;
+    return true;
+  }
+  if (pathname == "/dev/log/main") {
+    *log_id = ARC_LOG_ID_MAIN;
+    return true;
+  }
+  if (pathname == "/dev/log/radio") {
+    *log_id = ARC_LOG_ID_RADIO;
+    return true;
+  }
+  if (pathname == "/dev/log/system") {
+    *log_id = ARC_LOG_ID_SYSTEM;
+    return true;
+  }
+  return false;
+}
+
+int DoStatLocked(const std::string& pathname, struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = S_IFCHR | 0666;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  // st_uid, st_gid, st_size, st_blocks should be zero.
+
+  // TODO(crbug.com/242337): Fill st_dev if needed.
+  out->st_rdev = DeviceHandler::GetDeviceId(pathname);
+  return 0;
+}
+
+
+class DevLogger : public DeviceStream {
+ public:
+  DevLogger(const std::string& pathname, int oflag, arc_log_id_t log_id);
+
+  bool is_block() const { return !(oflag() & O_NONBLOCK); }
+
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+  virtual int fcntl(int cmd, va_list ap) OVERRIDE;
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+  virtual bool IsSelectReadReady() const OVERRIDE;
+  virtual int16_t GetPollEvents() const OVERRIDE;
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~DevLogger();
+
+ private:
+  friend class DevLoggerDevice;
+
+  static void ReadReady();
+
+  arc::LoggerReader* reader_;
+  int version_;
+
+  DISALLOW_COPY_AND_ASSIGN(DevLogger);
+};
+
+DevLogger::DevLogger(
+    const std::string& pathname, int oflag, arc_log_id_t log_id)
+    : DeviceStream(oflag, pathname),
+      reader_(Logger::GetInstance()->CreateReader(log_id)),
+      version_(1) {
+}
+
+DevLogger::~DevLogger() {
+  Logger::GetInstance()->ReleaseReader(reader_);
+  // Wake up the reading thread.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->Broadcast();
+}
+
+int DevLogger::ioctl(int request, va_list ap) {
+  int ret = 0;
+
+  switch (request) {
+    case LOGGER_GET_LOG_BUF_SIZE:
+      ret = Logger::GetInstance()->GetBufferSize(reader_);
+      break;
+    case LOGGER_GET_LOG_LEN:
+      ret = Logger::GetInstance()->GetLogLength(reader_);
+      break;
+    case LOGGER_GET_NEXT_ENTRY_LEN:
+      ret = Logger::GetInstance()->GetNextEntryLength(reader_);
+      break;
+    case LOGGER_FLUSH_LOG:
+      Logger::GetInstance()->FlushBuffer(reader_);
+      ret = 0;
+      break;
+    case LOGGER_GET_VERSION:
+      ret = version_;
+      break;
+    case LOGGER_SET_VERSION: {
+      int* out = va_arg(ap, int*);
+      if (*out != 1 && *out != 2) {
+        errno = EINVAL;
+        ret =  -1;
+      } else {
+        version_  = *out;
+      }
+      break;
+    }
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+  return ret;
+}
+
+int DevLogger::fcntl(int cmd, va_list ap) {
+  // TODO(penghuang): Setting O_NONBLOCK via fcntl is a no-op.
+  return FileStream::fcntl(cmd, ap);
+}
+
+int DevLogger::fstat(struct stat* out) {
+  return DoStatLocked(pathname(), out);
+}
+
+ssize_t DevLogger::read(void* buf, size_t count) {
+  ssize_t result;
+  if (!is_block()) {
+    result = Logger::GetInstance()->ReadLogEntry(reader_,
+        static_cast<struct logger_entry*>(buf), count);
+    if (result < 0) {
+      errno = -result;
+      return -1;
+    }
+    return result;
+  } else {
+    result = Logger::GetInstance()->ReadLogEntry(reader_,
+        static_cast<struct logger_entry*>(buf), count);
+    if (result == -EAGAIN) {
+      Logger::GetInstance()->WaitForReadReady(reader_, &DevLogger::ReadReady);
+      VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+      while (result == -EAGAIN) {
+        sys->Wait();
+        result = Logger::GetInstance()->ReadLogEntry(reader_,
+            static_cast<struct logger_entry*>(buf), count);
+      }
+    }
+    if (result >= 0)
+      return result;
+
+    errno = -result;
+    return -1;
+  }
+}
+
+ssize_t DevLogger::write(const void* buf, size_t count) {
+  errno = EPERM;
+  return -1;
+}
+
+const char* DevLogger::GetStreamType() const {
+  return "dev_logger";
+}
+
+bool DevLogger::IsSelectReadReady() const {
+  return Logger::GetInstance()->IsReadReady(reader_);
+}
+
+int16_t DevLogger::GetPollEvents() const {
+  return (IsSelectReadReady() ? POLLIN : 0) | POLLOUT;
+}
+
+void DevLogger::ReadReady() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->Broadcast();
+}
+
+}  // namespace
+
+DevLoggerHandler::DevLoggerHandler()
+    : DeviceHandler("DevLoggerHandler") {
+}
+
+DevLoggerHandler::~DevLoggerHandler() {
+}
+
+scoped_refptr<FileStream> DevLoggerHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  arc_log_id_t log_id;
+  if (!GetLogIdFromPath(pathname, &log_id)) {
+    errno = ENOENT;
+    return NULL;
+  }
+  return new DevLogger(pathname, oflag, log_id);
+}
+
+int DevLoggerHandler::stat(const std::string& pathname, struct stat* out) {
+  arc_log_id_t log_id;
+  if (!GetLogIdFromPath(pathname, &log_id)) {
+    errno = ENOENT;
+    return -1;
+  }
+  return DoStatLocked(pathname, out);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_logger.h b/src/posix_translation/dev_logger.h
new file mode 100644
index 0000000..4a0d2d4
--- /dev/null
+++ b/src/posix_translation/dev_logger.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_DEV_LOGGER_H_
+#define POSIX_TRANSLATION_DEV_LOGGER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/device_file.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+class ARC_EXPORT DevLoggerHandler : public DeviceHandler {
+ public:
+  DevLoggerHandler();
+  virtual ~DevLoggerHandler();
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevLoggerHandler);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DEV_LOGGER_H_
diff --git a/src/posix_translation/dev_logger_test.cc b/src/posix_translation/dev_logger_test.cc
new file mode 100644
index 0000000..b054ce9
--- /dev/null
+++ b/src/posix_translation/dev_logger_test.cc
@@ -0,0 +1,330 @@
+// Copyright 2014 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 <sys/sysmacros.h>
+
+#include "base/compiler_specific.h"
+#include "base/synchronization/lock.h"
+#include "common/logger.h"  // LOGGER_FLUSH_LOG etc.
+#include "gtest/gtest.h"
+#include "posix_translation/dev_logger.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+
+namespace posix_translation {
+
+// We use random numbers for this test.
+const int kLoggerMajorId = 42;
+const int kEventsMinorId = 43;
+const int kMainMinorId = 44;
+const int kRadioMinorId = 45;
+const int kSystemMinorId = 46;
+
+class DevLoggerTest : public FileSystemBackgroundTestCommon<DevLoggerTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(TestBlockingModeWithCondWait);
+
+  DevLoggerTest() {}
+  virtual ~DevLoggerTest() {}
+
+ protected:
+  virtual void SetUp() OVERRIDE;
+  int CallIoctl(scoped_refptr<FileStream> stream, int request, ...);
+
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevLoggerTest);
+};
+
+void DevLoggerTest::SetUp() {
+  FileSystemBackgroundTestCommon<DevLoggerTest>::SetUp();
+  handler_.reset(new DevLoggerHandler);
+  handler_->Initialize();
+  ASSERT_TRUE(handler_->IsInitialized());
+
+  DeviceHandler::AddDeviceId(
+      "/dev/log/events", kLoggerMajorId, kEventsMinorId);
+  DeviceHandler::AddDeviceId(
+      "/dev/log/main", kLoggerMajorId, kMainMinorId);
+  DeviceHandler::AddDeviceId(
+      "/dev/log/radio", kLoggerMajorId, kRadioMinorId);
+  DeviceHandler::AddDeviceId(
+      "/dev/log/system", kLoggerMajorId, kSystemMinorId);
+}
+
+int DevLoggerTest::CallIoctl(
+    scoped_refptr<FileStream> stream, int request, ...) {
+  va_list ap;
+  va_start(ap, request);
+  const int ret = stream->ioctl(request, ap);
+  va_end(ap);
+  return ret;
+}
+
+TEST_F(DevLoggerTest, TestInit) {
+}
+
+TEST_F(DevLoggerTest, TestOnDirectoryContentsNeeded) {
+  base::AutoLock lock(mutex());
+  // readdir/getdents are not supported.
+  EXPECT_EQ(NULL, handler_->OnDirectoryContentsNeeded("/"));
+}
+
+TEST_F(DevLoggerTest, TestStat) {
+  base::AutoLock lock(mutex());
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/dev/log/events", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kLoggerMajorId, kEventsMinorId), st.st_rdev);
+
+  EXPECT_EQ(-1, handler_->stat("/dev/log/unknown_path", &st));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(DevLoggerTest, TestStatfs) {
+  base::AutoLock lock(mutex());
+  struct statfs st = {};
+  EXPECT_EQ(0, handler_->statfs("/dev/log/main", &st));
+  EXPECT_NE(0, static_cast<int>(st.f_type));  // check something is filled.
+}
+
+TEST_F(DevLoggerTest, TestOpen) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  // Confirm that we can open the following paths.
+  stream = handler_->open(-1 /* fd */, "/dev/log/events", O_RDONLY, 0);
+  EXPECT_TRUE(stream);
+  stream = handler_->open(-1, "/dev/log/main", O_RDONLY, 0);
+  EXPECT_TRUE(stream);
+  stream = handler_->open(-1, "/dev/log/radio", O_RDONLY, 0);
+  EXPECT_TRUE(stream);
+  stream = handler_->open(-1, "/dev/log/system", O_RDONLY, 0);
+  EXPECT_TRUE(stream);
+
+  // Try to open it as a directory, which should fail.
+  errno = 0;
+  stream =
+    handler_->open(-1, "/dev/log/events", O_RDONLY | O_DIRECTORY, 0);
+  EXPECT_EQ(ENOTDIR, errno);
+  EXPECT_FALSE(stream);
+
+  // Try to open with bad paths.
+  errno = 0;
+  stream = handler_->open(-1, "/dev/log/event", O_RDONLY, 0);
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_FALSE(stream);
+  errno = 0;
+  stream = handler_->open(-1, "/dev/log/systems", O_RDONLY, 0);
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_FALSE(stream);
+  errno = 0;
+  stream = handler_->open(-1, "/dev/log/unknown_path", O_RDONLY, 0);
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_FALSE(stream);
+}
+
+TEST_F(DevLoggerTest, TestIoctl) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  stream = handler_->open(-1, "/dev/log/main", O_RDONLY | O_NONBLOCK, 0);
+  ASSERT_TRUE(stream);
+
+  // Flush should be the first one.
+  ASSERT_EQ(0, CallIoctl(stream, LOGGER_FLUSH_LOG));
+
+  EXPECT_LT(0, CallIoctl(stream, LOGGER_GET_LOG_BUF_SIZE));
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_GET_LOG_LEN));
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_GET_NEXT_ENTRY_LEN));
+  EXPECT_EQ(1, CallIoctl(stream, LOGGER_GET_VERSION));
+  int new_version = 2;
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_SET_VERSION, &new_version));
+  EXPECT_EQ(2, CallIoctl(stream, LOGGER_GET_VERSION));
+  new_version = 1;
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_SET_VERSION, &new_version));
+  EXPECT_EQ(1, CallIoctl(stream, LOGGER_GET_VERSION));
+
+  // Try unsupported ioctl.
+  int dummy = 0;
+  EXPECT_EQ(-1, CallIoctl(stream, FIONREAD, &dummy));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DevLoggerTest, TestNonBlockingMode) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  stream = handler_->open(-1, "/dev/log/events", O_RDONLY | O_NONBLOCK, 0);
+  ASSERT_TRUE(stream);
+
+  // Flush the log and then test IsSelectReadReady().
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_FLUSH_LOG));
+  EXPECT_FALSE(stream->IsSelectReadReady());
+
+  // Call fstat().
+  struct stat st = {};
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kLoggerMajorId, kEventsMinorId), st.st_rdev);
+
+  // Call read(). Confirm it will not block.
+  errno = 0;
+  char buf[128] = {};
+  ssize_t result = stream->read(buf, sizeof(buf));
+  EXPECT_EQ(EWOULDBLOCK, errno);
+  EXPECT_EQ(-1, result);
+
+  // Write something to the log.
+  const char kTag[] = "Test";
+  const char kMsg[] = "Test log1";
+  arc::Logger* logger = arc::Logger::GetInstance();
+  logger->Log(ARC_LOG_ID_EVENTS, ARC_LOG_DEBUG, kTag, kMsg);
+  const ssize_t kPayloadSize = 1 + sizeof(kTag) + sizeof(kMsg);
+  const ssize_t kEntrySize = kPayloadSize + sizeof(struct logger_entry);
+  EXPECT_EQ(kEntrySize, CallIoctl(stream, LOGGER_GET_LOG_LEN));
+  EXPECT_EQ(kEntrySize, CallIoctl(stream, LOGGER_GET_NEXT_ENTRY_LEN));
+
+  // Call read().
+  EXPECT_TRUE(stream->IsSelectReadReady());
+  errno = 0;
+  result = stream->read(buf, sizeof(buf));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kEntrySize, result);
+
+  // Call write() which should always fail.
+  result = stream->write(buf, 1);
+  EXPECT_EQ(EPERM, errno);
+  EXPECT_EQ(-1, result);
+}
+
+TEST_F(DevLoggerTest, TestBlockingMode) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  stream = handler_->open(-1, "/dev/log/system", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Flush the log and then test IsSelectReadReady().
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_FLUSH_LOG));
+  EXPECT_FALSE(stream->IsSelectReadReady());
+
+  // Call fstat().
+  struct stat st = {};
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+
+  // Write something to the log.
+  const char kTag[] = "Test";
+  const char kMsg[] = "Test log2";
+  arc::Logger* logger = arc::Logger::GetInstance();
+  logger->Log(ARC_LOG_ID_SYSTEM, ARC_LOG_DEBUG, kTag, kMsg);
+  const ssize_t kPayloadSize = 1 + sizeof(kTag) + sizeof(kMsg);
+  const ssize_t kEntrySize = kPayloadSize + sizeof(struct logger_entry);
+  EXPECT_EQ(kEntrySize, CallIoctl(stream, LOGGER_GET_LOG_LEN));
+  EXPECT_EQ(kEntrySize, CallIoctl(stream, LOGGER_GET_NEXT_ENTRY_LEN));
+
+  // Call read().
+  ASSERT_TRUE(stream->IsSelectReadReady());
+  errno = 0;
+  char buf[128] = {};
+  ssize_t result = stream->read(buf, sizeof(buf));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kEntrySize, result);
+
+  // Call write() which should always fail.
+  result = stream->write(buf, 1);
+  EXPECT_EQ(EPERM, errno);
+  EXPECT_EQ(-1, result);
+
+  // Cleaning things up.
+  CallIoctl(stream, LOGGER_FLUSH_LOG);
+}
+
+TEST_F(DevLoggerTest, TestReadWithTinyBuf) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  stream = handler_->open(-1, "/dev/log/radio", O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_FLUSH_LOG));
+
+  // Write something to the log.
+  const char kTag[] = "Test";
+  const char kMsg[] = "Test log3";
+  arc::Logger* logger = arc::Logger::GetInstance();
+  logger->Log(ARC_LOG_ID_RADIO, ARC_LOG_DEBUG, kTag, kMsg);
+  const ssize_t kPayloadSize = 1 + sizeof(kTag) + sizeof(kMsg);
+  const ssize_t kEntrySize = kPayloadSize + sizeof(struct logger_entry);
+
+  // Call read() with 1 byte buffer. This should fail.
+  char c;
+  errno = 0;
+  ssize_t result = stream->read(&c, 1);
+  EXPECT_EQ(EINVAL, errno);
+  EXPECT_EQ(-1, result);
+
+  // Call read() with a tight buffer.
+  char buf[kEntrySize] = {};
+  errno = 0;
+  result = stream->read(buf, sizeof(buf));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kEntrySize, result);
+
+  // Cleaning things up.
+  CallIoctl(stream, LOGGER_FLUSH_LOG);
+}
+
+namespace {
+
+const char kTag[] = "Test";
+const char kMsg[] = "Test log4";
+
+void* Signal(void* data) {
+  // Lock the mutex to ensure that stream->read() is called first.
+  base::AutoLock lock(*static_cast<base::Lock*>(data));
+
+  // Write something to the log to wake up the background thread.
+  arc::Logger* logger = arc::Logger::GetInstance();
+  logger->Log(ARC_LOG_ID_SYSTEM, ARC_LOG_DEBUG, kTag, kMsg);
+
+  return NULL;
+}
+
+}  // namespace
+
+TEST_BACKGROUND_F(DevLoggerTest, TestBlockingModeWithCondWait) {
+  base::AutoLock lock(mutex());
+  scoped_refptr<FileStream> stream;
+
+  stream = handler_->open(-1, "/dev/log/system", O_RDONLY, 0);  // blocking
+  ASSERT_TRUE(stream);
+
+  // Flush the log and then test IsSelectReadReady().
+  EXPECT_EQ(0, CallIoctl(stream, LOGGER_FLUSH_LOG));
+  EXPECT_FALSE(stream->IsSelectReadReady());
+
+  // Start another thread.
+  pthread_t p;
+  pthread_create(&p, NULL, Signal, &mutex());
+
+  // Call read(). The thread will suspend until the Signal() thread calls
+  // Log().
+  const ssize_t kPayloadSize = 1 + sizeof(kTag) + sizeof(kMsg);
+  const ssize_t kEntrySize = kPayloadSize + sizeof(struct logger_entry);
+  errno = 0;
+  char buf[128] = {};
+  ssize_t result = stream->read(buf, sizeof(buf));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kEntrySize, result);
+
+  // Cleaning things up.
+  pthread_join(p, NULL);
+  CallIoctl(stream, LOGGER_FLUSH_LOG);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_null.cc b/src/posix_translation/dev_null.cc
new file mode 100644
index 0000000..b9e425e
--- /dev/null
+++ b/src/posix_translation/dev_null.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/dev_null.h"
+
+#include <string.h>
+#include <sys/vfs.h>
+
+#include "posix_translation/dir.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+int DoStatLocked(const std::string& pathname, mode_t mode, struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = mode;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  // st_uid, st_gid, st_size, st_blocks should be zero.
+
+  // TODO(crbug.com/242337): Fill st_dev if needed.
+  out->st_rdev = DeviceHandler::GetDeviceId(pathname);
+  return 0;
+}
+
+}  // namespace
+
+DevNullHandler::DevNullHandler()
+    : DeviceHandler("DevNullHandler"), mode_(S_IFCHR | 0666) {
+}
+
+DevNullHandler::DevNullHandler(mode_t mode)
+    : DeviceHandler("DevNullHandler"), mode_(mode) {
+}
+
+DevNullHandler::~DevNullHandler() {
+}
+
+scoped_refptr<FileStream> DevNullHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  return new DevNull(pathname, mode_, oflag);
+}
+
+int DevNullHandler::stat(const std::string& pathname, struct stat* out) {
+  return DoStatLocked(pathname, mode_, out);
+}
+
+//------------------------------------------------------------------------------
+
+DevNull::DevNull(const std::string& pathname, mode_t mode, int oflag)
+    : DeviceStream(oflag, pathname), mode_(mode) {
+}
+
+DevNull::~DevNull() {
+}
+
+int DevNull::fstat(struct stat* out) {
+  return DoStatLocked(pathname(), mode_, out);
+}
+
+void* DevNull::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  if ((flags & MAP_TYPE) == MAP_SHARED) {
+    errno = ENODEV;
+    return MAP_FAILED;
+  }
+  // See also: DevZero::mmap.
+  return ::mmap(addr, length, prot, flags | MAP_ANONYMOUS, -1, offset);
+}
+
+int DevNull::munmap(void* addr, size_t length) {
+  return ::munmap(addr, length);
+}
+
+ssize_t DevNull::read(void* buf, size_t count) {
+  return 0;
+}
+
+ssize_t DevNull::write(const void* buf, size_t count) {
+  return count;
+}
+
+const char* DevNull::GetStreamType() const { return "dev_null"; }
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_null.h b/src/posix_translation/dev_null.h
new file mode 100644
index 0000000..3a673f3
--- /dev/null
+++ b/src/posix_translation/dev_null.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_DEV_NULL_H_
+#define POSIX_TRANSLATION_DEV_NULL_H_
+
+#include <errno.h>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/device_file.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+class ARC_EXPORT DevNullHandler : public DeviceHandler {
+ public:
+  DevNullHandler();
+  explicit DevNullHandler(mode_t mode);
+  virtual ~DevNullHandler();
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+
+ private:
+  const mode_t mode_;
+
+  DISALLOW_COPY_AND_ASSIGN(DevNullHandler);
+};
+
+class DevNull : public DeviceStream {
+ public:
+  DevNull(const std::string& pathname, mode_t mode, int oflag);
+
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ private:
+  virtual ~DevNull();
+
+ private:
+  const mode_t mode_;
+
+  DISALLOW_COPY_AND_ASSIGN(DevNull);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DEV_NULL_H_
diff --git a/src/posix_translation/dev_null_test.cc b/src/posix_translation/dev_null_test.cc
new file mode 100644
index 0000000..c5893e7
--- /dev/null
+++ b/src/posix_translation/dev_null_test.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2013 The Chromium OS 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 <sys/sysmacros.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dev_null.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+// We use random numbers for this test.
+const int kNullMajorId = 42;
+const int kNullMinorId = 43;
+const int kFooBarMajorId = 44;
+const int kFooBarMinorId = 45;
+
+namespace posix_translation {
+
+class DevNullTest : public FileSystemTestCommon {
+ protected:
+  DevNullTest()
+      : handler_(new DevNullHandler),
+        handler_with_mode_(new DevNullHandler(S_IFREG | 0644)) {
+  }
+
+  scoped_ptr<FileSystemHandler> handler_;
+  scoped_ptr<FileSystemHandler> handler_with_mode_;
+
+ private:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    DeviceHandler::AddDeviceId("/dev/null", kNullMajorId, kNullMinorId);
+    DeviceHandler::AddDeviceId("/foo/bar", kFooBarMajorId, kFooBarMinorId);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(DevNullTest);
+};
+
+TEST_F(DevNullTest, TestInit) {
+}
+
+TEST_F(DevNullTest, TestMkdir) {
+  EXPECT_EQ(-1, handler_->mkdir("/dev/null", 0700));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(DevNullTest, TestRename) {
+  EXPECT_EQ(-1, handler_->rename("/dev/null", "/dev/foo"));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(0, handler_->rename("/dev/null", "/dev/null"));
+}
+
+TEST_F(DevNullTest, TestStat) {
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/dev/null", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  // Tests the constructor with a mode_t parameter.
+  EXPECT_EQ(0, handler_with_mode_->stat("/foo/bar", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFREG | 0644U, st.st_mode);
+  EXPECT_EQ(makedev(kFooBarMajorId, kFooBarMinorId), st.st_rdev);
+}
+
+TEST_F(DevNullTest, TestStatfs) {
+  struct statfs st = {};
+  EXPECT_EQ(0, handler_->statfs("/dev/null", &st));
+  EXPECT_NE(0U, st.f_type);  // check something is filled.
+}
+
+TEST_F(DevNullTest, TestTruncate) {
+  EXPECT_EQ(-1, handler_->truncate("/dev/null", 0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DevNullTest, TestUnlink) {
+  EXPECT_EQ(-1, handler_->unlink("/dev/null"));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(DevNullTest, TestUtimes) {
+  struct timeval times[2] = {};
+  EXPECT_EQ(-1, handler_->utimes("/dev/null", times));
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_F(DevNullTest, TestOpenClose) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+}
+
+TEST_F(DevNullTest, TestFstat) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st = {};
+  stream->fstat(&st);
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kNullMajorId, kNullMinorId), st.st_rdev);
+  // Tests the constructor with a mode_t parameter.
+  stream = handler_with_mode_->open(512, "/foo/bar", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  stream->fstat(&st);
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFREG | 0644U, st.st_mode);
+}
+
+TEST_F(DevNullTest, TestMmapShared) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  EXPECT_EQ(MAP_FAILED, stream->mmap(NULL, 128, PROT_READ, MAP_SHARED, 0));
+  EXPECT_EQ(ENODEV, errno);
+}
+
+TEST_F(DevNullTest, TestMmapPrivate) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+  char* p = static_cast<char*>(
+      stream->mmap(NULL, 128, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0));
+  EXPECT_NE(MAP_FAILED, p);
+  EXPECT_EQ(0, p[0]);
+  p[1] = 1;
+  EXPECT_EQ(1, p[1]);
+  EXPECT_EQ(0, stream->munmap(p, 128));
+}
+
+TEST_F(DevNullTest, TestRead) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  char buf[16] = {};
+  EXPECT_EQ(0, stream->read(buf, sizeof(buf)));
+}
+
+TEST_F(DevNullTest, TestWrite) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/null", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  EXPECT_EQ(3, stream->write("abc", 3));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_urandom.cc b/src/posix_translation/dev_urandom.cc
new file mode 100644
index 0000000..7858cbe
--- /dev/null
+++ b/src/posix_translation/dev_urandom.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/dev_urandom.h"
+
+#include <string.h>
+
+#include "posix_translation/dir.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+int DoStatLocked(const std::string& pathname, struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = S_IFCHR | 0666;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  // st_uid, st_gid, st_size, st_blocks should be zero.
+
+  // TODO(crbug.com/242337): Fill st_dev if needed.
+  out->st_rdev = DeviceHandler::GetDeviceId(pathname);
+  return 0;
+}
+
+}  // namespace
+
+DevUrandomHandler::DevUrandomHandler()
+    : DeviceHandler("DevUrandomHandler") {
+}
+
+DevUrandomHandler::~DevUrandomHandler() {
+}
+
+scoped_refptr<FileStream> DevUrandomHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  return new DevUrandom(pathname, oflag);
+}
+
+int DevUrandomHandler::stat(const std::string& pathname, struct stat* out) {
+  return DoStatLocked(pathname, out);
+}
+
+//------------------------------------------------------------------------------
+
+DevUrandom::DevUrandom(const std::string& pathname, int oflag)
+    : DeviceStream(oflag, pathname) {
+  nacl_interface_query(NACL_IRT_RANDOM_v0_1, &random_, sizeof(random_));
+}
+
+DevUrandom::~DevUrandom() {
+}
+
+int DevUrandom::fstat(struct stat* out) {
+  return DoStatLocked(pathname(), out);
+}
+
+ssize_t DevUrandom::read(void* buf, size_t count) {
+  size_t nread = 0;
+  if (!GetRandomBytes(buf, count, &nread)) {
+    errno = EIO;
+    return -1;
+  }
+  return nread;
+}
+
+ssize_t DevUrandom::write(const void* buf, size_t count) {
+  errno = EPERM;
+  return -1;
+}
+
+bool DevUrandom::GetRandomBytes(void* buf, size_t count, size_t* nread) {
+  return random_.get_random_bytes(
+      reinterpret_cast<unsigned char*>(buf), count, nread) == 0;
+}
+
+const char* DevUrandom::GetStreamType() const {
+  return "dev_urandom";
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_urandom.h b/src/posix_translation/dev_urandom.h
new file mode 100644
index 0000000..2063e6e
--- /dev/null
+++ b/src/posix_translation/dev_urandom.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_DEV_URANDOM_H_
+#define POSIX_TRANSLATION_DEV_URANDOM_H_
+
+#include <errno.h>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "native_client/src/untrusted/irt/irt.h"
+#include "posix_translation/device_file.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+class ARC_EXPORT DevUrandomHandler : public DeviceHandler {
+ public:
+  DevUrandomHandler();
+  virtual ~DevUrandomHandler();
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevUrandomHandler);
+};
+
+class DevUrandom : public DeviceStream {
+ public:
+  DevUrandom(const std::string& pathname, int oflag);
+
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~DevUrandom();
+
+ private:
+  bool GetRandomBytes(void* buf, size_t count, size_t* nread);
+
+  nacl_irt_random random_;
+
+  DISALLOW_COPY_AND_ASSIGN(DevUrandom);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DEV_URANDOM_H_
diff --git a/src/posix_translation/dev_urandom_test.cc b/src/posix_translation/dev_urandom_test.cc
new file mode 100644
index 0000000..16d4141
--- /dev/null
+++ b/src/posix_translation/dev_urandom_test.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2013 The Chromium OS 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 "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dev_urandom.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+// We use random numbers for this test.
+const int kUrandomMajorId = 42;
+const int kUrandomMinorId = 43;
+
+class DevUrandomTest : public FileSystemTestCommon {
+ protected:
+  DevUrandomTest() : handler_(new DevUrandomHandler) {
+  }
+
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    DeviceHandler::AddDeviceId(
+        "/dev/urandom", kUrandomMajorId, kUrandomMinorId);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(DevUrandomTest);
+};
+
+TEST_F(DevUrandomTest, TestInit) {
+}
+
+TEST_F(DevUrandomTest, TestMkdir) {
+  EXPECT_EQ(-1, handler_->mkdir("/dev/urandom", 0700));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(DevUrandomTest, TestRename) {
+  EXPECT_EQ(-1, handler_->rename("/dev/urandom", "/dev/foo"));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(0, handler_->rename("/dev/urandom", "/dev/urandom"));
+}
+
+TEST_F(DevUrandomTest, TestStat) {
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/dev/urandom", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kUrandomMajorId, kUrandomMinorId), st.st_rdev);
+}
+
+TEST_F(DevUrandomTest, TestStatfs) {
+  struct statfs st = {};
+  EXPECT_EQ(0, handler_->statfs("/dev/urandom", &st));
+  EXPECT_NE(0U, st.f_type);  // check something is filled.
+}
+
+TEST_F(DevUrandomTest, TestTruncate) {
+  EXPECT_EQ(-1, handler_->truncate("/dev/urandom", 0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DevUrandomTest, TestUnlink) {
+  EXPECT_EQ(-1, handler_->unlink("/dev/urandom"));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(DevUrandomTest, TestUtimes) {
+  struct timeval times[2] = {};
+  EXPECT_EQ(-1, handler_->utimes("/dev/urandom", times));
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_F(DevUrandomTest, TestOpenClose) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/urandom", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+}
+
+TEST_F(DevUrandomTest, TestOpenDirectory) {
+  const scoped_refptr<FileStream> kStreamNull = NULL;
+  EXPECT_EQ(kStreamNull, handler_->open(512, "/dev/urandom", O_DIRECTORY, 0));
+  EXPECT_EQ(ENOTDIR, errno);
+}
+
+TEST_F(DevUrandomTest, TestFstat) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/urandom", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st = {};
+  stream->fstat(&st);
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kUrandomMajorId, kUrandomMinorId), st.st_rdev);
+}
+
+#if defined(__native_client__)
+TEST_F(DevUrandomTest, TestRead) {
+  static const char kZero[16] = {};
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/urandom", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  char buf[16] = {};
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(buf)), stream->read(buf, sizeof(buf)));
+  EXPECT_NE(0, memcmp(buf, kZero, sizeof(buf)));
+}
+#endif
+
+TEST_F(DevUrandomTest, TestWrite) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/urandom", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  EXPECT_EQ(-1, stream->write("abc", 3));
+  EXPECT_EQ(EPERM, errno);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_zero.cc b/src/posix_translation/dev_zero.cc
new file mode 100644
index 0000000..54496fc
--- /dev/null
+++ b/src/posix_translation/dev_zero.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2014 The Chromium OS 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 "posix_translation/dev_zero.h"
+
+#include <string.h>
+#include <sys/vfs.h>
+
+#include "posix_translation/dir.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+int DoStatLocked(const std::string& pathname, struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = S_IFCHR | 0666;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  // st_uid, st_gid, st_size, st_blocks should be zero.
+
+  // TODO(crbug.com/242337): Fill st_dev if needed.
+  out->st_rdev = DeviceHandler::GetDeviceId(pathname);
+  return 0;
+}
+
+}  // namespace
+
+DevZeroHandler::DevZeroHandler() : DeviceHandler("DevZeroHandler") {
+}
+
+DevZeroHandler::~DevZeroHandler() {
+}
+
+scoped_refptr<FileStream> DevZeroHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  return new DevZero(pathname, oflag);
+}
+
+int DevZeroHandler::stat(const std::string& pathname, struct stat* out) {
+  return DoStatLocked(pathname, out);
+}
+
+DevZero::DevZero(const std::string& pathname, int oflag)
+    : DeviceStream(oflag, pathname) {
+}
+
+DevZero::~DevZero() {
+}
+
+int DevZero::fstat(struct stat* out) {
+  return DoStatLocked(pathname(), out);
+}
+
+void* DevZero::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  // The very simple mmap implementation is actually compatible with Linux. The
+  // kernel's real /dev/zero device behaves as follows (tested linux-3.13.0):
+  //   int fd = open("/dev/zero", O_RDWR);
+  //   char* p =
+  //     // Returns the same result with MAP_PRIVATE.
+  //     (char*)mmap(NULL, 128, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+  //   p[1] = 1;
+  //   printf("%d\n", p[0]);  // prints 0
+  //   printf("%d\n", p[1]);  // prints 1
+  // libcore.java.nio.BufferTest.testDevZeroMapRW tests the behavior and fails
+  // if p[1] returns zero.
+  return ::mmap(addr, length, prot, flags | MAP_ANONYMOUS, -1, offset);
+}
+
+int DevZero::munmap(void* addr, size_t length) {
+  return ::munmap(addr, length);
+}
+
+ssize_t DevZero::read(void* buf, size_t count) {
+  // On the other hand, read() always fills zero even after the device is
+  // updated with write() or mmap(PROT_WRITE).
+  memset(buf, 0, count);
+  return count;
+}
+
+ssize_t DevZero::write(const void* buf, size_t count) {
+  return count;
+}
+
+const char* DevZero::GetStreamType() const { return "dev_zero"; }
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/dev_zero.h b/src/posix_translation/dev_zero.h
new file mode 100644
index 0000000..08bec85
--- /dev/null
+++ b/src/posix_translation/dev_zero.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2014 The Chromium OS 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 POSIX_TRANSLATION_DEV_ZERO_H_
+#define POSIX_TRANSLATION_DEV_ZERO_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/device_file.h"
+
+namespace posix_translation {
+
+class ARC_EXPORT DevZeroHandler : public DeviceHandler {
+ public:
+  DevZeroHandler();
+  virtual ~DevZeroHandler();
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevZeroHandler);
+};
+
+class DevZero : public DeviceStream {
+ public:
+  DevZero(const std::string& pathname, int oflag);
+
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~DevZero();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevZero);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DEV_ZERO_H_
diff --git a/src/posix_translation/dev_zero_test.cc b/src/posix_translation/dev_zero_test.cc
new file mode 100644
index 0000000..573495a
--- /dev/null
+++ b/src/posix_translation/dev_zero_test.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2014 The Chromium OS 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 <sys/sysmacros.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dev_zero.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+// We use random numbers for this test.
+const int kNullMajorId = 42;
+const int kNullMinorId = 43;
+
+namespace posix_translation {
+
+class DevZeroTest : public FileSystemTestCommon {
+ protected:
+  DevZeroTest() : handler_(new DevZeroHandler) {
+  }
+
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    DeviceHandler::AddDeviceId("/dev/zero", kNullMajorId, kNullMinorId);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(DevZeroTest);
+};
+
+TEST_F(DevZeroTest, TestInit) {
+}
+
+TEST_F(DevZeroTest, TestMkdir) {
+  EXPECT_EQ(-1, handler_->mkdir("/dev/zero", 0700));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(DevZeroTest, TestRename) {
+  EXPECT_EQ(-1, handler_->rename("/dev/zero", "/dev/foo"));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(0, handler_->rename("/dev/zero", "/dev/zero"));
+}
+
+TEST_F(DevZeroTest, TestStat) {
+  struct stat st = {};
+  EXPECT_EQ(0, handler_->stat("/dev/zero", &st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+}
+
+TEST_F(DevZeroTest, TestStatfs) {
+  struct statfs st = {};
+  EXPECT_EQ(0, handler_->statfs("/dev/zero", &st));
+  EXPECT_NE(0U, st.f_type);  // check something is filled.
+}
+
+TEST_F(DevZeroTest, TestTruncate) {
+  EXPECT_EQ(-1, handler_->truncate("/dev/zero", 0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DevZeroTest, TestUnlink) {
+  EXPECT_EQ(-1, handler_->unlink("/dev/zero"));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(DevZeroTest, TestUtimes) {
+  struct timeval times[2] = {};
+  EXPECT_EQ(-1, handler_->utimes("/dev/zero", times));
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_F(DevZeroTest, TestOpenClose) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/zero", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+}
+
+TEST_F(DevZeroTest, TestFstat) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/zero", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st = {};
+  stream->fstat(&st);
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFCHR | 0666U, st.st_mode);
+  EXPECT_EQ(makedev(kNullMajorId, kNullMinorId), st.st_rdev);
+}
+
+TEST_F(DevZeroTest, TestMmapShared) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/zero", O_RDWR, 0);
+  ASSERT_TRUE(stream != NULL);
+  char* p = static_cast<char*>(
+      stream->mmap(NULL, 128, PROT_READ | PROT_WRITE, MAP_SHARED, 0));
+  EXPECT_NE(MAP_FAILED, p);
+  EXPECT_EQ(0, p[0]);
+  p[1] = 1;
+  EXPECT_EQ(1, p[1]);
+  EXPECT_EQ(0, stream->munmap(p, 128));
+}
+
+TEST_F(DevZeroTest, TestRead) {
+  static const char kZero[16] = {};
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/zero", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  char buf[16] = {};
+  EXPECT_EQ(static_cast<ssize_t>(sizeof(buf)), stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(0, memcmp(buf, kZero, sizeof(buf)));
+}
+
+TEST_F(DevZeroTest, TestWrite) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(512, "/dev/zero", O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  EXPECT_EQ(3, stream->write("abc", 3));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/device_file.cc b/src/posix_translation/device_file.cc
new file mode 100644
index 0000000..1f58bb0
--- /dev/null
+++ b/src/posix_translation/device_file.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2014 The Chromium OS 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 "posix_translation/device_file.h"
+
+#include <sys/sysmacros.h>
+
+#include <utility>
+
+#include "common/alog.h"
+#include "posix_translation/statfs.h"
+
+namespace posix_translation {
+
+DeviceIdMap* DeviceHandler::device_id_map_;
+
+DeviceHandler::DeviceHandler(const std::string& name)
+    : FileSystemHandler(name) {
+}
+
+DeviceHandler::~DeviceHandler() {
+}
+
+Dir* DeviceHandler::OnDirectoryContentsNeeded(const std::string& path) {
+  return NULL;
+}
+
+int DeviceHandler::statfs(const std::string& pathname, struct statfs* out) {
+  return DoStatFsForDev(out);
+}
+
+void DeviceHandler::AddDeviceId(const std::string& pathname,
+                                int major_id, int minor_id) {
+  if (!device_id_map_)
+    device_id_map_ = new DeviceIdMap;
+  std::pair<DeviceIdMap::iterator, bool> p =
+      device_id_map_->insert(
+          std::make_pair(pathname, makedev(major_id, minor_id)));
+  if (!p.second) {
+    ALOG_ASSERT(p.first->second == makedev(major_id, minor_id));
+  }
+}
+
+dev_t DeviceHandler::GetDeviceId(const std::string& pathname) {
+  ALOG_ASSERT(device_id_map_);
+  DeviceIdMap::const_iterator found = device_id_map_->find(pathname);
+  ALOG_ASSERT(found != device_id_map_->end(),
+              "Unknown device file name: %s", pathname.c_str());
+  return found->second;
+}
+
+int DeviceStream::fdatasync() {
+  errno = EINVAL;
+  return -1;
+}
+
+int DeviceStream::fsync() {
+  errno = EINVAL;
+  return -1;
+}
+
+DeviceStream::DeviceStream(int oflag, const std::string& pathname)
+    : FileStream(oflag, pathname) {
+}
+
+DeviceStream::~DeviceStream() {
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/device_file.h b/src/posix_translation/device_file.h
new file mode 100644
index 0000000..7cd38e2
--- /dev/null
+++ b/src/posix_translation/device_file.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Defines base classes for all /dev files.
+
+#ifndef POSIX_TRANSLATION_DEVICE_FILE_H_
+#define POSIX_TRANSLATION_DEVICE_FILE_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+
+#include "common/export.h"
+#include "base/compiler_specific.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+typedef std::map<std::string, dev_t> DeviceIdMap;
+
+// A class which implements default behaviors of /dev handlers. This
+// class also manages device IDs of special files (st_rdev).
+class ARC_EXPORT DeviceHandler : public FileSystemHandler {
+ public:
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& path) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+
+  // MT unsafe. You should call this while only a single thread
+  // accesses to posix_translation.
+  static void AddDeviceId(const std::string& pathname,
+                          int major_id, int minor_id);
+
+  // Looks up the device ID of |pathname| from |device_id_map_|.
+  static dev_t GetDeviceId(const std::string& pathname);
+
+ protected:
+  explicit DeviceHandler(const std::string& name);
+  virtual ~DeviceHandler();
+
+ private:
+  static DeviceIdMap* device_id_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceHandler);
+};
+
+// A class which implements default behaviors of /dev handlers.
+class DeviceStream : public FileStream {
+ public:
+  virtual int fdatasync() OVERRIDE;
+  virtual int fsync() OVERRIDE;
+
+ protected:
+  DeviceStream(int oflag, const std::string& pathname);
+  virtual ~DeviceStream();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DeviceStream);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_DEVICE_FILE_H_
diff --git a/src/posix_translation/dir.h b/src/posix_translation/dir.h
new file mode 100644
index 0000000..7ab9dc8
--- /dev/null
+++ b/src/posix_translation/dir.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_DIR_H_
+#define POSIX_TRANSLATION_DIR_H_
+
+#include <dirent.h>
+#include <string>
+
+namespace posix_translation {
+
+// Interface to a directory's contents.
+class Dir {
+ public:
+  virtual ~Dir() {}
+  virtual bool GetNext(dirent* entry) = 0;
+  virtual void rewinddir() = 0;
+
+  enum Type {
+    REGULAR = DT_REG,
+    DIRECTORY = DT_DIR,
+    SYMLINK = DT_LNK,
+  };
+
+  // Adds an entry. This can only be called before GetNext() is called for the
+  // first time or right after rewinddir() is called. If |name| already exists,
+  // Add() overwrites the existing one.
+  virtual void Add(const std::string& name, Type type) = 0;
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DIR_H_
diff --git a/src/posix_translation/directory_file_stream.cc b/src/posix_translation/directory_file_stream.cc
new file mode 100644
index 0000000..d1a6e52
--- /dev/null
+++ b/src/posix_translation/directory_file_stream.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 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 "posix_translation/directory_file_stream.h"
+
+#include "common/alog.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+// TODO(crbug.com/242337): Returning the correct |st_nlink| and |st_size| values
+// from stat() and fstat() for directories requires directory scan which is
+// expensive. For now, just fill plausible values.
+const nlink_t kNLinkForDir = 32;
+const off64_t kSizeForDir = 4096;
+const blksize_t kBlockSize = 4096;
+}  // namespace
+
+DirectoryFileStream::DirectoryFileStream(const std::string& streamtype,
+                                         const std::string& pathname,
+                                         FileSystemHandler* pathhandler)
+    : FileStream(O_RDONLY | O_DIRECTORY, pathname),
+      streamtype_(streamtype + "_dir"), pathhandler_(pathhandler) {
+}
+
+DirectoryFileStream::~DirectoryFileStream() {
+}
+
+void DirectoryFileStream::FillStatData(const std::string& pathname,
+                                       struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  out->st_ino =
+      VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+  out->st_mode = S_IFDIR;
+  out->st_nlink = kNLinkForDir;
+  out->st_size = kSizeForDir;
+  out->st_blksize = kBlockSize;
+}
+
+int DirectoryFileStream::ftruncate(off64_t length) {
+  // ftruncate should not return EISDIR. Do the same as Linux kernel.
+  errno = EINVAL;
+  return -1;
+}
+
+off64_t DirectoryFileStream::lseek(off64_t offset, int whence) {
+  LOG_ALWAYS_FATAL_IF(offset != 0 || whence != SEEK_SET,
+                      "Only complete directory rewind is supported");
+  // If no contents have been requested yet, no need to request them as
+  // rewinding is a noop.
+  if (contents_)
+    contents_->rewinddir();
+  return 0;  // do the same as Linux kernel.
+}
+
+ssize_t DirectoryFileStream::read(void* buf, size_t count) {
+  errno = EISDIR;
+  return -1;
+}
+
+ssize_t DirectoryFileStream::write(const void* buf, size_t count) {
+  errno = EBADF;
+  return -1;
+}
+
+int DirectoryFileStream::fstat(struct stat* out) {
+  FillStatData(pathname(), out);
+  return 0;
+}
+
+// getdents returns the number of bytes read, meaning it should
+// always return a multiple of sizeof(dirent) or -1 in case of error.
+int DirectoryFileStream::getdents(dirent* buf, size_t count_bytes) {
+  if (!contents_) {
+    Dir* concrete_contents =
+        pathhandler_->OnDirectoryContentsNeeded(pathname());
+    if (concrete_contents)
+      contents_.reset(concrete_contents);
+  }
+  if (!contents_) {
+    // The directory may have since been deleted or our pathhandler is
+    // confused.  Report no such directory.
+    errno = ENOENT;
+    return -1;
+  }
+  const size_t count_entries = count_bytes / sizeof(dirent);
+  if (count_entries < 1) {
+    // Return buffer is too small.
+    errno = EINVAL;
+    return -1;
+  }
+  size_t entries;
+  for (entries = 0; entries < count_entries; ++entries) {
+    if (!contents_->GetNext(&buf[entries]))
+      break;
+  }
+  return entries * sizeof(dirent);
+}
+
+const char* DirectoryFileStream::GetStreamType() const {
+  return streamtype_.c_str();
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/directory_file_stream.h b/src/posix_translation/directory_file_stream.h
new file mode 100644
index 0000000..d70c9fd
--- /dev/null
+++ b/src/posix_translation/directory_file_stream.h
@@ -0,0 +1,63 @@
+// Copyright 2014 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 POSIX_TRANSLATION_DIRECTORY_FILE_STREAM_H_
+#define POSIX_TRANSLATION_DIRECTORY_FILE_STREAM_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/file_stream.h"
+
+namespace posix_translation {
+
+class FileSystemHandler;
+
+class DirectoryFileStream : public FileStream {
+ public:
+  // DirectoryFileStream gets a |pathhandler| pointer but does not take
+  // ownership of it.  We assume it is not deleted before the directory
+  // is deleted.
+  DirectoryFileStream(const std::string& streamtype,
+                      const std::string& pathname,
+                      FileSystemHandler* pathhandler);
+
+  static void FillStatData(const std::string& pathname, struct stat* out);
+
+  // If permission bits of out->st_mode are not set in a handler,
+  // VirtualFileSystem will set the bits based of its file type.
+  virtual int fstat(struct stat* out) OVERRIDE;
+
+  virtual int ftruncate(off64_t length) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+  virtual int getdents(dirent* buf, size_t count) OVERRIDE;
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~DirectoryFileStream();
+
+ private:
+  const std::string streamtype_;
+  scoped_ptr<Dir> contents_;
+  // We expect FileSystemHandlers to be permanent relative to
+  // DirectoryFileStreams, so this pointer should always be valid.
+  FileSystemHandler* pathhandler_;
+
+  DISALLOW_COPY_AND_ASSIGN(DirectoryFileStream);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_DIRECTORY_FILE_STREAM_H_
diff --git a/src/posix_translation/directory_file_stream_test.cc b/src/posix_translation/directory_file_stream_test.cc
new file mode 100644
index 0000000..3c4b113
--- /dev/null
+++ b/src/posix_translation/directory_file_stream_test.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 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 <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dev_null.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+namespace {
+
+class MockFileStream : public FileStream {
+ public:
+  MockFileStream(int oflag, const std::string& pathname)
+    : FileStream(oflag, pathname) {
+  }
+
+  // Stubs for requred methods from FileStream. Do nothing here.
+  ssize_t read(void* buf, size_t count) OVERRIDE {
+    return -1;
+  }
+  ssize_t write(const void* buf, size_t count) OVERRIDE {
+    return -1;
+  }
+  const char* GetStreamType() const OVERRIDE {
+     return "MockFileStream";
+  }
+};
+
+class MockFileHandler : public FileSystemHandler {
+ public:
+  MockFileHandler()
+    : FileSystemHandler("MockFileHandler") {
+  }
+
+  scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE {
+    file_names_.AddFile(pathname);
+    return new MockFileStream(oflag, pathname);
+  }
+  Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE {
+    return file_names_.OpenDirectory(name);
+  }
+
+  // Stubs for required methods from FileSystemHandler. Do nothing here
+  int stat(const std::string& pathname, struct stat* out) OVERRIDE {
+    return -1;
+  }
+  int statfs(const std::string& pathname, struct statfs* out) OVERRIDE {
+    return -1;
+  }
+
+ private:
+  // An object which holds a list of files and directories in the file system.
+  // Contains directory information returned to getdents.
+  DirectoryManager file_names_;
+};
+
+}  // namespace
+
+class DirectoryFileStreamTest : public FileSystemTestCommon {
+ protected:
+  DirectoryFileStreamTest() {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    handler_.reset(new MockFileHandler);
+  }
+
+  virtual void TearDown() OVERRIDE {
+    handler_.reset();
+    FileSystemTestCommon::TearDown();
+  }
+
+  scoped_refptr<FileStream> GetDirectoryFileStream() {
+    return new DirectoryFileStream("test", "/", handler_.get());
+  }
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DirectoryFileStreamTest);
+};
+
+TEST_F(DirectoryFileStreamTest, TestConstruct) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+}
+
+TEST_F(DirectoryFileStreamTest, TestFtruncate) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  EXPECT_EQ(-1, stream->ftruncate(0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(DirectoryFileStreamTest, TestLseek) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  errno = 0;
+  EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_F(DirectoryFileStreamTest, TestRead) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  char buf[128];
+  EXPECT_EQ(-1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(EISDIR, errno);
+}
+
+TEST_F(DirectoryFileStreamTest, TestWrite) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  char buf[128] = {};
+  EXPECT_EQ(-1, stream->write(buf, sizeof(buf)));
+  EXPECT_EQ(EBADF, errno);
+}
+
+TEST_F(DirectoryFileStreamTest, TestFstat) {
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  struct stat st = {};
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_NE(0U, st.st_ino);
+  EXPECT_EQ(S_IFDIR | 0U, st.st_mode);
+}
+
+TEST_F(DirectoryFileStreamTest, TestGetDentsFail) {
+  // Replace |handler_| with a one which does not support
+  // OnDirectoryContentsNeeded().
+  handler_.reset(new DevNullHandler);
+  ASSERT_EQ(NULL, handler_->OnDirectoryContentsNeeded("/"));
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  dirent ent;
+  EXPECT_EQ(-1, stream->getdents(&ent, 1 * sizeof(dirent)));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(DirectoryFileStreamTest, TestGetDents) {
+  // First case, Empty folder. We expect only 2 entries
+  scoped_refptr<FileStream> stream = GetDirectoryFileStream();
+  errno = 0;
+  dirent first_ent[3] = {};
+  ASSERT_EQ(static_cast<int>(2 * sizeof(dirent)),
+            stream->getdents(first_ent, 3 * sizeof(dirent)));
+  EXPECT_EQ(0, errno);
+  EXPECT_STREQ(".", first_ent[0].d_name);
+  EXPECT_STREQ("..", first_ent[1].d_name);
+
+  scoped_refptr<FileStream> tmp_file =
+      handler_->open(-1, "/foo", O_WRONLY | O_CREAT | O_TRUNC, 0700);
+  ASSERT_TRUE(tmp_file);
+
+  // MockFileHandler supports OnDirectoryContentsNeeded().
+  scoped_ptr<Dir> dir(handler_->OnDirectoryContentsNeeded(""));
+  ASSERT_TRUE(NULL != dir.get());
+
+  // Now we created foo and expects number of entries is increased
+  stream = GetDirectoryFileStream();
+  dirent second_ent[3] = {};
+  EXPECT_EQ(-1, stream->getdents(second_ent, 0));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  ASSERT_EQ(static_cast<int>(3 * sizeof(dirent)),
+            stream->getdents(second_ent, 3 * sizeof(dirent)));
+  EXPECT_EQ(0, errno);
+  EXPECT_STREQ(".", second_ent[0].d_name);
+  EXPECT_STREQ("..", second_ent[1].d_name);
+  EXPECT_STREQ("foo", second_ent[2].d_name);
+
+  // Rewind the stream and read the stream again with a smaller count.
+  EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+  errno = 0;
+  dirent third_ent[3] = {};
+  ASSERT_EQ(static_cast<int>(2 * sizeof(dirent)),
+            stream->getdents(third_ent, 2.5 * sizeof(dirent)));
+  EXPECT_EQ(0, errno);
+  EXPECT_STREQ(".", third_ent[0].d_name);
+  EXPECT_STREQ("..", third_ent[1].d_name);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/directory_manager.cc b/src/posix_translation/directory_manager.cc
new file mode 100644
index 0000000..f4d502f
--- /dev/null
+++ b/src/posix_translation/directory_manager.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2013 The Chromium OS 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 "posix_translation/directory_manager.h"
+
+#include <dirent.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "common/alog.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+// Our implementation of POSIX's ::DIR object.
+class DirectoryManager::DirImpl : public Dir {
+ public:
+  DirImpl(const std::string& dirname, const FilesInDir& files)
+      : dirname_(dirname), pos_(0) {
+    util::EnsurePathEndsWithSlash(&dirname_);
+    files_.reserve(files.size() + 2);
+    files_.push_back(std::make_pair("./", Dir::DIRECTORY));
+    files_.push_back(std::make_pair("../", Dir::DIRECTORY));
+    std::copy(files.begin(), files.end(), std::back_inserter(files_));
+    // Keep entries in |files_| sorted for easier unit testing. We
+    // skip the first two entries. bionic-unit-tests-cts expects the
+    // first entry is ".", not "..".
+    std::sort(files_.begin() + 2, files_.end());
+  }
+
+  virtual bool GetNext(dirent* entry) OVERRIDE {
+    if (pos_ >= files_.size())
+      return false;
+    std::string name = files_[pos_].first;
+    entry->d_type = files_[pos_].second;
+
+    ARC_STRACE_REPORT("Found %s in %s", name.c_str(), dirname_.c_str());
+    std::string path = dirname_ + name;
+
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    // VirtualFileSystem::kResolveParentSymlinks is a must here since |d_ino|
+    // must be filled as if it is filled with lstat(2).
+    sys->GetNormalizedPathLocked(
+        &path, VirtualFileSystem::kResolveParentSymlinks);
+    entry->d_ino = sys->GetInodeUncheckedLocked(path);
+
+    entry->d_reclen = sizeof(dirent);
+    if (util::EndsWithSlash(name)) {
+      ALOG_ASSERT(entry->d_type == Dir::DIRECTORY);
+      name.erase(name.size() - 1);  // remove trailing slash.
+    } else {
+      ALOG_ASSERT(entry->d_type != Dir::DIRECTORY);
+    }
+
+    const size_t len = std::min(sizeof(entry->d_name) - 1, name.length());
+    if (len != name.length())
+      ALOGW("DirImpl::GetNext: '%s' is too long. Truncated.", name.c_str());
+    memcpy(entry->d_name, name.data(), len);
+    entry->d_name[len] = '\0';
+
+    entry->d_off = pos_;
+
+    ++pos_;
+    return true;
+  }
+
+  virtual void rewinddir() OVERRIDE { pos_ = 0; }
+
+  virtual void Add(const std::string& name, Dir::Type type) OVERRIDE {
+    ALOG_ASSERT(pos_ == 0);
+    const std::pair<std::string, Dir::Type> val = std::make_pair(name, type);
+
+    // Find an existing entry with the same |name| (if any). Otherwise, |it|
+    // will point to the appropriate insertion point for the sorted vector.
+    std::vector<std::pair<std::string, Dir::Type> >::iterator it =
+        std::lower_bound(files_.begin(), files_.end(),
+                         val,  // The comparator ignores |val.second|.
+                         CompareFirst);
+    if (it != files_.end() && (it->first == name)) {
+      // Overwrite the existing element.
+      it->second = type;
+    } else {
+      files_.insert(it, val);
+    }
+  }
+
+ private:
+  virtual ~DirImpl() {}
+
+  static bool CompareFirst(const std::pair<std::string, Dir::Type>& lhs,
+                           const std::pair<std::string, Dir::Type>& rhs) {
+    return lhs.first < rhs.first;
+  }
+
+  std::string dirname_;
+  // files in the directory. sorted by name.
+  std::vector<std::pair<std::string, Dir::Type> > files_;
+  // The current position in the |files_| list.
+  size_t pos_;
+
+  DISALLOW_COPY_AND_ASSIGN(DirImpl);
+};
+
+DirectoryManager::DirectoryManager() {
+  MakeDirectory("/");
+}
+
+DirectoryManager::~DirectoryManager() {}
+
+bool DirectoryManager::AddFile(const std::string& pathname) {
+  return AddFileWithType(pathname, Dir::REGULAR);
+}
+
+bool DirectoryManager::AddFileWithType(const std::string& pathname,
+                                       Dir::Type type) {
+  if (!util::IsAbsolutePath(pathname))
+    return false;  // can not handle relative paths.
+  if (util::EndsWithSlash(pathname))
+    return false;  // not a file, but a directory.
+  if (StatDirectory(pathname))
+    return false;  // |pathname| is already registered as a directory.
+
+  DirAndFile dir_and_file = SplitPath(pathname);
+  // The directory is not in the map yet. Add it.
+  if (!StatDirectory(dir_and_file.first))
+    MakeDirectories(dir_and_file.first);
+  return AddFileInternal(dir_and_file.first, dir_and_file.second, type);
+}
+
+bool DirectoryManager::RemoveFile(const std::string& pathname) {
+  if (util::EndsWithSlash(pathname))
+    return false;  // not a file, but a directory.
+  DirAndFile dir_and_file = SplitPath(pathname);
+  FilesInDir* files = GetFilesInDir(dir_and_file.first);
+  if (!files)
+    return false;  // directory not found.
+  return files->erase(dir_and_file.second) > 0;
+}
+
+bool DirectoryManager::RemoveDirectory(const std::string& dirname) {
+  if (dirname == "/")
+    return false;  // removing the root is not allowed.
+  std::string dirname_slash = dirname;
+  util::EnsurePathEndsWithSlash(&dirname_slash);
+  FilesInDir* files = GetFilesInDir(dirname_slash);
+  if (!files)
+    return false;  // directory not found.
+  if (!files->empty())
+    return false;  // directory not empty.
+
+  // Remove the directory from the map.
+  bool result = dir_to_files_.erase(dirname_slash) > 0;
+  ALOG_ASSERT(result, "dir=%s", dirname_slash.c_str());
+
+  // Remove the directory from its parent's record.
+  std::string parent_slash = util::GetDirName(dirname_slash);
+  util::EnsurePathEndsWithSlash(&parent_slash);
+  FilesInDir* parent = GetFilesInDir(parent_slash);
+  ALOG_ASSERT(parent, "parent=%s", parent_slash.c_str());
+  result = parent->erase(dirname_slash.erase(0, parent_slash.length())) > 0;
+  ALOG_ASSERT(result,
+              "remove %s from %s", dirname_slash.c_str(), parent_slash.c_str());
+
+  return true;
+}
+
+bool DirectoryManager::StatFile(const std::string& pathname) const {
+  if (util::EndsWithSlash(pathname))
+    return false;  // not a file, but a directory.
+  DirAndFile dir_and_file = SplitPath(pathname);
+  const FilesInDir* files = GetFilesInDir(dir_and_file.first);
+  if (!files)
+    return false;  // directory not found.
+  return files->find(dir_and_file.second) != files->end();
+}
+
+bool DirectoryManager::StatDirectory(const std::string& dirname) const {
+  // TODO(yusukes): For better performance, we should eliminate the string
+  // copy (here and elsewhere in this file). Now that we know the type of each
+  // entry in |dir_to_files_|, we should be able to stop using the trailing
+  // slash as a marker.
+  std::string dirname_slash = dirname;
+  util::EnsurePathEndsWithSlash(&dirname_slash);
+  return dir_to_files_.find(dirname_slash) != dir_to_files_.end();
+}
+
+Dir* DirectoryManager::OpenDirectory(const std::string& dirname) const {
+  if (StatFile(dirname)) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+  std::string dirname_slash = dirname;
+  util::EnsurePathEndsWithSlash(&dirname_slash);
+  const FilesInDir* files = GetFilesInDir(dirname_slash);
+  if (!files) {
+    errno = ENOENT;
+    return NULL;
+  }
+  return new DirImpl(dirname_slash, *files);
+}
+
+void DirectoryManager::MakeDirectories(const std::string& dirname) {
+  std::vector<std::string> paths;
+
+  std::string dirname_slash = dirname;
+  util::EnsurePathEndsWithSlash(&dirname_slash);
+  base::SplitString(dirname_slash, '/', &paths);
+
+  std::string current_path = "/";
+  for (size_t i = 0; i < paths.size(); ++i) {
+    if (paths[i].empty())
+      continue;
+    AddFileInternal(current_path, paths[i] + "/", Dir::DIRECTORY);
+    current_path += paths[i] + "/";
+    MakeDirectory(current_path);
+  }
+}
+
+bool DirectoryManager::MakeDirectory(const std::string& dirname) {
+  if (!util::EndsWithSlash(dirname))
+    return false;
+  return dir_to_files_.insert(make_pair(dirname, FilesInDir())).second;
+}
+
+bool DirectoryManager::AddFileInternal(const std::string& directory,
+                                       const std::string& filename,
+                                       Dir::Type type) {
+  ALOG_ASSERT(StatDirectory(directory));
+  FilesInDir* files = GetFilesInDir(directory);
+  ALOG_ASSERT(files);
+  return files->insert(make_pair(filename, type)).second;
+}
+
+const DirectoryManager::FilesInDir* DirectoryManager::GetFilesInDir(
+    const std::string& directory) const {
+  return const_cast<DirectoryManager*>(this)->GetFilesInDir(directory);
+}
+
+DirectoryManager::FilesInDir*
+DirectoryManager::GetFilesInDir(const std::string& directory) {
+  ALOG_ASSERT(directory.empty() || util::EndsWithSlash(directory));
+  base::hash_map<std::string, FilesInDir>::iterator it =  // NOLINT
+      dir_to_files_.find(directory);
+  if (it == dir_to_files_.end())
+    return NULL;
+  return &(it->second);
+}
+
+// static
+std::pair<std::string, std::string> DirectoryManager::SplitPath(
+    const std::string& pathname) {
+  if (pathname.empty())
+    return std::make_pair("", "");
+  if (pathname.find('/') == std::string::npos)
+    return std::make_pair("", pathname);
+  if (util::EndsWithSlash(pathname))
+    return std::make_pair(pathname, "");
+
+  // |pathname| is not empty and has at least one slash at the beginning of the
+  // string or somewhere in the middle.
+  std::vector<std::string> paths;
+  base::SplitString(pathname, '/', &paths);
+  std::string file = paths.back();
+  paths.pop_back();
+  ALOG_ASSERT(!paths.empty());
+  return std::make_pair(JoinString(paths, '/') + "/", file);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/directory_manager.h b/src/posix_translation/directory_manager.h
new file mode 100644
index 0000000..2ce981e
--- /dev/null
+++ b/src/posix_translation/directory_manager.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_DIRECTORY_MANAGER_H_
+#define POSIX_TRANSLATION_DIRECTORY_MANAGER_H_
+
+#include <dirent.h>
+
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"  // DISALLOW_COPY_AND_ASSIGN
+#include "base/containers/hash_tables.h"
+#include "gtest/gtest_prod.h"
+#include "posix_translation/dir.h"
+
+namespace posix_translation {
+
+class Dir;
+
+// A class to keep track of a list of directories in a file system as well as a
+// list of files in each directory. This class is not thread-safe.
+class DirectoryManager {
+ public:
+  DirectoryManager();
+  ~DirectoryManager();
+
+  // Adds a file to the manager. |pathname| must be absolute, and must not end
+  // with '/'.
+  bool AddFile(const std::string& pathname);
+
+  // Adds a file with specified type to the manager. |pathname| must be
+  // absolute, and must not end with '/'. |type| is DT_* defined in
+  // dirent.h.
+  bool AddFileWithType(const std::string& pathname, Dir::Type type);
+
+  // Removes |pathname| and returns true. Returns false if |pathname| is not
+  // registered. This function does not remove directories.
+  bool RemoveFile(const std::string& pathname);
+
+  // Removes |dirname| if the directory exists and is empty. Both "/usr/bin/"
+  // and "/usr/bin" forms are accepted as |dirname|.
+  bool RemoveDirectory(const std::string& dirname);
+
+  // Returns true if |pathname| is registered. Returns NULL if |pathname| is
+  // not.
+  bool StatFile(const std::string& pathname) const;
+
+  // Returns true if the directory, |dirname|, exists. Both "/usr/bin/" and
+  // "/usr/bin" forms are accepted as |dirname|.
+  bool StatDirectory(const std::string& dirname) const;
+
+  // Returns a Dir object which contains a list of files in |dirname|. Both
+  // "/usr/bin/" and "/usr/bin" forms are accepted as |dirname|. Returns NULL
+  // if |dirname| is not registered.
+  Dir* OpenDirectory(const std::string& dirname) const;
+
+  // Adds a directory or directories to the manager.  Both "/usr/bin/" and
+  // "/usr/bin" forms are accepted as |dirname|.
+  void MakeDirectories(const std::string& dirname);
+
+  // TODO(crbug.com/190550): If needed, support rmdir.
+ private:
+  FRIEND_TEST(DirectoryManagerTest, TestAddRemoveFileBasic);
+  FRIEND_TEST(DirectoryManagerTest, TestMakeRemoveDirectory);
+  FRIEND_TEST(DirectoryManagerTest, TestGetFilesInDir);
+  FRIEND_TEST(DirectoryManagerTest, TestIsAbsolute);
+  FRIEND_TEST(DirectoryManagerTest, TestSplitPath);
+
+  class DirImpl;
+  typedef std::pair<std::string /* dir */, std::string /* file */> DirAndFile;
+  typedef base::hash_map<std::string, Dir::Type> FilesInDir;  // NOLINT
+
+  bool MakeDirectory(const std::string& dirname);
+  bool AddFileInternal(const std::string& directory,
+                       const std::string& filename,
+                       Dir::Type type);
+
+  const FilesInDir* GetFilesInDir(const std::string& directory) const;
+  FilesInDir* GetFilesInDir(const std::string& directory);
+
+  // Splits "/path/to/file" into a pair of "/path/to/" and "file".
+  static DirAndFile SplitPath(const std::string& pathname);
+
+  // A mapping from a full directory name (e.g. "/usr/lib/") to a list of files
+  // in the directory (e.g. {"libc.so.6", "X11/"}). Since we do not support
+  // symlinks/hardlinks and we only handle canonicalized file names, we don't
+  // have to have a tree. The simple map would suffice.
+  base::hash_map<std::string, FilesInDir> dir_to_files_;  // NOLINT
+
+  DISALLOW_COPY_AND_ASSIGN(DirectoryManager);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_DIRECTORY_MANAGER_H_
diff --git a/src/posix_translation/directory_manager_test.cc b/src/posix_translation/directory_manager_test.cc
new file mode 100644
index 0000000..6dd655f
--- /dev/null
+++ b/src/posix_translation/directory_manager_test.cc
@@ -0,0 +1,396 @@
+// Copyright (c) 2013 The Chromium OS 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 "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+class DirectoryManagerTest : public FileSystemTestCommon {
+ protected:
+  DirectoryManagerTest() {
+  }
+  virtual ~DirectoryManagerTest() {
+  }
+
+  DirectoryManager manager_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DirectoryManagerTest);
+};
+
+TEST_F(DirectoryManagerTest, TestInitialState) {
+  EXPECT_TRUE(manager_.StatDirectory(""));
+  EXPECT_TRUE(manager_.StatDirectory("/"));
+  EXPECT_FALSE(manager_.StatDirectory("/a"));
+  EXPECT_FALSE(manager_.StatFile(""));
+  EXPECT_FALSE(manager_.StatFile("/"));
+  EXPECT_FALSE(manager_.StatFile("a"));
+}
+
+TEST_F(DirectoryManagerTest, TestMakeDirectories) {
+  // Test if AddFile() automatically creates the directories, "/usr" and
+  // "/usr/bin".
+  EXPECT_TRUE(manager_.AddFile("/usr/bin/objdump"));
+  EXPECT_TRUE(manager_.StatDirectory(""));
+  EXPECT_TRUE(manager_.StatDirectory("/"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr/"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr/bin"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr/bin/"));
+  EXPECT_FALSE(manager_.StatDirectory("/usr/bi"));
+  EXPECT_FALSE(manager_.StatDirectory("/usr/bin/o"));
+  EXPECT_FALSE(manager_.StatDirectory("/usr/bin/objdump"));
+  EXPECT_TRUE(manager_.StatFile("/usr/bin/objdump"));
+
+  EXPECT_TRUE(manager_.AddFile("/usr/sbin/sshd"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr/sbin"));
+  EXPECT_TRUE(manager_.StatDirectory("/usr/sbin/"));
+  EXPECT_TRUE(manager_.StatFile("/usr/sbin/sshd"));
+}
+
+TEST_F(DirectoryManagerTest, TestAddRemoveFileBasic) {
+  EXPECT_FALSE(manager_.AddFile("./"));  // relative path
+  EXPECT_FALSE(manager_.AddFile("/"));  // not a file.
+  EXPECT_TRUE(manager_.AddFile("/a.txt"));
+
+  // Add "/a.txt".
+  std::string content;
+  EXPECT_TRUE(manager_.StatFile("/a.txt"));
+  EXPECT_FALSE(manager_.StatFile("a.txt"));
+  EXPECT_FALSE(manager_.StatFile("/b.txt"));
+
+  // Add "/b.txt" too.
+  EXPECT_TRUE(manager_.AddFile("/b.txt"));
+  EXPECT_TRUE(manager_.StatFile("/b.txt"));
+
+  // Read "/" directory.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/usr/");
+    EXPECT_FALSE(files);
+    files = manager_.GetFilesInDir("/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(2U, files->size());
+    ASSERT_EQ(1U, files->count("a.txt"));  // not "/a.txt".
+    ASSERT_EQ(1U, files->count("b.txt"));
+  }
+
+  // Remove "/a.txt".
+  EXPECT_FALSE(manager_.RemoveFile("a.txt"));  // relative path.
+  EXPECT_FALSE(manager_.RemoveFile("./a.txt"));  // relative path.
+  EXPECT_FALSE(manager_.RemoveFile("/a.txt/"));  // directory
+  EXPECT_TRUE(manager_.RemoveFile("/a.txt"));
+  EXPECT_FALSE(manager_.StatFile("/a.txt"));
+
+  // Read "/" directory again.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/usr/");
+    EXPECT_EQ(kNull, files);
+    files = manager_.GetFilesInDir("/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(1U, files->size());  // "a.txt" no longer exist.
+    ASSERT_EQ(1U, files->count("b.txt"));
+  }
+
+  // Remove "/b.txt".
+  EXPECT_TRUE(manager_.RemoveFile("/b.txt"));
+  EXPECT_FALSE(manager_.StatFile("/b.txt"));
+
+  // "/" still exists.
+  EXPECT_TRUE(manager_.StatDirectory("/"));
+  // Read "/" directory again.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/usr/");
+    EXPECT_EQ(kNull, files);
+    files = manager_.GetFilesInDir("/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(0U, files->size());  // all files are gone.
+  }
+}
+
+TEST_F(DirectoryManagerTest, TestMakeRemoveDirectory) {
+  // Removing the root is not allowed.
+  EXPECT_FALSE(manager_.RemoveDirectory("/"));
+
+  EXPECT_FALSE(manager_.RemoveDirectory("/foo"));  // does not exist
+  manager_.MakeDirectories("/foo/bar");
+  EXPECT_FALSE(manager_.RemoveDirectory("/foo"));  // not empty
+  EXPECT_TRUE(manager_.RemoveDirectory("/foo/bar"));
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/foo/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(0U, files->size());  // all entries in /foo are gone.
+  }
+  EXPECT_TRUE(manager_.AddFile("/foo/a.txt"));
+  EXPECT_FALSE(manager_.RemoveDirectory("/foo"));  // not empty
+  EXPECT_FALSE(manager_.RemoveDirectory("/foo/a.txt"));  // not a directory
+  EXPECT_TRUE(manager_.RemoveFile("/foo/a.txt"));
+  EXPECT_TRUE(manager_.RemoveDirectory("/foo"));  // now succeeds
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(0U, files->size());  // all entries in / are gone.
+  }
+}
+
+TEST_F(DirectoryManagerTest, TestGetFilesInDir) {
+  // Test if AddFile() automatically creates the dir, "/usr/bin".
+  EXPECT_TRUE(manager_.AddFile("/1"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/2"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/3"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/dir2/4"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/dir2/5"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/6"));
+  EXPECT_TRUE(manager_.AddFile("/dir1/dir3/7"));
+  // Add a file and then remove it.
+  EXPECT_TRUE(manager_.AddFile("/dir1/8"));
+  EXPECT_TRUE(manager_.RemoveFile("/dir1/8"));
+  EXPECT_TRUE(manager_.AddFile("/dir4/9"));
+  // This operation does not remove the directory "/dir4" itself.
+  EXPECT_TRUE(manager_.RemoveFile("/dir4/9"));
+
+  // Read "/dir1" directory. Confirm 1, 4, 5, and 7 are NOT returned.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files =
+        manager_.GetFilesInDir("/dir1/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(5U, files->size());
+    EXPECT_EQ(1U, files->count("2"));
+    EXPECT_EQ(1U, files->count("3"));
+    EXPECT_EQ(1U, files->count("dir2/"));
+    EXPECT_EQ(1U, files->count("6"));
+    EXPECT_EQ(1U, files->count("dir3/"));
+  }
+
+  // Read "/" directory. Confirm only "1", "dir1/", and "dir4" are returned.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files = manager_.GetFilesInDir("/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(3U, files->size());
+    EXPECT_EQ(1U, files->count("1"));
+    EXPECT_EQ(1U, files->count("dir1/"));
+    EXPECT_EQ(1U, files->count("dir4/"));
+  }
+
+  // Read "/dir1/dir2" directory.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files =
+        manager_.GetFilesInDir("/dir1/dir2/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(2U, files->size());
+    EXPECT_EQ(1U, files->count("4"));
+    EXPECT_EQ(1U, files->count("5"));
+  }
+
+  // Read "/dir1/dir3" directory.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files =
+        manager_.GetFilesInDir("/dir1/dir3/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(1U, files->size());
+    EXPECT_EQ(1U, files->count("7"));
+  }
+
+  // Read "/dir4" directory.
+  {
+    static const DirectoryManager::FilesInDir* kNull = NULL;
+    const DirectoryManager::FilesInDir* files =
+        manager_.GetFilesInDir("/dir4/");
+    ASSERT_NE(kNull, files);
+    EXPECT_EQ(0U, files->size());
+  }
+}
+
+TEST_F(DirectoryManagerTest, TestOpenDirectory) {
+  static const Dir* kNullDirp = NULL;
+
+  EXPECT_TRUE(manager_.AddFile("/1"));
+  EXPECT_TRUE(manager_.AddFile("/2"));
+  EXPECT_TRUE(manager_.AddFile("/3"));
+  // Open the dir, remove one file, and use the Dir object.
+  scoped_ptr<Dir> dirp(manager_.OpenDirectory("/"));
+  ASSERT_NE(kNullDirp, dirp.get());
+  EXPECT_TRUE(manager_.RemoveFile("/2"));
+  dirent entry;
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("1"), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  // Since OpenDirectory is called before calling RemoveFile, "2" is returned.
+  EXPECT_EQ(std::string("2"), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("3"), entry.d_name);
+  EXPECT_FALSE(dirp->GetNext(&entry));
+  EXPECT_FALSE(dirp->GetNext(&entry));
+
+  // Test error cases.
+  dirp.reset(manager_.OpenDirectory("/a"));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(kNullDirp, dirp.get());
+  dirp.reset(manager_.OpenDirectory("/1"));
+  EXPECT_EQ(ENOTDIR, errno);
+  EXPECT_EQ(kNullDirp, dirp.get());
+  dirp.reset(manager_.OpenDirectory("/2"));  // already removed file.
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(kNullDirp, dirp.get());
+}
+
+TEST_F(DirectoryManagerTest, TestOpenSubDirectory) {
+  static const Dir* kNullDirp = NULL;
+  EXPECT_TRUE(manager_.AddFile("/dir/1"));
+
+  // Open "/dir" and scan the directory.
+  scoped_ptr<Dir> dirp(manager_.OpenDirectory("/dir"));
+  ASSERT_NE(kNullDirp, dirp.get());
+  dirent entry;
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  const ino_t ino_1 = entry.d_ino;
+  EXPECT_NE(0U, ino_1);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("1"), entry.d_name);
+  EXPECT_FALSE(dirp->GetNext(&entry));
+
+  // Open "/" and scan the directory.
+  dirp.reset(manager_.OpenDirectory("/"));
+  ASSERT_NE(kNullDirp, dirp.get());
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  const ino_t ino_2 = entry.d_ino;
+  EXPECT_NE(0U, ino_2);
+
+  // Compare inode numbers for "/." and "/dir/..". They should be the same.
+  EXPECT_EQ(ino_1, ino_2);
+}
+
+TEST_F(DirectoryManagerTest, TestOpenDirectoryTooLongFileName) {
+  static const Dir* kNullDirp = NULL;
+
+  dirent e;
+  EXPECT_TRUE(manager_.AddFile(
+      "/" + std::string(sizeof(e.d_name) * 2, 'W')));
+  EXPECT_TRUE(manager_.AddFile(
+      "/" + std::string(sizeof(e.d_name) + 1, 'X')));
+  EXPECT_TRUE(manager_.AddFile(
+      "/" + std::string(sizeof(e.d_name), 'Y')));
+  EXPECT_TRUE(manager_.AddFile(
+      "/" + std::string(sizeof(e.d_name) - 1, 'Z')));
+  scoped_ptr<Dir> dirp(manager_.OpenDirectory("/"));
+  ASSERT_NE(kNullDirp, dirp.get());
+  dirent entry;
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  // Check that DirImpl is properly handling a too long d_name entry.
+  EXPECT_EQ(std::string(sizeof(e.d_name) - 1, 'W'), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(sizeof(e.d_name) - 1, 'X'), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(sizeof(e.d_name) - 1, 'Y'), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(sizeof(e.d_name) - 1, 'Z'), entry.d_name);
+}
+
+TEST_F(DirectoryManagerTest, TestSplitPath) {
+  std::string test_str;
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "/";
+  EXPECT_EQ("/", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "/a.txt";
+  EXPECT_EQ("/", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("a.txt", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "/a/b.txt";
+  EXPECT_EQ("/a/", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("b.txt", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "/a/b/";
+  EXPECT_EQ("/a/b/", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "a/b/";
+  EXPECT_EQ("a/b/", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).second);
+
+  test_str = "a.txt";
+  EXPECT_EQ("", DirectoryManager::SplitPath(test_str).first);
+  EXPECT_EQ("a.txt", DirectoryManager::SplitPath(test_str).second);
+}
+
+TEST_F(DirectoryManagerTest, TestOpenDirectoryAddEntryLater) {
+  static const Dir* kNullDirp = NULL;
+
+  EXPECT_TRUE(manager_.AddFile("/2"));
+  // Open the dir, remove one file, and use the Dir object.
+  scoped_ptr<Dir> dirp(manager_.OpenDirectory("/"));
+  ASSERT_NE(kNullDirp, dirp.get());
+
+  // Overwrite the entry.
+  dirp->Add("2", Dir::SYMLINK);
+  dirent entry;
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("2"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);  // no longer DT_REG.
+  EXPECT_FALSE(dirp->GetNext(&entry));
+
+  // Add an entry.
+  dirp->rewinddir();
+  dirp->Add("0", Dir::SYMLINK);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("0"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("2"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+  EXPECT_FALSE(dirp->GetNext(&entry));
+
+  // Add an entry again.
+  dirp->rewinddir();
+  dirp->Add("1", Dir::REGULAR);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("0"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("1"), entry.d_name);
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("2"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+  EXPECT_FALSE(dirp->GetNext(&entry));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/epoll_stream.cc b/src/posix_translation/epoll_stream.cc
new file mode 100644
index 0000000..5207bdc
--- /dev/null
+++ b/src/posix_translation/epoll_stream.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2013 The Chromium OS 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 <string.h>
+#include <sys/time.h>
+
+#include <algorithm>
+#include <set>
+
+#include "common/danger.h"
+#include "posix_translation/epoll_stream.h"
+#include "posix_translation/time_util.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+EPollStream::EPollStream(int fd, int oflag)
+    : FileStream(oflag, ""), fd_(fd),
+      cond_(&VirtualFileSystem::GetVirtualFileSystem()->mutex()) {
+}
+
+EPollStream::~EPollStream() {
+}
+
+void EPollStream::OnLastFileRef() {
+  // We cannot do this in the destructor because we have cyclic reference
+  // (epoll_map_ and listeners_) so ~EPollStream will not be called unless
+  // we call StopListeningTo for epoll_map_.
+  for (EPollMap::iterator it = epoll_map_.begin(); it != epoll_map_.end();
+       ++it) {
+    StopListeningTo(it->second.stream_);
+  }
+  epoll_map_.clear();
+}
+
+void EPollStream::HandleNotificationFrom(
+    scoped_refptr<FileStream> file, bool is_closing) {
+  ALOG_ASSERT(epoll_map_.count(file) != 0,
+              "Epoll listener notification from unregistered file");
+  if (is_closing) {
+    epoll_map_.erase(file);
+  }
+  // Multiple threads could wait on a level-triggered epoll. We could only
+  // use Signal() if all registrations were edge-triggered or one-shot.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  cond_.Broadcast();
+}
+
+int EPollStream::epoll_ctl(
+    int op, scoped_refptr<FileStream> file, struct epoll_event* event) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  EPollMap::iterator it;
+  switch (op) {
+    case EPOLL_CTL_ADD:
+      // TODO(crbug.com/238302): Support edge-based triggering. Without such
+      // support the application may be waking up in a busy loop.
+      if (event->events & (EPOLLPRI | EPOLLET | EPOLLONESHOT)) {
+        ALOGE("Unsupported epoll events: %s",
+              arc::GetEpollEventStr(event->events).c_str());
+      }
+      if (!epoll_map_.insert(std::make_pair(
+              file.get(), EPollEntry(file, *event))).second) {
+        errno = EEXIST;
+        return -1;
+      }
+      if (!StartListeningTo(file)) {
+        errno = EPERM;
+        return -1;
+      }
+      // The spec requires that a blocked epoll_wait() checks for new files.
+      sys->mutex().AssertAcquired();
+      cond_.Broadcast();
+      break;
+    case EPOLL_CTL_MOD:
+      if (event->events & (EPOLLPRI | EPOLLET | EPOLLONESHOT)) {
+        ALOGE("Unsupported epoll events: %s",
+              arc::GetEpollEventStr(event->events).c_str());
+      }
+      it = epoll_map_.find(file);
+      if (it == epoll_map_.end()) {
+        errno = ENOENT;
+        return -1;
+      }
+      it->second.event_ = *event;
+      sys->mutex().AssertAcquired();
+      cond_.Broadcast();  // New events may have to unblock epoll_wait().
+      break;
+    case EPOLL_CTL_DEL:
+      if (!epoll_map_.erase(file)) {
+        errno = ENOENT;
+        return -1;
+      }
+      StopListeningTo(file);
+      break;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+
+  return 0;
+}
+
+int EPollStream::epoll_wait(struct epoll_event* events, int maxevents,
+                            int timeout) {
+  if (!events) {
+    errno = EFAULT;
+    return -1;
+  }
+  if (maxevents <= 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  const base::TimeTicks time_limit = timeout <= 0 ?
+      base::TimeTicks() :  // No timeout.
+      base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(timeout);
+
+  // If timeout is 0, it is just polling. Then, set this flag, so that result
+  // will be returned properly.
+  bool is_timedout = (timeout == 0);
+  while (true) {
+    // check all fds in epoll_map_ for relevant events
+    // TODO(crbug.com/242633): Enqueue notifications from files and avoid O(N)
+    // search.
+    int count = 0;
+    for (EPollMap::iterator it = epoll_map_.begin(); it != epoll_map_.end();
+         ++it) {
+      scoped_refptr<FileStream> stream = it->second.stream_;
+      const uint32_t event_mask =
+          it->second.event_.events | POLLERR | POLLHUP | POLLNVAL;
+      uint32_t found_events = stream->GetPollEvents() & event_mask;
+      if (found_events) {
+        events[count].events = found_events;
+        events[count].data = it->second.event_.data;
+        count++;
+        if (count == maxevents)
+          break;
+      }
+    }
+
+    if (is_timedout || count > 0)
+      return count;
+
+    // 'timedout == true' only means that |timeout_rem| has expired. |cond|
+    // might or might not have been signaled. To update |count|, we need to
+    // run the for-loop above once more.
+    is_timedout = internal::WaitUntil(&cond_, time_limit);
+  }
+
+  // Should not reach here.
+  ALOG_ASSERT(false);
+}
+
+ssize_t EPollStream::read(void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+ssize_t EPollStream::write(const void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+const char* EPollStream::GetStreamType() const {
+  return "epoll";
+}
+
+EPollStream::EPollEntry::EPollEntry()
+    : stream_(NULL) {
+  memset(&event_, 0, sizeof(event_));
+}
+
+EPollStream::EPollEntry::EPollEntry(scoped_refptr<FileStream> stream,
+                                    struct epoll_event event)
+    : stream_(stream), event_(event) {
+}
+
+EPollStream::EPollEntry::~EPollEntry() {
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/epoll_stream.h b/src/posix_translation/epoll_stream.h
new file mode 100644
index 0000000..e0b43f6
--- /dev/null
+++ b/src/posix_translation/epoll_stream.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_EPOLL_STREAM_H_
+#define POSIX_TRANSLATION_EPOLL_STREAM_H_
+
+#include <string.h>
+#include <sys/epoll.h>
+
+#include <map>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/synchronization/condition_variable.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+class EPollStream : public FileStream {
+ public:
+  EPollStream(int fd, int oflag);
+
+  virtual int epoll_ctl(int op, scoped_refptr<FileStream> file,
+                        struct epoll_event* event) OVERRIDE;
+  virtual int epoll_wait(struct epoll_event* events, int maxevents,
+                         int timeout) OVERRIDE;
+
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~EPollStream();
+
+  virtual void OnLastFileRef() OVERRIDE;
+
+  virtual void HandleNotificationFrom(
+      scoped_refptr<FileStream> file, bool is_closing) OVERRIDE;
+
+ private:
+  class EPollEntry {
+   public:
+    EPollEntry();
+    EPollEntry(scoped_refptr<FileStream> stream, struct epoll_event event);
+    virtual ~EPollEntry();
+
+    scoped_refptr<FileStream> stream_;
+    struct epoll_event event_;
+  };
+
+  // The key is FileStream*, obfuscated to avoid direct use.
+  typedef std::map<void*, EPollEntry> EPollMap;
+
+  int fd_;
+  EPollMap epoll_map_;
+  base::ConditionVariable cond_;
+
+  DISALLOW_COPY_AND_ASSIGN(EPollStream);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_EPOLL_STREAM_H_
diff --git a/src/posix_translation/external_file.cc b/src/posix_translation/external_file.cc
new file mode 100644
index 0000000..2494766
--- /dev/null
+++ b/src/posix_translation/external_file.cc
@@ -0,0 +1,423 @@
+// Copyright (c) 2014 The Chromium OS 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 "posix_translation/external_file.h"
+
+#include <errno.h>
+
+#include "base/strings/string_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "common/alog.h"
+#include "native_client/src/untrusted/irt/irt.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+base::Lock& GetFileSystemMutex() {
+  return VirtualFileSystem::GetVirtualFileSystem()->mutex();
+}
+
+}  // namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// ExternalFileWrapperHandler
+ExternalFileWrapperHandler::ExternalFileWrapperHandler()
+  : FileSystemHandler("ExternalFileWrapperHandler") {
+  nacl_interface_query(NACL_IRT_RANDOM_v0_1, &random_, sizeof(random_));
+  ALOG_ASSERT(random_.get_random_bytes);
+}
+
+ExternalFileWrapperHandler::~ExternalFileWrapperHandler() {
+}
+
+int ExternalFileWrapperHandler::mkdir(
+    const std::string& pathname, mode_t mode) {
+  ALOG_ASSERT(!util::EndsWithSlash(pathname));
+  if (pathname == root_directory_) {
+    // Request to the root directory.
+    errno = EEXIST;
+    return -1;
+  }
+
+  std::string slot = GetSlot(pathname);
+  if (slot.empty()) {
+    // Request to an invalid path.
+    errno = EPERM;
+    return -1;
+  }
+
+  errno = (slot_file_map_.find(slot) == slot_file_map_.end()) ? EPERM : EEXIST;
+  return -1;
+}
+
+scoped_refptr<FileStream> ExternalFileWrapperHandler::open(
+    int unused_fd, const std::string& pathname, int oflag, mode_t cmode) {
+  ALOG_ASSERT(!util::EndsWithSlash(pathname));
+  static const char kExternalFileDirName[] = "external_file";
+
+  if (pathname == root_directory_) {
+    // Request to the root directory.
+    return new DirectoryFileStream(kExternalFileDirName, pathname, this);
+  }
+
+  std::string slot = GetSlot(pathname);
+  if (slot_file_map_.find(slot) == slot_file_map_.end()) {
+    // Invalid path format or slot does not exist.
+    errno = ENOENT;
+    return NULL;
+  }
+
+  // Request to the slot directory.
+  return new DirectoryFileStream(kExternalFileDirName, pathname, this);
+}
+
+int ExternalFileWrapperHandler::stat(
+    const std::string& pathname, struct stat* out) {
+  ALOG_ASSERT(!util::EndsWithSlash(pathname));
+  if (pathname == root_directory_) {
+    // Request to the root directory.
+    DirectoryFileStream::FillStatData(pathname, out);
+    return 0;
+  }
+
+  std::string slot = GetSlot(pathname);
+  if (slot_file_map_.find(slot) == slot_file_map_.end()) {
+    // Invalid path format or slot does not exist.
+    errno = ENOENT;
+    return -1;
+  }
+
+  // Request to the slot directory.
+  DirectoryFileStream::FillStatData(pathname, out);
+  return 0;
+}
+
+int ExternalFileWrapperHandler::statfs(
+    const std::string& pathname, struct statfs* out) {
+  struct stat st;
+  if (this->stat(pathname, &st) == 0)
+    return DoStatFsForData(out);
+  errno = ENOENT;
+  return -1;
+}
+
+void ExternalFileWrapperHandler::OnMounted(const std::string& path) {
+  ALOG_ASSERT(root_directory_.empty(),
+              "Do not mount the same wrapper handler to two or more places: %s",
+              path.c_str());
+  ALOG_ASSERT(util::EndsWithSlash(path));
+  root_directory_ = path;
+  util::RemoveTrailingSlashes(&root_directory_);
+}
+
+void ExternalFileWrapperHandler::OnUnmounted(const std::string& path) {
+  ALOG_ASSERT(path == root_directory_ + "/");
+  root_directory_.clear();
+}
+
+Dir* ExternalFileWrapperHandler::OnDirectoryContentsNeeded(
+    const std::string& pathname) {
+  ALOG_ASSERT(!util::EndsWithSlash(pathname));
+  DirectoryManager directory;
+
+  if (pathname == root_directory_) {
+    // Request for the root directory.
+    for (SlotFileMap::iterator i = slot_file_map_.begin();
+         i != slot_file_map_.end(); ++i) {
+      directory.MakeDirectories(pathname + i->first);
+    }
+    return directory.OpenDirectory(pathname);
+  }
+
+  std::string slot = GetSlot(pathname);
+  if (slot.empty()) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  SlotFileMap::iterator i = slot_file_map_.find(slot);
+  if (i == slot_file_map_.end()) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  // Request for the slot directory.
+  directory.MakeDirectories(pathname);
+  directory.AddFile(pathname + i->second);
+  return directory.OpenDirectory(pathname);
+}
+
+std::string ExternalFileWrapperHandler::GetSlot(const std::string& file_path) {
+  ALOG_ASSERT(!root_directory_.empty(), "OnMounted() has not been called.");
+  if (!StartsWithASCII(file_path, root_directory_, true) &&
+      util::EndsWithSlash(file_path)) {
+    return "";
+  }
+
+  std::string slot = file_path.c_str() + root_directory_.size();
+  if (slot.empty() || slot == "/") {
+    return "";
+  }
+
+  ALOG_ASSERT(StartsWithASCII(slot, "/", true));
+  if (slot.find('/', 1) != std::string::npos) {
+    return "";
+  }
+
+  return slot;
+}
+
+std::string ExternalFileWrapperHandler::GenerateUniqueSlotLocked() const {
+  // Generate 128-bit random string.
+  GetFileSystemMutex().AssertAcquired();
+  const ssize_t kRandLen = 16;
+  unsigned char buffer[kRandLen];
+  for (ssize_t i = 0; i < kRandLen; ) {
+    size_t nread = 0;
+    int r;
+    do {
+      r = random_.get_random_bytes(buffer, kRandLen - i, &nread);
+      ALOG_ASSERT(r == 0 || r == EINTR);
+    } while (r == EINTR);  // try again in the case of EINTR.
+    ALOG_ASSERT(nread > 0);
+    i += nread;
+  }
+  return "/" + base::HexEncode(buffer, kRandLen);;
+}
+
+std::string ExternalFileWrapperHandler::SetPepperFileSystem(
+    const pp::FileSystem* pepper_file_system,
+    const std::string& mount_source_in_pepper_file_system,
+    const std::string& mount_dest_in_vfs) {
+  base::AutoLock lock(GetFileSystemMutex());
+
+  ALOG_ASSERT(pepper_file_system);
+  ALOG_ASSERT(util::IsAbsolutePath(mount_source_in_pepper_file_system));
+  ALOG_ASSERT(mount_source_in_pepper_file_system.find('/', 1) ==
+              std::string::npos);
+
+  std::string slot;
+  if (mount_dest_in_vfs.empty()) {
+    // If |mount_point_in_vfs| is not specified, mount it on a unique path.
+    slot = GenerateUniqueSlotLocked();
+  } else {
+    ALOG_ASSERT(StartsWithASCII(mount_dest_in_vfs, root_directory_, true));
+    ALOG_ASSERT(
+        EndsWith(mount_dest_in_vfs, mount_source_in_pepper_file_system, true));
+    // Remove leading |root_directory_| and trailing
+    // |mount_source_in_pepper_file_system| to get slot.
+    // For example, if the |mount_dest_in_vfs| is "/a/b/c/d.txt" and the
+    // |root_directory_| is "/a/b" and
+    // |mount_source_in_pepper_file_system| is "/d.txt", the slot is just
+    // after |root_directory_| and just before
+    // |mount_source_in_pepper_file_system|.
+    slot = mount_dest_in_vfs.substr(
+        root_directory_.size(),
+        mount_dest_in_vfs.size() - root_directory_.size() -
+        mount_source_in_pepper_file_system.size());
+    ALOG_ASSERT(StartsWithASCII(slot, "/", true));
+    ALOG_ASSERT(slot.find('/', 1) == std::string::npos);
+  }
+
+  ALOG_ASSERT(!GetSlot(root_directory_ + slot).empty());
+
+  std::string mount_point =
+      root_directory_ + slot + mount_source_in_pepper_file_system;
+  ALOG_ASSERT(mount_dest_in_vfs.empty() || mount_dest_in_vfs == mount_point);
+
+  LOG_ALWAYS_FATAL_IF(
+      !slot_file_map_.insert(
+          make_pair(slot, mount_source_in_pepper_file_system)).second,
+      "%s", mount_point.c_str());
+  scoped_ptr<FileSystemHandler> handler;
+  {
+    // Need to unlock the file system lock since handler creation and Mount
+    // requires filesystem lock.
+    base::AutoUnlock unlock(GetFileSystemMutex());
+    handler = MountExternalFile(pepper_file_system,
+                                mount_source_in_pepper_file_system,
+                                mount_point);
+  }
+
+  file_handlers_.push_back(handler.release());
+  return mount_point;
+}
+
+scoped_ptr<FileSystemHandler> ExternalFileWrapperHandler::MountExternalFile(
+    const pp::FileSystem* file_system, const std::string& path_in_external_fs,
+    const std::string& path_in_vfs) {
+  scoped_ptr<FileSystemHandler> handler(new ExternalFileHandler(
+      file_system, path_in_external_fs, path_in_vfs));
+  VirtualFileSystem::GetVirtualFileSystem()->Mount(path_in_vfs, handler.get());
+  return handler.Pass();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ExternalFileHandlerBase
+ExternalFileHandlerBase::ExternalFileHandlerBase(const char* classname)
+    : PepperFileHandler(classname, 0 /* disable cache */) {
+}
+
+ExternalFileHandlerBase::~ExternalFileHandlerBase() {
+}
+
+std::string ExternalFileHandlerBase::SetPepperFileSystem(
+    const pp::FileSystem* file_system,
+    const std::string& path_in_pepperfs,
+    const std::string& path_in_vfs) {
+  ppapi_file_path_ = path_in_pepperfs;
+
+  // If already mount point path is set, |path_in_vfs| must equal with it.
+  ALOG_ASSERT(virtual_file_path_.empty() || virtual_file_path_ == path_in_vfs);
+  virtual_file_path_ = path_in_vfs;
+  return PepperFileHandler::SetPepperFileSystem(
+      file_system, path_in_pepperfs, path_in_vfs);
+}
+
+int ExternalFileHandlerBase::mkdir(const std::string& pathname, mode_t mode) {
+  return PepperFileHandler::mkdir(GetExternalPPAPIPath(pathname), mode);
+}
+
+scoped_refptr<FileStream> ExternalFileHandlerBase::open(
+    int unused_fd, const std::string& pathname, int oflag, mode_t cmode) {
+  return PepperFileHandler::open(unused_fd, GetExternalPPAPIPath(pathname),
+                                 oflag, cmode);
+}
+
+int ExternalFileHandlerBase::remove(const std::string& pathname) {
+  return PepperFileHandler::remove(GetExternalPPAPIPath(pathname));
+}
+
+int ExternalFileHandlerBase::rename(const std::string& oldpath,
+                                    const std::string& newpath) {
+  return PepperFileHandler::rename(GetExternalPPAPIPath(oldpath),
+                                   GetExternalPPAPIPath(newpath));
+}
+
+int ExternalFileHandlerBase::rmdir(const std::string& pathname) {
+  return PepperFileHandler::rmdir(GetExternalPPAPIPath(pathname));
+}
+
+int ExternalFileHandlerBase::stat(const std::string& pathname,
+                                  struct stat* out) {
+  return PepperFileHandler::stat(GetExternalPPAPIPath(pathname), out);
+}
+
+int ExternalFileHandlerBase::statfs(
+    const std::string& pathname, struct statfs* out) {
+  return PepperFileHandler::statfs(GetExternalPPAPIPath(pathname), out);
+}
+
+int ExternalFileHandlerBase::truncate(const std::string& pathname,
+                                      off64_t length) {
+  return PepperFileHandler::truncate(GetExternalPPAPIPath(pathname), length);
+}
+
+int ExternalFileHandlerBase::unlink(const std::string& pathname) {
+  return PepperFileHandler::unlink(GetExternalPPAPIPath(pathname));
+}
+
+int ExternalFileHandlerBase::utimes(const std::string& pathname,
+                                    const struct timeval times[2]) {
+  return PepperFileHandler::utimes(GetExternalPPAPIPath(pathname), times);
+}
+
+void ExternalFileHandlerBase::OnMounted(const std::string& path) {
+  return PepperFileHandler::OnMounted(GetExternalPPAPIPath(path));
+}
+
+void ExternalFileHandlerBase::OnUnmounted(const std::string& path) {
+  return PepperFileHandler::OnUnmounted(GetExternalPPAPIPath(path));
+}
+
+void ExternalFileHandlerBase::SetMountPointInVFS(const std::string& path) {
+  ALOG_ASSERT(virtual_file_path_.empty(),
+              "The mount point has already been set: %s", path.c_str());
+  virtual_file_path_ = path;
+}
+
+std::string ExternalFileHandlerBase::GetExternalPPAPIPath(
+    const std::string& file_path) const {
+  std::string output = file_path;
+
+  if (StartsWithASCII(output, virtual_file_path_, true)) {
+    ReplaceFirstSubstringAfterOffset(&output, 0, virtual_file_path_,
+                                     ppapi_file_path_);
+  } else {
+    const std::string non_slash_tail_path =
+        virtual_file_path_.substr(0, virtual_file_path_.size() - 1);
+    if (StartsWithASCII(output, non_slash_tail_path, true)) {
+      ReplaceFirstSubstringAfterOffset(&output, 0, non_slash_tail_path,
+                                       ppapi_file_path_);
+    } else {
+      // Some method calls other functions with re-written path. For example
+      // PepperFileHandler::statfs calls PepperFileHandler::stat. Passing
+      // through without re-writing.
+      ALOG_ASSERT(StartsWithASCII(output, ppapi_file_path_, true));
+    }
+  }
+  return output;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ExternalFileHandler
+ExternalFileHandler::ExternalFileHandler(
+    const pp::FileSystem* file_system,
+    const std::string& ppapi_file_path,
+    const std::string& virtual_file_path)
+    : ExternalFileHandlerBase("ExternalFileHandler") {
+  SetPepperFileSystem(file_system, ppapi_file_path, virtual_file_path);
+}
+
+ExternalFileHandler::~ExternalFileHandler() {
+}
+
+scoped_refptr<FileStream> ExternalFileHandler::open(
+    int unused_fd, const std::string& pathname, int oflag, mode_t cmode) {
+  // Drop TRUNC and CREAT here because pp::FileIO::Open with TRUNC/CREAT for
+  // chosen file does not work. (crbug.com/336160).
+  scoped_refptr<FileStream> fs =
+      ExternalFileHandlerBase::open(unused_fd, pathname,
+                                    oflag & ~(O_TRUNC | O_CREAT), cmode);
+  if (fs && (oflag & O_TRUNC))
+    fs->ftruncate(0);
+  return fs;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ExternalDirectoryHandler
+ExternalDirectoryHandler::ExternalDirectoryHandler(
+    const std::string& virtual_file_path,
+    ExternalDirectoryHandler::Observer* observer)
+    : ExternalFileHandlerBase("ExternalDirectoryHandler"),
+      observer_(observer) {
+  ALOG_ASSERT(observer_.get());
+  SetMountPointInVFS(virtual_file_path);
+}
+
+ExternalDirectoryHandler::~ExternalDirectoryHandler() {
+}
+
+void ExternalDirectoryHandler::Initialize() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (!IsInitialized())
+    observer_->OnInitializing();
+
+  // Check IsInitialized again since OnInitializing may initialize this
+  // handler synchronously.
+  if (!IsInitialized())
+    ExternalFileHandlerBase::Initialize();
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/external_file.h b/src/posix_translation/external_file.h
new file mode 100644
index 0000000..87fa452
--- /dev/null
+++ b/src/posix_translation/external_file.h
@@ -0,0 +1,200 @@
+// Copyright (c) 2014 The Chromium OS 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 POSIX_TRANSLATION_EXTERNAL_FILE_H_
+#define POSIX_TRANSLATION_EXTERNAL_FILE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string16.h"
+#include "common/export.h"
+#include "native_client/src/untrusted/irt/irt.h"
+#include "posix_translation/pepper_file.h"
+
+namespace posix_translation {
+
+// A class which provides the directory handling which holding external files.
+// The mounted path is constructed from three parts: RootDirectory, Slot,
+// Filename. The RootDirectory is stored in |root_directory_|, and the slot and
+// filename pair is stored in |slot_file_map_|.
+// Example:
+//   The mount point of this handler is /data/data/org.chromium.arc/external.
+//   Then chosen file is "/foo.txt"
+//
+//   In this case, the mounted path will be like:
+//   /data/data/org.chromium.arc/external/0183748209/foo.txt
+//   Here, RootDirectory is "/data/data/org.chromium.arc/external",
+//   Slot is "/361F9A2BF6CDFD23EEE2C3D618C170E5", and Filename is "/foo.txt"
+//
+// RootDirectory:
+//   The RootDirectory is the same as mount point of this file handler. In this
+//   handler, RootDirectory must NOT end with slash.
+// Slot:
+//   The Slot is used for identifying the mounted file. One slot is
+//   corresponding to one mounted entry. The slot must start with slash and the
+//   rest must only contain alphanumeric characters.
+// Filename:
+//   The Filename is corresponding to absolute path in chosen Pepper file
+//   system. In this case, the path must start with slash and the rest must NOT
+//   contain slash. There is only one Filename per slot.
+class ARC_EXPORT ExternalFileWrapperHandler : public FileSystemHandler {
+ public:
+  ExternalFileWrapperHandler();
+  virtual ~ExternalFileWrapperHandler();
+
+  // Overridden from FileSystemHandler
+  virtual scoped_refptr<FileStream> open(int unused_fd,
+                                         const std::string& pathname,
+                                         int oflag, mode_t cmode) OVERRIDE;
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual void OnMounted(const std::string& path) OVERRIDE;
+  virtual void OnUnmounted(const std::string& path) OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual std::string SetPepperFileSystem(
+      const pp::FileSystem* pepper_file_system,
+      const std::string& mount_source_in_pepper_file_system,
+      const std::string& mount_dest_in_vfs) OVERRIDE;
+
+ private:
+  friend class TestableExternalFileWrapperHandler;
+
+  // Returns slot from |file_path|. The slot is starting slash.
+  // This function returns empty string if |file_path| is invalid.
+  std::string GetSlot(const std::string& file_path);
+
+  // Generates unique slot name.
+  std::string GenerateUniqueSlotLocked() const;
+
+  // This function takes the ownership of |file_system|.
+  // virtual for testing purpose.
+  virtual scoped_ptr<FileSystemHandler> MountExternalFile(
+      const pp::FileSystem* file_system,
+      const std::string& path_in_external_fs,
+      const std::string& path_in_vfs);
+
+  // The mounted directory in VFS. This must NOT end with slash.
+  std::string root_directory_;
+
+  // A map from slot to filename the external file handler having.
+  typedef base::hash_map<std::string, std::string> SlotFileMap;  // NOLINT
+  SlotFileMap slot_file_map_;
+
+  // Mounted handlers.
+  ScopedVector<FileSystemHandler> file_handlers_;
+
+  // For generating unique slot.
+  nacl_irt_random random_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExternalFileWrapperHandler);
+};
+
+class ExternalFileHandlerBase : public PepperFileHandler {
+ public:
+  explicit ExternalFileHandlerBase(const char* classname);
+  // |ppapi_file_path| and |virtual_file_path| can be both file and directory.
+  virtual ~ExternalFileHandlerBase();
+
+  virtual std::string SetPepperFileSystem(
+      const pp::FileSystem* file_system,
+      const std::string& path_in_pepperfs,
+      const std::string& path_in_vfs) OVERRIDE;
+
+  // Overridden from PepperFileHandler.
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(int unused_fd,
+                                         const std::string& pathname,
+                                         int oflag, mode_t cmode) OVERRIDE;
+  virtual int remove(const std::string& pathname) OVERRIDE;
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE;
+  virtual int rmdir(const std::string& pathname) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual int truncate(const std::string& pathname, off64_t length) OVERRIDE;
+  virtual int unlink(const std::string& pathname) OVERRIDE;
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]) OVERRIDE;
+  virtual void OnMounted(const std::string& path) OVERRIDE;
+  virtual void OnUnmounted(const std::string& path) OVERRIDE;
+
+ protected:
+  void SetMountPointInVFS(const std::string& path);
+
+ private:
+  friend class TestableExternalFileHandler;
+
+  // Returns external PPAPI file path correponding to |file_path|.
+  std::string GetExternalPPAPIPath(const std::string& file_path) const;
+
+  // The file path in PPAPI file path.
+  std::string ppapi_file_path_;
+
+  // The file path in VFS.
+  std::string virtual_file_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExternalFileHandlerBase);
+};
+
+// This class provides external file handling.
+// The given external file will be shown in |virtual_file_path| on virtual file
+// system.
+class ExternalFileHandler : public ExternalFileHandlerBase {
+ public:
+  ExternalFileHandler(const pp::FileSystem* file_system,
+                      const std::string& ppapi_file_path,
+                      const std::string& virtual_file_path);
+  virtual ~ExternalFileHandler();
+
+  virtual scoped_refptr<FileStream> open(int unused_fd,
+                                         const std::string& pathname,
+                                         int oflag, mode_t cmode) OVERRIDE;
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ExternalFileHandler);
+};
+
+// This class provides external directory handling.
+// The given external directory will be shown in |virtual_file_path| on virtual
+// file system. External directory handler can be "pending". The pending state
+// means that any specific pepper filesystem is not attached. Once the pending
+// external directory handler is initialized, call Observer::OnInitializing and
+// block until the filesystem is attached with SetExternalDirectory.
+class ARC_EXPORT ExternalDirectoryHandler : public ExternalFileHandlerBase {
+ public:
+  // An obeser class for external directory handler. By passing this instance
+  // to ExternalDirectoryHandler ctor, OnInitializing is called just
+  // before PepperFileHandler::Initialize function call. This observer can be
+  // used on-demand initialization of ExternalDirectoryHandler.
+  class Observer {
+   public:
+    virtual ~Observer() {}
+
+    // Calles just before the PepperFileHandler::Initialize call.
+    virtual void OnInitializing() = 0;
+  };
+
+  // Creates pending external directory handler. If this handler is initialized,
+  // |observer| will be called and block until filesystem will be ready. This
+  // class takes the ownership of |observer|.
+  ExternalDirectoryHandler(const std::string& virtual_file_path,
+                                 Observer* observer);
+
+  virtual ~ExternalDirectoryHandler();
+
+  virtual void Initialize() OVERRIDE;
+
+ private:
+  scoped_ptr<Observer> observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExternalDirectoryHandler);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_EXTERNAL_FILE_H_
diff --git a/src/posix_translation/external_file_test.cc b/src/posix_translation/external_file_test.cc
new file mode 100644
index 0000000..fcd6fed
--- /dev/null
+++ b/src/posix_translation/external_file_test.cc
@@ -0,0 +1,660 @@
+// Copyright (c) 2014 The Chromium OS 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 "posix_translation/external_file.h"
+
+#include <algorithm>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "gtest/gtest.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "posix_translation/test_util/mock_virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+const char kExternalFilesDir[] = "/a/bb/ccc/dddd";
+const char kExternalDirPath[] = "/aa/bb/ccc/";
+
+// Puts all directory entries in |stream| into |out|. |stream| must be a
+// directory stream. |out| is sorted and does not contain "." and "..".
+void ReadDirectoryEntries(scoped_refptr<FileStream> stream,
+                          std::vector<std::string>* out) {
+  const int kSize = 1024;
+  out->clear();
+  int read_len = 0;
+  uint8_t buffer[kSize];
+  do {
+    read_len = stream->getdents(reinterpret_cast<dirent*>(buffer), kSize);
+    for (int i = 0; i < read_len;) {
+      struct dirent* p = reinterpret_cast<struct dirent*>(buffer + i);
+      std::string file = p->d_name;
+      if (file != "." && file != "..")  // Skip "." and ".."
+        out->push_back(p->d_name);
+      i += p->d_reclen;
+    }
+  } while (read_len > 0);
+  std::sort(out->begin(), out->end());
+}
+
+}  // namespace
+
+typedef FileSystemTestCommon ExternalFileTest;
+
+class TestableExternalFileHandler : public ExternalFileHandler {
+ public:
+  TestableExternalFileHandler(const pp::FileSystem* file_system,
+                              const std::string& ppapi_file_path,
+                              const std::string& virtual_file_path)
+      : ExternalFileHandler(file_system, ppapi_file_path,
+                                  virtual_file_path) {}
+  using ExternalFileHandler::GetExternalPPAPIPath;
+};
+
+// A mock observer for ExternalDirectoryHandler.
+class MockObserver : public ExternalDirectoryHandler::Observer {
+ public:
+  MockObserver() : on_initializing_call_count_(0), handler_(NULL) {}
+  virtual ~MockObserver() {}
+
+  virtual void OnInitializing() OVERRIDE {
+    on_initializing_call_count_++;
+    if (handler_) {
+      base::AutoUnlock unlock(
+          VirtualFileSystem::GetVirtualFileSystem()->mutex());
+      handler_->SetPepperFileSystem(
+          new pp::FileSystem(), "/Documents", kExternalDirPath);
+    }
+  }
+
+  // Caller must free |handler|.
+  void SetHandler(ExternalDirectoryHandler* handler) {
+    handler_ = handler;
+  }
+
+  int on_initializing_call_count() const { return on_initializing_call_count_; }
+
+ private:
+  int on_initializing_call_count_;
+  ExternalDirectoryHandler* handler_;
+};
+
+class ExternalDirectoryTest
+    : public FileSystemBackgroundTestCommon<ExternalDirectoryTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(ConstructDestructTest);
+  DECLARE_BACKGROUND_TEST(InitializeTest);
+};
+
+TEST_F(ExternalFileTest, TestConstructDestruct) {
+  // ExternalFileTest ctor tries to acquire the mutex.
+  base::AutoUnlock unlock(file_system_->mutex());
+
+  scoped_ptr<ExternalFileHandler> handler;
+  handler.reset(new TestableExternalFileHandler(
+      new pp::FileSystem(),
+      "/some_file.txt",
+      "/some/path/in/vfs/file.txt"));
+  handler.reset();
+}
+
+TEST_F(ExternalFileTest, GetExternalPPAPIPath) {
+  // "\xEF\xBF\xBD": U+FFFE(REPLACEMENT CHARACTER)
+  // "\xF0\xA0\x80\x8B": U+2000B(surrogate pair)
+  // "\xE2\x80\x8F": U+200F(RIGHT-TO-LEFT MARK)
+  // "\xEF\xBC\x8F": U+FF0F(FULLWIDTH SOLIDUS)
+  const std::string kDangerousUnicodes =
+      "\xEF\xBF\xBD\xF0\xA0\x80\x8B\xE2\x80\x8F\xEF\xBC\x8F";
+
+  // ExternalFileTest ctor tries to acquire the mutex.
+  base::AutoUnlock unlock(file_system_->mutex());
+  {
+    SCOPED_TRACE("External path is regular file.");
+    TestableExternalFileHandler handler(
+        new pp::FileSystem(),
+        "/regular.txt",
+        "/vendor/chromium/.external/1/regular.txt");
+
+    EXPECT_EQ("/regular.txt",
+              handler.GetExternalPPAPIPath(
+                  "/vendor/chromium/.external/1/regular.txt"));
+  }
+  {
+    SCOPED_TRACE("External path is directory");
+    TestableExternalFileHandler handler(new pp::FileSystem(),
+                                        "/directory/",
+                                        "/sdcard/external/");
+    EXPECT_EQ("/directory/",
+              handler.GetExternalPPAPIPath("/sdcard/external/"));
+    EXPECT_EQ("/directory/",
+              handler.GetExternalPPAPIPath("/sdcard/external"));
+    EXPECT_EQ("/directory/regular.txt",
+              handler.GetExternalPPAPIPath("/sdcard/external/regular.txt"));
+    EXPECT_EQ("/directory/sub/regular.txt",
+              handler.GetExternalPPAPIPath("/sdcard/external/sub/regular.txt"));
+    EXPECT_EQ("/directory/" + kDangerousUnicodes + "/regular.txt",
+              handler.GetExternalPPAPIPath(
+                  "/sdcard/external/" + kDangerousUnicodes + "/regular.txt"));
+  }
+  {
+    SCOPED_TRACE("External path is root.");
+    TestableExternalFileHandler handler(new pp::FileSystem(), "/",
+                                        "/sdcard/external/");
+
+    EXPECT_EQ("/",
+              handler.GetExternalPPAPIPath("/sdcard/external/"));
+    EXPECT_EQ("/",
+              handler.GetExternalPPAPIPath("/sdcard/external"));
+    EXPECT_EQ("/regular.txt",
+              handler.GetExternalPPAPIPath("/sdcard/external/regular.txt"));
+    EXPECT_EQ("/sub/regular.txt",
+              handler.GetExternalPPAPIPath("/sdcard/external/sub/regular.txt"));
+    EXPECT_EQ("/" + kDangerousUnicodes + "/regular.txt",
+              handler.GetExternalPPAPIPath(
+                  "/sdcard/external/" + kDangerousUnicodes + "/regular.txt"));
+  }
+  {
+    SCOPED_TRACE("External path has unicode characters.");
+    TestableExternalFileHandler handler(new pp::FileSystem(),
+                                        "/" + kDangerousUnicodes + "/",
+                                        "/sdcard/external/");
+    EXPECT_EQ("/" + kDangerousUnicodes + "/",
+              handler.GetExternalPPAPIPath("/sdcard/external/"));
+    EXPECT_EQ("/" + kDangerousUnicodes + "/",
+              handler.GetExternalPPAPIPath("/sdcard/external"));
+    EXPECT_EQ("/" + kDangerousUnicodes + "/regular.txt",
+              handler.GetExternalPPAPIPath("/sdcard/external/regular.txt"));
+    EXPECT_EQ("/" + kDangerousUnicodes + "/sub/regular.txt",
+              handler.GetExternalPPAPIPath(
+                  "/sdcard/external/sub/regular.txt"));
+  }
+}
+
+TEST_BACKGROUND_F(ExternalDirectoryTest, ConstructDestructTest) {
+  scoped_ptr<ExternalDirectoryHandler> handler;
+  MockObserver* observer = new MockObserver();
+  handler.reset(new ExternalDirectoryHandler(kExternalDirPath, observer));
+
+  EXPECT_EQ(0, observer->on_initializing_call_count());
+  handler.reset();
+}
+
+TEST_BACKGROUND_F(ExternalDirectoryTest, InitializeTest) {
+  scoped_ptr<ExternalDirectoryHandler> handler;
+  MockObserver* observer = new MockObserver();
+  handler.reset(new ExternalDirectoryHandler(kExternalDirPath, observer));
+  observer->SetHandler(handler.get());
+
+  EXPECT_EQ(0, observer->on_initializing_call_count());
+  base::AutoLock lock(file_system_->mutex());
+  handler->Initialize();
+  EXPECT_EQ(1, observer->on_initializing_call_count());
+  handler.reset();
+}
+
+class TestableExternalFileWrapperHandler : public ExternalFileWrapperHandler {
+ public:
+  TestableExternalFileWrapperHandler() : ExternalFileWrapperHandler() {}
+
+  using ExternalFileWrapperHandler::GetSlot;
+  using ExternalFileWrapperHandler::GenerateUniqueSlotLocked;
+  using ExternalFileWrapperHandler::file_handlers_;
+  using ExternalFileWrapperHandler::slot_file_map_;
+
+  struct MountInfo {
+    MountInfo(const std::string& ext, const std::string& vfs)
+        : path_in_external_fs(ext), path_in_vfs(vfs) {}
+    std::string path_in_external_fs;
+    std::string path_in_vfs;
+  };
+
+  virtual scoped_ptr<FileSystemHandler> MountExternalFile(
+      const pp::FileSystem* file_system,
+      const std::string& path_in_external_fs,
+      const std::string& path_in_vfs) OVERRIDE {
+    EXPECT_TRUE(file_system);
+    delete file_system;
+    mounts_.push_back(MountInfo(path_in_external_fs, path_in_vfs));
+    return scoped_ptr<FileSystemHandler>();
+  }
+
+  const std::vector<MountInfo>& mounts() {
+    return mounts_;
+  }
+
+ private:
+  std::vector<MountInfo> mounts_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestableExternalFileWrapperHandler);
+};
+
+// ExternalFileWrapperTest must be usable on main thread, so do not test with
+// BACKGROUND_TEST_F.
+class ExternalFileWrapperTest
+    : public FileSystemBackgroundTestCommon<ExternalFileWrapperTest> {
+ public:
+  virtual void SetUp() {
+    FileSystemTestCommon::SetUp();
+    handler_.reset(new TestableExternalFileWrapperHandler());
+    handler_->OnMounted(std::string(kExternalFilesDir) + "/");
+  }
+
+  virtual void TearDown() {
+    handler_->OnUnmounted(std::string(kExternalFilesDir) + "/");
+    handler_.reset();
+    FileSystemTestCommon::TearDown();
+  }
+
+ protected:
+  scoped_ptr<TestableExternalFileWrapperHandler> handler_;
+};
+
+TEST_F(ExternalFileWrapperTest, ConstructDestructTest) {
+  scoped_ptr<ExternalFileWrapperHandler> handler;
+  handler.reset(new ExternalFileWrapperHandler());
+  handler.reset();
+}
+
+TEST_F(ExternalFileWrapperTest, Mount_EmptyMountPoint) {
+  const char kPathInExtFs[] = "/foo.txt";
+  std::string mount_point =
+      handler_->SetPepperFileSystem(
+          new pp::FileSystem(), kPathInExtFs,
+          std::string() /* assign new directory */);
+  EXPECT_FALSE(mount_point.empty());
+  EXPECT_TRUE(StartsWithASCII(mount_point, kExternalFilesDir, true));
+  EXPECT_TRUE(EndsWith(mount_point, kPathInExtFs, true));
+
+  ASSERT_EQ(1U, handler_->mounts().size());
+  EXPECT_EQ(mount_point, handler_->mounts()[0].path_in_vfs);
+  EXPECT_EQ(kPathInExtFs, handler_->mounts()[0].path_in_external_fs);
+
+  ASSERT_EQ(1U, handler_->slot_file_map_.size());
+  EXPECT_TRUE(
+      StartsWithASCII(handler_->slot_file_map_.begin()->first, "/", true));
+  EXPECT_EQ(std::string::npos,
+            handler_->slot_file_map_.begin()->first.find('/', 1));
+  EXPECT_EQ(kPathInExtFs,
+            handler_->slot_file_map_.begin()->second);
+
+  ASSERT_EQ(1U, handler_->file_handlers_.size());
+}
+
+TEST_F(ExternalFileWrapperTest, Mount_WithMountPoint) {
+  const char kPathInExtFs[] = "/foo.txt";
+  const char kMountPosition[] = "/a/bb/ccc/dddd/0ABE8364802/foo.txt";
+  std::string mount_point = handler_->SetPepperFileSystem(
+      new pp::FileSystem(), kPathInExtFs, kMountPosition);
+
+  EXPECT_EQ(kMountPosition, mount_point);
+  EXPECT_TRUE(StartsWithASCII(mount_point, kExternalFilesDir, true));
+  EXPECT_TRUE(EndsWith(mount_point, kPathInExtFs, true));
+
+  ASSERT_EQ(1U, handler_->mounts().size());
+  EXPECT_EQ(mount_point, handler_->mounts()[0].path_in_vfs);
+  EXPECT_EQ(kPathInExtFs, handler_->mounts()[0].path_in_external_fs);
+
+  ASSERT_EQ(1U, handler_->slot_file_map_.size());
+  EXPECT_TRUE(
+      StartsWithASCII(handler_->slot_file_map_.begin()->first, "/", true));
+  EXPECT_EQ(std::string::npos,
+            handler_->slot_file_map_.begin()->first.find('/', 1));
+  EXPECT_EQ(kPathInExtFs,
+            handler_->slot_file_map_.begin()->second);
+
+  ASSERT_EQ(1U, handler_->file_handlers_.size());
+}
+
+TEST_F(ExternalFileWrapperTest, GetSlotTest) {
+  static const struct TestData {
+    const char* input;
+    const char* expected_slot;
+  } kTestData[] = {
+    // Success cases
+    { "/a/bb/ccc/dddd/ABE8364802", "/ABE8364802" },
+    { "/a/bb/ccc/dddd/938493948", "/938493948" },
+
+    // Fail cases
+    { "/a/bb/ccc/dddd/ABE8364802/", "" },
+    { "/a/bb/ccc/dddd/ABE8/364802", "" },
+    { "/a/bb/ccc/dddd/ABE8/364802/", "" },
+    { "/a/bb/ccc/dddd/ABE8364802/foo/", "" },
+    { "/a/bb/ccc/dddd/ABE8364802/foo.txt", "" },
+    { "/a/bb/ccc/dddd", "" },
+    { "/a/bb/ccc/dddd/", "" },
+    { "/a/bb/ccc/dddd//", "" },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestData) ; ++i) {
+    EXPECT_EQ(kTestData[i].expected_slot,
+              handler_->GetSlot(kTestData[i].input))
+        << kTestData[i].input;
+  }
+}
+
+TEST_F(ExternalFileWrapperTest, GenerateUniqueSlot) {
+  base::AutoLock lock(file_system_->mutex());
+  const size_t kCount = 1000U;
+  std::set<std::string> set_for_unique_check;
+  for (size_t i = 0U; i < kCount; ++i) {
+    set_for_unique_check.insert(handler_->GenerateUniqueSlotLocked());
+  }
+  EXPECT_EQ(kCount, set_for_unique_check.size());
+}
+
+TEST_F(ExternalFileWrapperTest, mkdir) {
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+
+  // root directory
+  errno = 0;
+  EXPECT_EQ(-1, handler_->mkdir(ext_files_dir, 0755));
+  EXPECT_EQ(EEXIST, errno);
+
+  const std::string path_in_extfs = "/foo.txt";
+
+  for (int i = 0; i < 10; ++i) {
+    std::string slot = base::StringPrintf("/%X", i);
+
+    // before calling Mount, NOENT.
+    errno = 0;
+    EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + slot, 0755));
+    EXPECT_EQ(EPERM, errno);
+
+    std::string mount_point = handler_->SetPepperFileSystem(
+        new pp::FileSystem(), path_in_extfs,
+        kExternalFilesDir + slot + path_in_extfs);
+
+    // newly mounted slot directory.
+    errno = 0;
+    EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + slot, 0755));
+    EXPECT_EQ(EEXIST, errno);
+
+    // previously mounted slots must be able to open.
+    for (int j = 0; j < i; ++j) {
+      std::string old_slot = base::StringPrintf("/%X", i);
+      errno = 0;
+      EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + old_slot, 0755));
+      EXPECT_EQ(EEXIST, errno);
+    }
+  }
+
+  // unknown slot
+  errno = 0;
+  EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + "/ABCDEF", 0755));
+  EXPECT_EQ(EPERM, errno);
+
+  // every path in correct slot.
+  errno = 0;
+  EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + "/0/foo", 0755));
+  EXPECT_EQ(EPERM, errno);
+
+  errno = 0;
+  EXPECT_EQ(-1, handler_->mkdir(ext_files_dir + "/0/bar", 0755));
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_F(ExternalFileWrapperTest, open) {
+  base::AutoLock lock(file_system_->mutex());
+
+  const int kUnusedFd = 10;
+  const char kDirectoryStreamType[] = "external_file_dir";
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+  scoped_refptr<FileStream> stream;
+
+  // root directory
+  errno = 0;
+  stream = handler_->open(kUnusedFd, ext_files_dir, 0, 0644);
+  ASSERT_TRUE(stream.get());
+  EXPECT_EQ(kDirectoryStreamType, std::string(stream->GetStreamType()));
+  EXPECT_EQ(0, errno);
+
+  const std::string path_in_extfs = "/foo.txt";
+
+  for (int i = 0; i < 10; ++i) {
+    std::string slot = base::StringPrintf("/%X", i);
+
+    // before calling Mount, NOENT.
+    errno = 0;
+    stream = handler_->open(kUnusedFd, ext_files_dir + slot, 0, 0644);
+    ASSERT_FALSE(stream.get());
+    EXPECT_EQ(ENOENT, errno);
+
+    {
+      base::AutoUnlock unlock(file_system_->mutex());
+      std::string mount_point = handler_->SetPepperFileSystem(
+          new pp::FileSystem(), path_in_extfs,
+          kExternalFilesDir + slot + path_in_extfs);
+    }
+
+    // newly mounted slot directory.
+    errno = 0;
+    stream = handler_->open(kUnusedFd, ext_files_dir + slot, 0, 0644);
+    ASSERT_TRUE(stream.get());
+    EXPECT_EQ(kDirectoryStreamType, std::string(stream->GetStreamType()));
+    EXPECT_EQ(0, errno);
+
+    // previously mounted slots must be able to open.
+    for (int j = 0; j < i; ++j) {
+      std::string old_slot = base::StringPrintf("/%X", i);
+      errno = 0;
+      stream = handler_->open(kUnusedFd, ext_files_dir + old_slot, 0, 0644);
+      ASSERT_TRUE(stream.get());
+      EXPECT_EQ(kDirectoryStreamType, std::string(stream->GetStreamType()));
+      EXPECT_EQ(0, errno);
+    }
+  }
+
+  // unknown slot
+  errno = 0;
+  stream = handler_->open(kUnusedFd, ext_files_dir + "/12345", 0, 0644);
+  ASSERT_FALSE(stream.get());
+  EXPECT_EQ(ENOENT, errno);
+
+  // every path in correct slot.
+  errno = 0;
+  stream = handler_->open(kUnusedFd,
+                          ext_files_dir + "/0/foo.txt", 0, 0644);
+  ASSERT_FALSE(stream.get());
+  EXPECT_EQ(ENOENT, errno);
+  errno = 0;
+  stream = handler_->open(kUnusedFd,
+                          ext_files_dir + "/0/bar.txt", 0, 0644);
+  ASSERT_FALSE(stream.get());
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ExternalFileWrapperTest, stat) {
+  base::AutoLock lock(file_system_->mutex());
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+
+  struct stat st = {};
+  // root directory
+  errno = 0;
+  EXPECT_EQ(0, handler_->stat(ext_files_dir, &st));
+  EXPECT_EQ(0, errno);
+  EXPECT_TRUE(st.st_mode & S_IFDIR);
+
+  const std::string path_in_extfs = "/foo.txt";
+
+  for (int i = 0; i < 10; ++i) {
+    std::string slot = base::StringPrintf("/%X", i);
+
+    // before calling Mount, NOENT.
+    errno = 0;
+    EXPECT_EQ(-1, handler_->stat(ext_files_dir + slot, &st));
+    EXPECT_EQ(ENOENT, errno);
+
+    {
+      base::AutoUnlock unlock(file_system_->mutex());
+      std::string mount_point = handler_->SetPepperFileSystem(
+          new pp::FileSystem(), path_in_extfs,
+          kExternalFilesDir + slot + path_in_extfs);
+    }
+
+    // newly mounted slot directory.
+    errno = 0;
+    st.st_mode = 0;
+    EXPECT_EQ(0, handler_->stat(ext_files_dir + slot, &st));
+    EXPECT_EQ(0, errno);
+    EXPECT_TRUE(st.st_mode & S_IFDIR);
+
+    // previously mounted slots must be able to open.
+    for (int j = 0; j < i; ++j) {
+      std::string old_slot = base::StringPrintf("/%X", i);
+      errno = 0;
+      st.st_mode = 0;
+      EXPECT_EQ(0, handler_->stat(ext_files_dir + old_slot, &st));
+      EXPECT_EQ(0, errno);
+      EXPECT_TRUE(st.st_mode & S_IFDIR);
+    }
+  }
+
+  // unknown slot
+  errno = 0;
+  EXPECT_EQ(-1, handler_->stat(ext_files_dir + "/ABCDEF", &st));
+  EXPECT_EQ(ENOENT, errno);
+
+  // every path in correct slot.
+  errno = 0;
+  EXPECT_EQ(-1, handler_->stat(ext_files_dir + "/0/foo", &st));
+  EXPECT_EQ(ENOENT, errno);
+
+  errno = 0;
+  EXPECT_EQ(-1, handler_->stat(ext_files_dir + "/0/bar", &st));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ExternalFileWrapperTest, statfs) {
+  base::AutoLock lock(file_system_->mutex());
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+
+  struct statfs stfs = {};
+  // root directory
+  errno = 0;
+  EXPECT_EQ(0, handler_->statfs(ext_files_dir, &stfs));
+  EXPECT_EQ(0, errno);
+
+  const std::string path_in_extfs = "/foo.txt";
+
+  for (int i = 0; i < 10; ++i) {
+    std::string slot = base::StringPrintf("/%X", i);
+
+    // before calling Mount, NOENT.
+    errno = 0;
+    EXPECT_EQ(-1, handler_->statfs(ext_files_dir + slot, &stfs));
+    EXPECT_EQ(ENOENT, errno);
+
+    {
+      base::AutoUnlock unlock(file_system_->mutex());
+      std::string mount_point = handler_->SetPepperFileSystem(
+          new pp::FileSystem(), path_in_extfs,
+          kExternalFilesDir + slot + path_in_extfs);
+    }
+
+    // newly mounted slot directory.
+    errno = 0;
+    EXPECT_EQ(0, handler_->statfs(ext_files_dir + slot, &stfs));
+    EXPECT_EQ(0, errno);
+
+    // previously mounted slots must be able to open.
+    for (int j = 0; j < i; ++j) {
+      std::string old_slot = base::StringPrintf("/%X", i);
+      errno = 0;
+      EXPECT_EQ(0, handler_->statfs(ext_files_dir + old_slot, &stfs));
+      EXPECT_EQ(0, errno);
+    }
+  }
+
+  // unknown slot
+  errno = 0;
+  EXPECT_EQ(-1, handler_->statfs(ext_files_dir + "/ABCDEF", &stfs));
+  EXPECT_EQ(ENOENT, errno);
+
+  // every path in correct slot.
+  errno = 0;
+  EXPECT_EQ(-1, handler_->statfs(ext_files_dir + "/0/foo", &stfs));
+  EXPECT_EQ(ENOENT, errno);
+
+  errno = 0;
+  EXPECT_EQ(-1, handler_->statfs(ext_files_dir + "/0/bar", &stfs));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ExternalFileWrapperTest, getdents_root) {
+  base::AutoLock lock(file_system_->mutex());
+
+  const int kUnusedFd = 10;
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+  const std::string path_in_extfs = "/foo.txt";
+  scoped_refptr<FileStream> stream;
+  std::vector<std::string> dir_entries;
+
+  for (size_t i = 0; i < 10; ++i) {
+    std::string slot = base::StringPrintf("/%X", i);
+
+    errno = 0;
+    stream = handler_->open(kUnusedFd, ext_files_dir, 0, 0644);
+    ASSERT_TRUE(stream.get());
+    EXPECT_EQ(0, errno);
+    ReadDirectoryEntries(stream, &dir_entries);
+    EXPECT_EQ(i, dir_entries.size());
+    for (size_t j = 0; j < i; ++j) {
+      EXPECT_EQ(base::StringPrintf("%X", j), dir_entries[j]);
+    }
+
+    {
+      base::AutoUnlock unlock(file_system_->mutex());
+      std::string mount_point = handler_->SetPepperFileSystem(
+          new pp::FileSystem(), path_in_extfs,
+          kExternalFilesDir + slot + path_in_extfs);
+    }
+
+    errno = 0;
+    stream = handler_->open(kUnusedFd, ext_files_dir, 0, 0644);
+    ASSERT_TRUE(stream.get());
+    EXPECT_EQ(0, errno);
+    ReadDirectoryEntries(stream, &dir_entries);
+    EXPECT_EQ(i + 1, dir_entries.size());
+    for (size_t j = 0; j < i + 1; ++j) {
+      EXPECT_EQ(base::StringPrintf("%X", j), dir_entries[j]);
+    }
+  }
+}
+
+TEST_F(ExternalFileWrapperTest, getdents_slot) {
+  base::AutoLock lock(file_system_->mutex());
+
+  const int kUnusedFd = 10;
+  const std::string ext_files_dir = kExternalFilesDir;  // for easy appending.
+  const std::string path_in_extfs = "/foo.txt";
+  const std::string slot = "/987923847";
+  scoped_refptr<FileStream> stream;
+  std::vector<std::string> dir_entries;
+
+  {
+    base::AutoUnlock unlock(file_system_->mutex());
+    std::string mount_point = handler_->SetPepperFileSystem(
+        new pp::FileSystem(), path_in_extfs,
+        kExternalFilesDir + slot + path_in_extfs);
+  }
+
+  errno = 0;
+  stream = handler_->open(kUnusedFd, ext_files_dir + slot, 0, 0644);
+  ASSERT_TRUE(stream.get());
+  EXPECT_EQ(0, errno);
+  ReadDirectoryEntries(stream, &dir_entries);
+  EXPECT_EQ(1U, dir_entries.size());
+  EXPECT_EQ("foo.txt", dir_entries[0]);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/fd_to_file_stream_map.cc b/src/posix_translation/fd_to_file_stream_map.cc
new file mode 100644
index 0000000..e0994a7
--- /dev/null
+++ b/src/posix_translation/fd_to_file_stream_map.cc
@@ -0,0 +1,117 @@
+// Copyright 2014 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 "posix_translation/fd_to_file_stream_map.h"
+
+#include <algorithm>  // for heap
+#include <utility>
+
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "posix_translation/file_stream.h"
+#include "ppapi/cpp/module.h"
+
+namespace posix_translation {
+
+FdToFileStreamMap::FdToFileStreamMap(int min_file_id, int max_file_id)
+    : min_file_id_(min_file_id), max_file_id_(max_file_id) {
+  ALOG_ASSERT(max_file_id_ >= min_file_id_);
+  unused_fds_.reserve(max_file_id - min_file_id_ + 1);
+  for (int fd = min_file_id_; fd <= max_file_id_; ++fd) {
+    unused_fds_.push_back(fd);
+  }
+  std::make_heap(unused_fds_.begin(), unused_fds_.end(), cmp_);
+}
+
+FdToFileStreamMap::~FdToFileStreamMap() {
+  for (FileStreamMap::const_iterator it = streams_.begin();
+       it != streams_.end();
+       ++it) {
+    if (it->second)
+      it->second->ReleaseFileRef();
+  }
+}
+
+void FdToFileStreamMap::AddFileStream(
+    int fd, scoped_refptr<FileStream> stream) {
+  if (stream)
+    stream->AddFileRef();
+  std::pair<FileStreamMap::iterator, bool> p =
+      streams_.insert(std::make_pair(fd, stream));
+  FileStreamMap::iterator it = p.first;
+  if (p.second) {
+    // Slow path. The |fd| is not the one claimed by GetFirstUnusedDescriptor().
+    std::vector<int>::iterator remove_it =
+        std::remove(unused_fds_.begin(), unused_fds_.end(), fd);
+    unused_fds_.erase(remove_it, unused_fds_.end());
+    std::make_heap(unused_fds_.begin(), unused_fds_.end(), cmp_);
+  } else {
+    ALOG_ASSERT(!it->second, "fd=%d", fd);
+    it->second = stream;
+  }
+}
+
+void FdToFileStreamMap::ReplaceFileStream(
+    int fd, scoped_refptr<FileStream> stream) {
+  ALOG_ASSERT(streams_.find(fd) != streams_.end() && streams_.find(fd)->second);
+  scoped_refptr<FileStream> old_stream = streams_[fd];
+  if (stream != old_stream) {
+    streams_[fd] = stream;
+    stream->AddFileRef();
+    old_stream->ReleaseFileRef();
+  }
+}
+
+void FdToFileStreamMap::RemoveFileStream(int fd) {
+  FileStreamMap::iterator iter = streams_.find(fd);
+  ALOG_ASSERT(iter != streams_.end());
+
+  // OnLastFileRef() of the stream could call Wait(), which unlocks the mutex.
+  // During the unlocked period, if other thread tries to access the stream
+  // via this fd map, it'll cause a problem of accessing already closed stream,
+  // which is asserted in FileStream. So, we remove the stream from the map
+  // first.
+  scoped_refptr<FileStream> old_stream(iter->second);
+  streams_.erase(iter);
+  unused_fds_.push_back(fd);
+  std::push_heap(unused_fds_.begin(), unused_fds_.end(), cmp_);
+  if (old_stream)
+    old_stream->ReleaseFileRef();
+}
+
+int FdToFileStreamMap::GetFirstUnusedDescriptor() {
+  int fd = unused_fds_.empty() ? -1 : unused_fds_.front();
+  if (fd != -1) {
+    std::pop_heap(unused_fds_.begin(), unused_fds_.end(), cmp_);
+    unused_fds_.pop_back();
+    AddFileStream(fd, NULL);  // mark as used.
+  } else {
+    ALOGW("All %d file descriptors in use, cannot allocate a new one.",
+          max_file_id_ - min_file_id_ + 1);
+  }
+  return fd;
+}
+
+bool FdToFileStreamMap::IsKnownDescriptor(int fd) {
+  return streams_.find(fd) != streams_.end();
+}
+
+scoped_refptr<FileStream> FdToFileStreamMap::GetStream(int fd) {
+  FileStreamMap::const_iterator it = streams_.find(fd);
+  scoped_refptr<FileStream> stream = it != streams_.end() ? it->second : NULL;
+
+  if (stream) {
+    stream->CheckNotClosed();
+    ALOG_ASSERT(stream->IsAllowedOnMainThread() ||
+                !pp::Module::Get()->core()->IsMainThread());
+
+    // Call REPORT_HANDLER() so that the current function call is categrized as
+    // |stream->GetStreamType()| rather than |kVirtualFileSystemHandlerStr|.
+    ARC_STRACE_REPORT_HANDLER(stream->GetStreamType());
+  }
+
+  return stream;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/fd_to_file_stream_map.h b/src/posix_translation/fd_to_file_stream_map.h
new file mode 100644
index 0000000..3ec31c8
--- /dev/null
+++ b/src/posix_translation/fd_to_file_stream_map.h
@@ -0,0 +1,57 @@
+// Copyright 2014 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.
+//
+// A map from file descriptor to FileStream object.
+
+#ifndef POSIX_TRANSLATION_FD_TO_FILE_STREAM_MAP_H_
+#define POSIX_TRANSLATION_FD_TO_FILE_STREAM_MAP_H_
+
+#include <sys/select.h>
+
+#include <functional>
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+
+namespace posix_translation {
+
+class FileStream;
+
+class FdToFileStreamMap {
+ public:
+  FdToFileStreamMap(int min_file_id, int max_file_id);
+  ~FdToFileStreamMap();
+
+  int GetFirstUnusedDescriptor();
+  void AddFileStream(int fd, scoped_refptr<FileStream> stream);
+  void ReplaceFileStream(int fd, scoped_refptr<FileStream> stream);
+  void RemoveFileStream(int fd);
+  bool IsKnownDescriptor(int fd);
+  scoped_refptr<FileStream> GetStream(int fd);
+
+ protected:
+  friend class VirtualFileSystem;
+
+ private:
+  // File streams that have assigned file descriptors. For allocated file
+  // descriptors without a stream (when stream is in a process of being created
+  // or assigned) the value will be NULL.
+  typedef std::map<int, scoped_refptr<FileStream> > FileStreamMap;
+  FileStreamMap streams_;
+  std::vector<int> unused_fds_;  // min-heap.
+  std::greater<int> cmp_;  // to use |unused_fds_| as a min-heap.
+
+  // The minimum/maximum fd number allowed.
+  const int min_file_id_;
+  const int max_file_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(FdToFileStreamMap);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_FD_TO_FILE_STREAM_MAP_H_
diff --git a/src/posix_translation/fd_to_file_stream_map_test.cc b/src/posix_translation/fd_to_file_stream_map_test.cc
new file mode 100644
index 0000000..dcaeb2c
--- /dev/null
+++ b/src/posix_translation/fd_to_file_stream_map_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 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 "base/compiler_specific.h"
+#include "gtest/gtest.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+
+namespace posix_translation {
+
+class FdToFileStreamMapTest
+    : public FileSystemBackgroundTestCommon<FdToFileStreamMapTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(TestGetStream);
+  DECLARE_BACKGROUND_TEST(TestReplaceStream);
+};
+
+namespace {
+
+class StubFileStream : public FileStream {
+ public:
+  StubFileStream() : FileStream(0, ""), allow_on_main_thread_(false) {
+  }
+
+  virtual ssize_t read(void*, size_t) OVERRIDE { return -1; }
+  virtual ssize_t write(const void*, size_t) OVERRIDE { return -1; }
+  virtual const char* GetStreamType() const OVERRIDE { return "stub"; }
+  virtual bool IsAllowedOnMainThread() const OVERRIDE {
+    return allow_on_main_thread_;
+  }
+
+  // Allows to use this stream on main thread.
+  void AllowOnMainThread() {
+    allow_on_main_thread_ = true;
+  }
+
+ private:
+  bool allow_on_main_thread_;
+};
+
+}  // namespace
+
+TEST_BACKGROUND_F(FdToFileStreamMapTest, TestGetStream) {
+  // TEST_BACKGROUND_F because it is not allowed to call GetStream() on the main
+  // thread by default.
+  int fd = GetFirstUnusedDescriptor();
+  EXPECT_GE(fd, 0);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  scoped_refptr<FileStream> stream = new StubFileStream;
+  AddFileStream(fd, stream);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  EXPECT_EQ(stream, GetStream(fd));
+
+  int fd2 = GetFirstUnusedDescriptor();
+  EXPECT_GE(fd2, 0);
+  EXPECT_NE(fd, fd2);
+  EXPECT_TRUE(IsKnownDescriptor(fd2));
+  scoped_refptr<FileStream> stream2 = new StubFileStream;
+  AddFileStream(fd2, stream2);
+  EXPECT_TRUE(IsKnownDescriptor(fd2));
+  EXPECT_EQ(stream2, GetStream(fd2));
+
+  RemoveFileStream(fd);
+  EXPECT_FALSE(IsKnownDescriptor(fd));
+  EXPECT_EQ(NULL, GetStream(fd).get());
+  EXPECT_EQ(fd, GetFirstUnusedDescriptor());  // |fd| should be reused.
+  AddFileStream(fd, NULL);
+  RemoveFileStream(fd);
+
+  RemoveFileStream(fd2);
+}
+
+TEST_BACKGROUND_F(FdToFileStreamMapTest, TestReplaceStream) {
+  // TEST_BACKGROUND_F because it is not allowed to call GetStream() on the main
+  // thread by default.
+  int fd = GetFirstUnusedDescriptor();
+  EXPECT_GE(fd, 0);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  scoped_refptr<FileStream> stream1 = new StubFileStream;
+  scoped_refptr<FileStream> stream2 = new StubFileStream;
+  AddFileStream(fd, stream1);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  EXPECT_EQ(stream1, GetStream(fd));
+  ReplaceFileStream(fd, stream2);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  EXPECT_EQ(stream2, GetStream(fd));
+  RemoveFileStream(fd);
+  EXPECT_FALSE(IsKnownDescriptor(fd));
+  EXPECT_EQ(NULL, GetStream(fd).get());
+}
+
+TEST_F(FdToFileStreamMapTest, TestGetStreamOnMainThread) {
+  // This test verifies that using file IO on main thread does not abort if the
+  // corresponding stream is allowed to work on it.
+  int fd = GetFirstUnusedDescriptor();
+  EXPECT_GE(fd, 0);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  scoped_refptr<StubFileStream> stream = new StubFileStream;
+
+  // This is is necessary to not make FdToFileStreamMap::GetStream() abort with
+  // '!pp::Module::Get()->core()->IsMainThread()' assertion failure.
+  stream->AllowOnMainThread();
+
+  AddFileStream(fd, stream);
+  EXPECT_TRUE(IsKnownDescriptor(fd));
+  EXPECT_EQ(stream, GetStream(fd));
+  RemoveFileStream(fd);
+}
+
+TEST_F(FdToFileStreamMapTest, TestSetStream) {
+  // Call AddFileStream() with a fd which is NOT returned from
+  // GetFirstUnusedDescriptor().
+  ASSERT_FALSE(IsKnownDescriptor(kMinFdForTesting));
+  AddFileStream(kMinFdForTesting, NULL);
+  EXPECT_TRUE(IsKnownDescriptor(kMinFdForTesting));
+  // The same fd, kMinFdForTesting, should not be returned from
+  // GetFirstUnusedDescriptor().
+  int fd = GetFirstUnusedDescriptor();
+  EXPECT_NE(kMinFdForTesting, fd);
+
+  // Do the same with a bigger fd (42) and non-NULL stream.
+  scoped_refptr<FileStream> stream = new StubFileStream;
+  ASSERT_FALSE(IsKnownDescriptor(42));
+  AddFileStream(42, stream);
+  EXPECT_TRUE(IsKnownDescriptor(42));
+  for (int i = 0; i < 50; ++i) {
+    fd = GetFirstUnusedDescriptor();
+    EXPECT_NE(42, fd);
+  }
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/fence_stream.cc b/src/posix_translation/fence_stream.cc
new file mode 100644
index 0000000..603db8e
--- /dev/null
+++ b/src/posix_translation/fence_stream.cc
@@ -0,0 +1,489 @@
+// Copyright 2014 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.
+//
+// You can find the Linux Kernel implementation at:
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/staging/android/sync.c
+
+#include "posix_translation/fence_stream.h"
+
+#include <linux/sync.h>
+#include <stddef.h>
+#include <string.h>
+#include <time.h>
+
+#include <limits>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/strings/string_util.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "common/arc_strace.h"
+#include "common/process_emulator.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/time_util.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+// This source code contains four types of locks, file system lock,
+// timeline locks, fence stream locks and sync point locks. The hierarchy of
+// these locks is
+//   file system lock > timeline locks > fence stream locks > sync point locks.
+// This ">" means the larger lock will not be newly acquired while the smaller
+// lock is acquired. And any two locks in the same layer will not nest. Here,
+// file system lock only protects the ref count of fence stream, so it is safe
+// to unlock the file system lock if needed only before acquiring other locks.
+//
+// To keep above hierarchy, any FenceStream instance calls Timeline's member
+// methods without FenceStream's lock. On the other hand, any Timelines
+// instance calls FenceStream's member methods with Timeline's lock.
+// Any SyncPoint locks won't violate above hierarchy since SyncPoint lock is
+// private and SyncPoint does not call any Fence or Timeline  member methods.
+
+namespace {
+
+base::Lock& GetFileSystemMutex() {
+  return VirtualFileSystem::GetVirtualFileSystem()->mutex();
+}
+
+// Increments |counter_| during an instance of this class is alive.
+class ScopedCountIncrementer {
+ public:
+  // Caller must free |counter_| after this instance is deleted.
+  explicit ScopedCountIncrementer(uint32_t* counter) : counter_(counter) {
+    ALOG_ASSERT(counter_);
+    ++(*counter_);
+  }
+
+  ~ScopedCountIncrementer() {
+    ALOG_ASSERT(*counter_);
+    --(*counter_);
+  }
+
+ private:
+  uint32_t* counter_;
+};
+
+}  // namespace
+
+Timeline::Timeline() : counter_(0) {
+}
+
+Timeline::~Timeline() {
+}
+
+int Timeline::CreateFence(const std::string& name, uint32_t signaling_time) {
+  scoped_ptr<SyncPoint> sp(new SyncPoint(signaling_time, 0ULL));
+  ScopedVector<FenceStream::SyncPointTimeline> sync_points;
+  sync_points.push_back(new FenceStream::SyncPointTimeline(sp.Pass(), this));
+
+  base::AutoLock vfs_lock(GetFileSystemMutex());
+  base::AutoLock lock(mutex_);
+  const int fd = VirtualFileSystem::GetVirtualFileSystem()->AddFileStreamLocked(
+      FenceStream::CreateFenceTimelineLocked(name, sync_points.Pass()));
+  ALOG_ASSERT(fd >= 0);
+  ARC_STRACE_REGISTER_FD(fd, name.c_str());
+  return fd;
+}
+
+void Timeline::IncrementCounter(uint32_t amount) {
+  base::AutoLock lock(mutex_);
+
+  ALOG_ASSERT(counter_ < (std::numeric_limits<uint32_t>::max() - amount),
+              "Timeline counter overflow.");
+
+  // Find sync points which shall signal in (counter_, counter_+amount].
+  std::multimap<uint32_t, SyncPoint*>::iterator lower =
+      sync_points_.lower_bound(counter_ + 1);
+  std::multimap<uint32_t, SyncPoint*>::iterator upper =
+      sync_points_.upper_bound(counter_ + amount);
+
+  counter_ += amount;
+
+  for (std::multimap<uint32_t, SyncPoint*>::iterator it = lower;
+       it != upper; ++it) {
+    FenceStream* fence = sync_point_fence_[it->second];
+    it->second->MarkAsSignaled();
+    fence->MaybeSignal();
+  }
+}
+
+void Timeline::AttachSyncPoint(FenceStream* fence, SyncPoint* pt) {
+  base::AutoLock lock(mutex_);
+  AttachSyncPointLocked(fence, pt);
+}
+
+void Timeline::AttachSyncPointLocked(FenceStream* fence, SyncPoint* pt) {
+  mutex_.AssertAcquired();
+  sync_points_.insert(std::make_pair(pt->signaling_time(), pt));
+  sync_point_fence_.insert(std::make_pair(pt, fence));
+
+  if (pt->IsSignaled())
+    return;
+
+  if (pt->signaling_time() <= counter_)
+    pt->MarkAsSignaled();
+}
+
+void Timeline::DetachSyncPoint(SyncPoint* pt) {
+  base::AutoLock lock(mutex_);
+  size_t erased_count = sync_point_fence_.erase(pt);
+  ALOG_ASSERT(1U == erased_count);
+  std::pair<std::multimap<uint32_t, SyncPoint*>::iterator,
+      std::multimap<uint32_t, SyncPoint*>::iterator> range =
+          sync_points_.equal_range(pt->signaling_time());
+
+  for (std::multimap<uint32_t, SyncPoint*>::iterator it = range.first;
+       it != range.second; ++it) {
+    if (it->second == pt) {
+      sync_points_.erase(it);
+      // We don't have same syncpoints in a timeline.
+      return;
+    }
+  }
+  ALOG_ASSERT(false, "Releasing not managed sync point.");
+}
+
+SyncPoint::SyncPoint(uint32_t signaling_time, uint64_t timestamp_ns)
+    : timestamp_ns_(timestamp_ns), signaling_time_(signaling_time) {
+}
+
+SyncPoint::~SyncPoint() {
+}
+
+void SyncPoint::MarkAsSignaled() {
+  base::AutoLock lock(mutex_);
+  ALOG_ASSERT(timestamp_ns_ == 0ULL,
+              "The sync point has already been signaled");
+  timespec ts;
+  int result = clock_gettime(CLOCK_MONOTONIC, &ts);
+  ALOG_ASSERT(result == 0, "clock_gettime failed: errno=%d", errno);
+  timestamp_ns_ = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+bool SyncPoint::IsSignaled() {
+  return timestamp_ns_ != 0ULL;
+}
+
+uint32_t SyncPoint::FillSyncPtInfo(sync_pt_info* info, uint32_t length) {
+  base::AutoLock lock(mutex_);
+  if (length < sizeof(sync_pt_info))
+    return 0;
+
+  info->len = sizeof(sync_pt_info);
+
+  // On Linux, the timeline name is the command line name who creates this
+  // timeline. Use "arc" instead here since Chrome v2 app does not have the
+  // concept.
+  base::strlcpy(info->obj_name, "arc", sizeof(info->obj_name));
+
+  // The driver name is the same as the original Linux implementation.
+  base::strlcpy(info->driver_name, "sw_sync", sizeof(info->driver_name));
+  info->timestamp_ns = timestamp_ns_;
+  // We fill no driver_data.
+  return info->len;
+}
+
+uint64_t SyncPoint::timestamp_ns() {
+  base::AutoLock lock(mutex_);
+  return timestamp_ns_;
+}
+
+//------------------------------------------------------------------------------
+
+FenceStream::FenceStream(const std::string& fence_name,
+                         ScopedVector<SyncPointTimeline> sync_points)
+    : FileStream(O_RDWR, ""), fence_name_(fence_name), status_(FENCE_ACTIVE),
+      fence_cond_(&fence_mutex_), sync_points_(sync_points.Pass()),
+      waiting_thread_count_for_testing_(0) {
+  ALOG_ASSERT(fence_name.size() < sizeof(sync_fence_info_data().name),
+              "The length of driver name must be less than %d bytes.",
+              sizeof(sync_fence_info_data().name));
+  set_permission(PermissionInfo(arc::kRootUid, true));
+}
+
+FenceStream::~FenceStream() {
+  for (size_t i = 0; i < sync_points_.size(); ++i) {
+    sync_points_[i]->timeline->DetachSyncPoint(
+        sync_points_[i]->sync_point.get());
+  }
+}
+
+// static
+scoped_refptr<FenceStream> FenceStream::CreateFence(
+    const std::string& fence_name,
+    ScopedVector<SyncPointTimeline> sync_points) {
+  scoped_refptr<FenceStream> fence(
+      new FenceStream(fence_name, sync_points.Pass()));
+  for (size_t i = 0; i < fence->sync_points_.size(); ++i) {
+    fence->sync_points_[i]->timeline->AttachSyncPoint(
+        fence, fence->sync_points_[i]->sync_point.get());
+  }
+  fence->MaybeSignal();
+  return fence;
+}
+
+// static
+scoped_refptr<FenceStream> FenceStream::CreateFenceTimelineLocked(
+    const std::string& fence_name,
+    ScopedVector<SyncPointTimeline> sync_points) {
+  scoped_refptr<FenceStream> fence(
+      new FenceStream(fence_name, sync_points.Pass()));
+  for (size_t i = 0; i < fence->sync_points_.size(); ++i) {
+    fence->sync_points_[i]->timeline->AttachSyncPointLocked(
+        fence, fence->sync_points_[i]->sync_point.get());
+  }
+  fence->MaybeSignal();
+  return fence;
+}
+
+ssize_t FenceStream::read(void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+ssize_t FenceStream::write(const void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FenceStream::ioctl(int request, va_list ap) {
+  GetFileSystemMutex().AssertAcquired();
+
+  // Unable to write switch-case since bionic ioctl.h enables _IOC_TYPECHECK
+  // which is not allowed in constant expression.
+  const unsigned int urequest = static_cast<unsigned int>(request);
+  if (urequest == SYNC_IOC_WAIT) {
+    return SyncIocWait(ap);
+  } else if (urequest == SYNC_IOC_MERGE) {
+    return SyncIocMerge(ap);
+  } else if (urequest == SYNC_IOC_FENCE_INFO) {
+    return SyncIocFenceInfo(ap);
+  } else {
+    errno = ENOTTY;
+    return -1;
+  }
+}
+
+const char* FenceStream::GetStreamType() const {
+  return "fence";
+}
+
+void FenceStream::MaybeSignal() {
+  base::AutoLock lock(fence_mutex_);
+  MaybeSignalLocked();
+}
+
+void FenceStream::MaybeSignalLocked() {
+  fence_mutex_.AssertAcquired();
+
+  if (GetSignaledSyncPointCountLocked() < sync_points_.size())
+    return;
+  status_ = FENCE_SIGNALED;
+  ALOG_ASSERT(IsValidLocked());
+  fence_cond_.Broadcast();
+}
+
+int FenceStream::SyncIocWait(va_list ap) {
+  const base::TimeTicks start(base::TimeTicks::Now());
+
+  // To avoid dead-lock, need to release file system lock before fence lock
+  // acquiring.
+  base::AutoUnlock unlock(GetFileSystemMutex());
+  base::AutoLock lock(fence_mutex_);
+  ALOG_ASSERT(IsValidLocked());
+
+  // |waiting_thread_count_for_testing_| must be incremented after the
+  // |fence_mutex_| is acquired.
+  ScopedCountIncrementer incrementor(&waiting_thread_count_for_testing_);
+
+  int* timeout_pt = va_arg(ap, int*);
+  if (!timeout_pt) {
+    errno = EFAULT;
+    return -1;
+  }
+  int timeout = *timeout_pt;
+
+  if (sync_points_.empty()) {
+    ALOGW("SYNC_IOC_WAIT is called for empty sync points.");
+    return 0;
+  }
+
+  if (status_ == FENCE_SIGNALED)
+    return 0;
+  ALOG_ASSERT(status_ == FENCE_ACTIVE);
+
+  // VirtualFileSystem::ioctl added the reference during this function call, so
+  // no need to increment reference count here.
+
+  // Negative timeout means the call can block indefinitely.
+  const base::TimeTicks time_limit = timeout < 0 ?
+      base::TimeTicks() : start + base::TimeDelta::FromMilliseconds(timeout);
+
+  while (true) {
+    const bool is_timeout = internal::WaitUntil(&fence_cond_, time_limit);
+    ALOG_ASSERT(IsValidLocked());
+
+    if (status_ == FENCE_SIGNALED)
+      return 0;
+
+    if (is_timeout) {
+      ALOG_ASSERT(timeout >= 0);
+      errno = ETIME;
+      return -1;
+    }
+  }
+  ALOG_ASSERT(false, "Must not be reached here.");
+  return 0;
+}
+
+int FenceStream::SyncIocMerge(va_list ap) {
+  GetFileSystemMutex().AssertAcquired();
+
+  sync_merge_data* data = va_arg(ap, sync_merge_data*);
+  if (!data) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  VirtualFileSystem* vfs = VirtualFileSystem::GetVirtualFileSystem();
+
+  scoped_refptr<FileStream> file_stream = vfs->GetStreamLocked(data->fd2);
+  if (!file_stream ||
+      strcmp(file_stream->GetStreamType(), GetStreamType()) != 0) {
+    // Return ENOENT if the given FD is not a fence stream. This is compatible
+    // with upstream implementation.
+    errno = ENOENT;
+    return -1;
+  }
+
+  // This downcast is safe since the stream check is passed above.
+  scoped_refptr<FenceStream> other_fence_stream =
+      static_cast<FenceStream*>(file_stream.get());
+  if (this == other_fence_stream) {
+    // Just return duped FD if the sync_ioc_merge is called for same stream.
+    data->fence = vfs->DupLocked(data->fd2, -1);
+    return 0;
+  }
+
+  // If sync points exist in a same timeline, use the latter one.
+  std::map<Timeline*, SyncPoint*> timeline_syncpoint;
+  for (size_t i = 0; i < sync_points_.size(); ++i) {
+    timeline_syncpoint[sync_points_[i]->timeline] =
+        sync_points_[i]->sync_point.get();
+  }
+  for (size_t i = 0; i < other_fence_stream->sync_points_.size(); ++i) {
+    SyncPoint* pt = other_fence_stream->sync_points_[i]->sync_point.get();
+    Timeline* tm = other_fence_stream->sync_points_[i]->timeline;
+    std::pair<std::map<Timeline*, SyncPoint*>::iterator, bool> p =
+        timeline_syncpoint.insert(std::make_pair(tm, pt));
+
+    if (!p.second && p.first->second->signaling_time() < pt->signaling_time())
+      p.first->second = pt;
+  }
+
+  ScopedVector<FenceStream::SyncPointTimeline> new_sync_points;
+  for (std::map<Timeline*, SyncPoint*>::iterator it =
+       timeline_syncpoint.begin(); it != timeline_syncpoint.end(); ++it) {
+    scoped_ptr<SyncPoint> sp(new SyncPoint(it->second->signaling_time(),
+                                           it->second->timestamp_ns()));
+    new_sync_points.push_back(
+        new FenceStream::SyncPointTimeline(sp.Pass(), it->first));
+  }
+
+  data->fence = vfs->AddFileStreamLocked(
+      FenceStream::CreateFence(data->name, new_sync_points.Pass()));
+  if (data->fence == -1) {
+    errno = EMFILE;
+    return -1;
+  }
+  return 0;
+}
+
+int FenceStream::SyncIocFenceInfo(va_list ap) {
+  base::AutoUnlock unlock(GetFileSystemMutex());
+  base::AutoLock lock(fence_mutex_);
+  ALOG_ASSERT(IsValidLocked());
+
+  sync_fence_info_data* info = va_arg(ap, sync_fence_info_data*);
+  if (!info) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  if (info->len < sizeof(sync_fence_info_data)) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  base::strlcpy(info->name, fence_name_.c_str(), sizeof(info->name));
+  info->status = status_;
+  uint32_t written_length = offsetof(sync_fence_info_data, pt_info);
+
+  for (size_t i = 0; i < sync_points_.size(); ++i) {
+    sync_pt_info* target =
+        reinterpret_cast<sync_pt_info*>(
+            reinterpret_cast<uint8_t*>(info) + written_length);
+    uint32_t result = sync_points_[i]->sync_point->FillSyncPtInfo(
+        target, info->len - written_length);
+    if (!result) {
+      ALOGW("Failed to write sync point informations.");
+      errno = ENOMEM;
+      return -1;
+    }
+    written_length += result;
+  }
+  info->len = written_length;
+  return 0;
+}
+
+bool FenceStream::IsValidLocked() const {
+  fence_mutex_.AssertAcquired();
+
+  ALOG_ASSERT(!fence_name_.empty());
+
+  // Check all sync points have different timelines.
+  std::set<Timeline*> timelines;
+  for (size_t i = 0; i < sync_points_.size(); ++i) {
+    if (!timelines.insert(sync_points_[i]->timeline).second) {
+      ALOGE("Found two sync points which are on the same timeline.");
+      return false;
+    }
+  }
+
+  if (status_ != FENCE_ACTIVE && status_ != FENCE_SIGNALED) {
+    ALOGE("Unexpected status value: %d", status_);
+    return false;
+  }
+  return true;
+}
+
+uint32_t FenceStream::GetSignaledSyncPointCountLocked() const {
+  fence_mutex_.AssertAcquired();
+  uint32_t num_signaled = 0;
+  for (size_t i = 0; i < sync_points_.size(); ++i) {
+    if (sync_points_[i]->sync_point->IsSignaled())
+      num_signaled++;
+  }
+  return num_signaled;
+}
+
+uint32_t FenceStream::GetWaitingThreadCountFenceForTesting() {
+  base::AutoLock lock(fence_mutex_);
+  return waiting_thread_count_for_testing_;
+}
+
+FenceStream::SyncPointTimeline::SyncPointTimeline(scoped_ptr<SyncPoint> sp,
+                                                  scoped_refptr<Timeline> tm)
+        : sync_point(sp.Pass()), timeline(tm) {
+}
+
+FenceStream::SyncPointTimeline::~SyncPointTimeline() {
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/fence_stream.h b/src/posix_translation/fence_stream.h
new file mode 100644
index 0000000..c999101
--- /dev/null
+++ b/src/posix_translation/fence_stream.h
@@ -0,0 +1,297 @@
+// Copyright 2014 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.
+//
+// This header provides the same functionalities as the sync driver in Linux
+// kernel. Just like the sync driver, this header consists 3 classes: Timeline,
+// SyncPoint, and Fence.
+//
+// Timeline:  A timeline represents a monotonically increasing counter. On
+//            Linux, hardware vendor can provide a hardware specific
+//            implementation. On destruction, all sync points on the timeline
+//            are signaled.
+// SyncPoint: A sync point represents a specific value on the attached timeline.
+//            Sync point may not belong to any timeline.
+// Fence:     A fence is a collection of sync points. This is backed by a file
+//            descriptor. A fence may have sync points on different timelines.
+//
+// An example diagram:
+//
+// Timeline(TL) and SyncPoint(SP): (*: counter, +: sync points)
+//          SP1            SP2
+// --*-------+--------------+----------------> TL1
+//                                SP3
+//         ----*-------------------+---------> TL2
+//
+// Fence(FE):
+// FE1: [SP1]:
+// FE2: [SP2, SP3]:
+//
+// Here, above system works as follows:
+// 1. Each timeline increments their counter at any time.
+// 2. If TL1 counter reaches SP1, the SP1 sync point is signaled. As the result,
+//    the FE1 is signaled since FE1 only has SP1 sync point.
+// 3. Then, if TL1 counter reaches SP2, the sync point SP2 is signaled. However
+//    FE2 is not signaled since it also has SP3 which is not signalled yet.
+// 4. Then, if TL2 counter reaches SP3, the sync point SP3 is signaled and FE2
+//    is also signaled since all sync points which FE2 have are signaled.
+//
+// Note that all sync point must be managed by a fence, and also all sync point
+// must be on a timeline.
+//
+// Also note that creating a new sync points or a new fence stream are timeline
+// implementation dependent. For example, there is a reference implementation
+// in Android. Its timeline is file descriptor backed implementation, hence
+// a new fence can be created by calling ioctl with SW_SYNC_IOC_CREATE_FENCE
+// and a timeline file descriptor. For more detail please see
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/staging/android/uapi/sw_sync.h
+//
+// Fence FD accepts following requests for ioctl(2):
+// SYNC_IOC_WAIT:
+//   Block until all sync points in the fence are signaled or timeout is
+//   reached. The third argument is a pointer to an integer, which is timeout
+//   period in millisecond.
+// SYNC_IOC_FENCE_INFO:
+//   Retrieve fence information including attached sync points.
+//   The third argument is a pointer of sync_fence_info_data struct which is
+//   used as both input/output. As the input, sync_fence_info_data::len is the
+//   total length of the passed buffer. With this function call, the fence
+//   information and multiple attached sync point information are written to
+//   the buffer. If the size of the buffer is not sufficient for filling, this
+//   function fails with ENOMEM.
+// SYNC_IOC_MERGE:
+//   Create a new "merged" fence which has copied sync points in both passed two
+//   fences: the first fence is passed as the first argument, and the other
+//   fence is passed in the third argument's struct. Here "merged" means that
+//   waiting the merged fence is equal to waiting both passed two fences.
+//   The third argument is a pointer to a sync_merge_data struct. |fd2| and
+//   |name| members in that sturct are used for input, and |fence| member is
+//   used for output. The |fence| will be filled with a new fence FD whose name
+//   is |name|. As the result of this process, |fence| has both sync points in
+//   passed fences. If two sync points are attached to the same timeline, as the
+//   result of this process, only the later one is used. The sync points in a
+//   new fence are copied. The passed two fences and their sync points are not
+//   affected by this operation.
+//   For example, merging FE1 and FE2 in the diagram above results in a new
+//   fence FE3 which has two sync points:[SP2, SP3]. Here FE3 doesn't have SP1
+//   since there is a later sync point SP2 in FE2. Even after this operation,
+//   FE1 and FE2 are still alive.
+// Also, if the fence is no longer necessary, the fence can be closed with
+// close(2). For more details, please see
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/staging/android/uapi/sync.h
+//
+// To reduce contention of file system mutex, use different mutex for guarding
+// each fence and sync points.
+#ifndef POSIX_TRANSLATION_FENCE_STREAM_H_
+#define POSIX_TRANSLATION_FENCE_STREAM_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "common/export.h"
+#include "posix_translation/device_file.h"
+#include "posix_translation/file_system_handler.h"
+
+struct sync_pt_info;
+
+namespace posix_translation {
+
+class FenceStream;
+class SyncPoint;
+
+// A software based timeline implementation. Timeline has a monotnically
+// increasing counter and it is incremented by IncrementCounter. This timeline
+// will signal the SyncPoints which are added by AddNewSyncPoint when this
+// internal counter reaches each sync point's signaling time.
+//
+// This Timeline implementation is compatible with sw_sync in Linux kernel but
+// no user-space APIs are provided.
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/staging/android/sw_sync.h
+class ARC_EXPORT Timeline : public base::RefCountedThreadSafe<Timeline> {
+ public:
+  Timeline();
+
+  int CreateFence(const std::string& name, uint32_t signaling_time);
+
+  // Increments the internal counter. This function does nothing even if the
+  // internal counter overflows.
+  void IncrementCounter(uint32_t amount);
+
+ protected:
+  friend class base::RefCountedThreadSafe<Timeline>;
+  virtual ~Timeline();
+
+ private:
+  friend class TestableTimeline;
+  friend class TimelineTest;
+  friend class FenceStream;  // For hiding Attach/DetachSyncPoint.
+
+  // Attaches the |pt| to |this| timeline. Each sync point calls this function
+  // when it is constructed.
+  void AttachSyncPoint(FenceStream* fence, SyncPoint* pt);
+  void AttachSyncPointLocked(FenceStream* fence, SyncPoint* pt);
+
+  // Removes |pt| from |this| timeline. Each sync point calls this function when
+  // it is destructed.
+  void DetachSyncPoint(SyncPoint* pt);
+
+  // A monotonically increasing counter.
+  uint32_t counter_;
+
+  // A map from firing counter value to sync point pointer.
+  // |this| object does not own |sync_points_|.
+  std::multimap<uint32_t, SyncPoint*> sync_points_;
+
+  // A map from a sync point to a fence stream which is the owner of the sync
+  // point. |this| object does not own both FenceStream and SyncPoint. The
+  // FenceStream will not be released until DetachSyncPoint is called.
+  std::map<SyncPoint*, FenceStream*> sync_point_fence_;
+
+  // A lock for protecting all fields of this Timeline.
+  base::Lock mutex_;
+
+  DISALLOW_COPY_AND_ASSIGN(Timeline);
+};
+
+// A SyncPoint represents a value on timeline. SyncPoint can be only attached on
+// one time line and also only attached on one fence. SyncPoints are typically
+// destroyed when the attached fence stream is closed.
+class SyncPoint {
+ public:
+  // The caller must not delete the new SyncPoint instance since |fence| takes
+  // ownership of the instance to ensure that |fence| always outlives the
+  // instance. This constructor must be called with both |timeline| and
+  // |fence| locked.
+  // If the creating sync point has already been signaled, must pass the
+  // signaled timestamp to |timestamp_ns|, otherwise must pass 0ULL.
+  SyncPoint(uint32_t signaling_time, uint64_t timestamp_ns);
+
+  ~SyncPoint();
+
+  // Updates the sync point state as signaled.
+  void MarkAsSignaled();
+
+  // Returns true if this sync point has already been signaled.
+  bool IsSignaled();
+
+  // Fills |info|. Returns written length. Returns 0 on error.
+  uint32_t FillSyncPtInfo(struct sync_pt_info* info, uint32_t length);
+
+  uint32_t signaling_time() const { return signaling_time_; }
+  uint64_t timestamp_ns();
+
+ private:
+  // A timestamp when this sync point was signaled. This is a monotonic time
+  // from the boot time and 0 if not signaled yet.
+  uint64_t timestamp_ns_;
+
+  // If the internal counter of |timeline_| reaches |signaling_time_|, this sync
+  // point is signaled.
+  const uint32_t signaling_time_;
+
+  // |mutex_| protects all fields in SyncPoint.
+  base::Lock mutex_;
+
+  DISALLOW_COPY_AND_ASSIGN(SyncPoint);
+};
+
+// A Fence is a collection of sync points. Fence is backed by a file descriptor
+// and the file descriptor can be passed to userspace. The application in
+// userspace can call ioctl(2) and close(2).
+class FenceStream : public FileStream {
+ public:
+  enum FenceStatus {
+    FENCE_ACTIVE = 0,  // The fence is not signaled. This is initial state.
+    FENCE_SIGNALED = 1,  // The fence is signaled.
+  };
+
+  struct SyncPointTimeline {
+    SyncPointTimeline(scoped_ptr<SyncPoint> sp, scoped_refptr<Timeline> tm);
+    ~SyncPointTimeline();
+
+    scoped_ptr<SyncPoint> sync_point;
+    scoped_refptr<Timeline> timeline;
+  };
+
+  // The |fence_name| is used to fill sync_fence_info_data::name when
+  // SYNC_IOC_FENCE_INFO is requested. To create fence stream, filesystem lock
+  // needs to be acquired.
+  static scoped_refptr<FenceStream> CreateFence(
+      const std::string& fence_name,
+      ScopedVector<SyncPointTimeline> sync_points);
+  static scoped_refptr<FenceStream> CreateFenceTimelineLocked(
+      const std::string& fence_name,
+      ScopedVector<SyncPointTimeline> sync_points);
+
+  // FileStream overrides.
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+  virtual const char* GetStreamType() const OVERRIDE;
+
+  // Looks all the |sync_points_| and signals |fence_cond_| if all of them are
+  // signaled state.
+  void MaybeSignal();
+
+  // Returns the number of waiting threads on |fence_cond_|. This function
+  // acquires |fence_mutex_|.
+  uint32_t GetWaitingThreadCountFenceForTesting();
+
+ protected:
+  virtual ~FenceStream();
+
+ private:
+  friend class FenceStreamTest;
+
+  // Uses CreateFence/CreateFenceTimelineLocked instead.
+  FenceStream(const std::string& fence_name,
+              ScopedVector<SyncPointTimeline> sync_points);
+
+  // SYNC_IOC_WAIT ioctl request handler.
+  int SyncIocWait(va_list ap);
+
+  // SYNC_IOC_MERGE ioctl request handler.
+  int SyncIocMerge(va_list ap);
+
+  // SYNC_IOC_FENCE_INFO ioctl request handler.
+  int SyncIocFenceInfo(va_list ap);
+
+  // Returns true if the fence stream is valid. The validity of fence stream is
+  // that 1)the internal state is consistent with sync points and 2)each sync
+  // point is on a different timeline.
+  bool IsValidLocked() const;
+
+  // Returns the number of signaled sync points.
+  uint32_t GetSignaledSyncPointCountLocked() const;
+
+  // Same as MaybeSignal but this function can be called with |fence_mutex_| is
+  // acquired.
+  void MaybeSignalLocked();
+
+  const std::string fence_name_;
+  FenceStatus status_;
+
+  // |fence_mutex_| must be declared before |fence_cond_|.
+  // |fence_mutex_| protects all fields in this fence stream except for
+  // |sync_points_|.
+  base::Lock fence_mutex_;
+  base::ConditionVariable fence_cond_;
+
+  const ScopedVector<SyncPointTimeline> sync_points_;
+
+  // The number of waiting threads on |fence_cond_|.
+  uint32_t waiting_thread_count_for_testing_;
+
+  DISALLOW_COPY_AND_ASSIGN(FenceStream);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_FENCE_STREAM_H_
diff --git a/src/posix_translation/fence_stream_test.cc b/src/posix_translation/fence_stream_test.cc
new file mode 100644
index 0000000..7487341
--- /dev/null
+++ b/src/posix_translation/fence_stream_test.cc
@@ -0,0 +1,1177 @@
+// Copyright 2014 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 "posix_translation/fence_stream.h"
+
+#include <linux/sync.h>
+#include <sched.h>
+
+#include <algorithm>
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "base/time/time.h"
+#include "gtest/gtest.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi_mocks/background_test.h"
+
+namespace posix_translation {
+
+namespace {
+const char kDriverName[] = "sw_sync";
+const char kFenceName[] = "test_fence";
+const char kTimelineName[] = "arc";
+
+const int kDefaultTimeoutInMs = 5 * 60 * 1000;  // 5 min
+
+// Helper function to get number of sync_pt_info in sync_fence_info_data.
+uint32_t SyncPtInfoCount(sync_fence_info_data* info) {
+  uint32_t read_len = sizeof(sync_fence_info_data);
+  int i = 0;
+  while (read_len < info->len) {
+    sync_pt_info* pt_info = reinterpret_cast<sync_pt_info*>(
+        reinterpret_cast<uint8_t*>(info) + read_len);
+    read_len += pt_info->len;
+    i++;
+  }
+  return i;
+}
+
+// Helper function to get sync_pt_info in sync_fence_info_data.
+// The |idx| is zero-origin.
+const sync_pt_info* GetSyncPtInfo(sync_fence_info_data* info, uint32_t idx) {
+  if (idx >= SyncPtInfoCount(info))
+    return NULL;
+
+  uint32_t read_len = sizeof(sync_fence_info_data);
+  sync_pt_info* result = NULL;
+  do {
+    result = reinterpret_cast<sync_pt_info*>(
+        reinterpret_cast<uint8_t*>(info) + read_len);
+    read_len += result->len;
+  } while (idx--);
+  return result;
+}
+
+// Calls Timeline::IncrementCounter with |value_| |times_| times.
+class ThreadedIncrementor : public base::DelegateSimpleThread::Delegate {
+ public:
+  // Caller must free |event|.
+  ThreadedIncrementor(scoped_refptr<Timeline> timeline, uint32_t value,
+                      uint32_t times, base::WaitableEvent* event)
+      : timeline_(timeline), value_(value), times_(times), event_(event),
+        thread_(this, "threaded_incrementor") {}
+
+  void Start() {
+    thread_.Start();
+  }
+
+  void Join() {
+    thread_.Join();
+  }
+
+ private:
+  // base::DelegateSimpleThread::Delegate override.
+  virtual void Run() OVERRIDE {
+    // Wait until the |event_| is signaled.
+    event_->Wait();
+
+    for (uint32_t i = 0; i < times_; ++i) {
+      timeline_->IncrementCounter(value_);
+    }
+  }
+
+  scoped_refptr<Timeline> timeline_;
+  const uint32_t value_;
+  const uint32_t times_;
+  base::WaitableEvent* event_;
+  base::DelegateSimpleThread thread_;
+};
+
+// Adds and keeps the sync point to |timeline|.
+class ThreadedAttacher : public base::DelegateSimpleThread::Delegate {
+ public:
+  // Caller must free |event|.
+  ThreadedAttacher(scoped_refptr<Timeline> timeline,
+                   uint32_t origin, uint32_t step, uint32 count,
+                   base::WaitableEvent* event)
+      : timeline_(timeline), origin_(origin),
+        step_(step), count_(count),
+        event_(event), thread_(this, "threaded_attacher") {}
+
+  void Start() {
+    thread_.Start();
+  }
+
+  void Join() {
+    thread_.Join();
+  }
+
+ private:
+  // base::DelegateSimpleThread::Delegate override.
+  virtual void Run() OVERRIDE {
+    event_->Wait();
+
+    for (uint32_t i = 0; i < count_; ++i) {
+      timeline_->CreateFence(kFenceName, origin_ + step_ * i);
+    }
+  }
+
+  scoped_refptr<Timeline> timeline_;
+  const uint32_t origin_;
+  const uint32_t step_;
+  const uint32_t count_;
+  base::WaitableEvent* event_;
+  base::DelegateSimpleThread thread_;
+  std::vector<scoped_refptr<FenceStream> > created_streams_;
+};
+
+// Releases |fences_to_be_removed| on a different thread.
+class ThreadedRemover : public base::DelegateSimpleThread::Delegate {
+ public:
+  // Caller must free |event|.
+  ThreadedRemover(std::vector<int> fences_to_be_removed,
+                  base::WaitableEvent* event)
+      : fences_to_be_removed_(fences_to_be_removed), event_(event),
+        thread_(this, "threaded_remover") {}
+
+  void Start() {
+    thread_.Start();
+  }
+
+  void Join() {
+    thread_.Join();
+  }
+
+ private:
+  // base::DelegateSimpleThread::Delegate override.
+  virtual void Run() OVERRIDE {
+    event_->Wait();
+    VirtualFileSystem* vfs = VirtualFileSystem::GetVirtualFileSystem();
+    for (size_t i = 0; i < fences_to_be_removed_.size(); ++i) {
+      vfs->close(fences_to_be_removed_[i]);
+    }
+  }
+
+  std::vector<int> fences_to_be_removed_;
+  base::WaitableEvent* event_;
+  base::DelegateSimpleThread thread_;
+};
+
+// Calls ioctl(SYNC_IOC_WAIT) on a different thread.
+class ThreadedWaiter : public base::DelegateSimpleThread::Delegate {
+ public:
+  ThreadedWaiter(int fd, int ioctl_timeout)
+      : fd_(fd), ioctl_timeout_(ioctl_timeout), result_(0),
+        is_waiting_(false),
+        vfs_(VirtualFileSystem::GetVirtualFileSystem()),
+        thread_(this, "threaded_waiter") {}
+
+  void StartAndBlockUntilReady() {
+    scoped_refptr<FenceStream> fence = GetFenceStream();
+
+    thread_.Start();
+
+    // Busy-wait until the waiter thread starts waiting on the condition
+    // variable.
+    while (fence->GetWaitingThreadCountFenceForTesting() == 0) {
+      sched_yield();
+    }
+  }
+
+  void Join() {
+    thread_.Join();
+  }
+
+  int result() { return result_; }
+
+  bool IsWaiting() const {
+    scoped_refptr<FenceStream> fence = GetFenceStream();
+    return fence->GetWaitingThreadCountFenceForTesting() != 0;
+  }
+
+ private:
+  // base::DelegateSimpleThread::Delegate override.
+  virtual void Run() OVERRIDE {
+    base::AutoLock lock(vfs_->mutex());
+    result_ = CallIoctlLocked(SYNC_IOC_WAIT, &ioctl_timeout_);
+  }
+
+  int CallIoctlLocked(int request, ...) {
+    va_list ap;
+    va_start(ap, request);
+    int ret = vfs_->GetStreamLocked(fd_)->ioctl(request, ap);
+    va_end(ap);
+    return ret;
+  }
+
+  scoped_refptr<FenceStream> GetFenceStream() const {
+    base::AutoLock lock(vfs_->mutex());
+    return static_cast<FenceStream*>(vfs_->GetStreamLocked(fd_).get());
+  }
+
+  int fd_;
+  int ioctl_timeout_;
+  int result_;
+  bool is_waiting_;
+  VirtualFileSystem* vfs_;
+  base::DelegateSimpleThread thread_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadedWaiter);
+};
+
+// Calls ioctl(SYNC_IOC_MERGE) on different thread. This thread wait |event|
+// just before calling ioctl.
+class ThreadedMerger : public base::DelegateSimpleThread::Delegate {
+ public:
+  // Caller must free |event|.
+  ThreadedMerger(const std::vector<int>& fds1, const std::vector<int>& fds2,
+                 base::WaitableEvent* event)
+      : fds1_(fds1), fds2_(fds2), result_(0),
+        event_(event), vfs_(VirtualFileSystem::GetVirtualFileSystem()),
+        thread_(this, "threaded_waiter") {
+    ALOG_ASSERT(fds1.size() == fds2.size());
+    ALOG_ASSERT(!fds1.empty());
+    ALOG_ASSERT(!fds2.empty());
+  }
+
+  void Start() {
+    thread_.Start();
+  }
+
+  void Join() {
+    thread_.Join();
+  }
+
+  int GetMergedFenceFd(size_t index) {
+    ALOG_ASSERT(index < merged_fence_fds_.size());
+    return merged_fence_fds_[index];
+  }
+
+ private:
+  // base::DelegateSimpleThread::Delegate override.
+  virtual void Run() OVERRIDE {
+    event_->Wait();
+    merged_fence_fds_.resize(fds1_.size());
+    for (size_t i = 0; i < fds1_.size(); ++i) {
+      sync_merge_data merge_data = {};
+      merge_data.fd2 = fds2_[i];
+      base::strlcpy(merge_data.name, kFenceName, sizeof(merge_data.name));
+
+      // Wait until the |event| is signaled.
+      result_ &= CallIoctl(fds1_[i], SYNC_IOC_MERGE, &merge_data);
+      merged_fence_fds_[i] = merge_data.fence;
+      ALOG_ASSERT(merge_data.fence != -1);
+    }
+  }
+
+  int CallIoctl(int fd, int request, ...) {
+    base::AutoLock lock(vfs_->mutex());
+    va_list ap;
+    va_start(ap, request);
+    int ret = vfs_->GetStreamLocked(fd)->ioctl(request, ap);
+    va_end(ap);
+    return ret;
+  }
+
+  std::vector<int> fds1_;
+  std::vector<int> fds2_;
+  std::vector<int> merged_fence_fds_;
+  int result_;
+  base::WaitableEvent* event_;
+  VirtualFileSystem* vfs_;
+  base::DelegateSimpleThread thread_;
+
+  DISALLOW_COPY_AND_ASSIGN(ThreadedMerger);
+};
+
+}  // namespace
+
+class TestableTimeline : public Timeline {
+ public:
+  TestableTimeline() {}
+
+  virtual ~TestableTimeline() {}
+
+  // Returns true if this timeline has at least one sync point at
+  // |signaling_time|. Otherwise returns false.
+  bool HasSyncPointAt(uint32_t signaling_time) {
+    base::AutoLock lock(mutex_);
+    std::pair<std::multimap<uint32_t, SyncPoint*>::iterator,
+        std::multimap<uint32_t, SyncPoint*>::iterator> range =
+            sync_points_.equal_range(signaling_time);
+    return range.first != range.second;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestableTimeline);
+};
+
+class TimelineTest : public FileSystemBackgroundTestCommon<TimelineTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(ConstructDestruct);
+  DECLARE_BACKGROUND_TEST(CreateFence);
+  DECLARE_BACKGROUND_TEST(CreateFence_AtPastPoint);
+  DECLARE_BACKGROUND_TEST(IncrementCounterTest);
+  DECLARE_BACKGROUND_TEST(Threaded_IncrementCounterTest);
+  DECLARE_BACKGROUND_TEST(Threaded_AttachRemoveTest);
+
+ protected:
+  virtual void SetUp() OVERRIDE {
+    FileSystemBackgroundTestCommon<TimelineTest>::SetUp();
+    vfs_ = VirtualFileSystem::GetVirtualFileSystem();
+  }
+
+  virtual void TearDown() OVERRIDE {
+    vfs_ = NULL;
+    FileSystemBackgroundTestCommon<TimelineTest>::TearDown();
+  }
+
+  uint32_t GetCounterValue(scoped_refptr<Timeline> timeline) {
+    base::AutoLock lock(timeline->mutex_);
+    return timeline->counter_;
+  }
+
+  bool IsSignaled(int fence_fd) {
+    base::AutoLock lock(vfs_->mutex());
+    errno = 0;
+    int timeout = 0;
+    int r = IoctlLocked(GetFenceStreamLocked(fence_fd), SYNC_IOC_WAIT,
+                        &timeout);
+    ALOG_ASSERT(errno == 0 || errno == ETIME);
+    return r == 0;
+  }
+
+  size_t GetMapEntryCount(scoped_refptr<Timeline> timeline) {
+    base::AutoLock lock(timeline->mutex_);
+    return timeline->sync_points_.size();
+  }
+
+  base::Lock& GetMutex(scoped_refptr<Timeline> timeline) {
+    return timeline->mutex_;
+  }
+
+  std::multimap<uint32_t, SyncPoint*>* GetInternalMapLocked(
+      scoped_refptr<Timeline> timeline) {
+    timeline->mutex_.AssertAcquired();
+    return &timeline->sync_points_;
+  }
+
+ private:
+  int IoctlLocked(scoped_refptr<FileStream> stream, int request, ...) {
+    vfs_->mutex().AssertAcquired();
+    va_list ap;
+    va_start(ap, request);
+    int r = stream->ioctl(request, ap);
+    va_end(ap);
+    return r;
+  }
+
+  scoped_refptr<FenceStream> GetFenceStreamLocked(int fence_fd) const {
+    vfs_->mutex().AssertAcquired();
+    return static_cast<FenceStream*>(vfs_->GetStreamLocked(fence_fd).get());
+  }
+
+  VirtualFileSystem* vfs_;
+};
+
+TEST_BACKGROUND_F(TimelineTest, ConstructDestruct) {
+  scoped_refptr<Timeline> timeline = new Timeline();
+  timeline = NULL;
+}
+
+TEST_BACKGROUND_F(TimelineTest, CreateFence) {
+  scoped_refptr<Timeline> timeline1 = new Timeline();
+  scoped_refptr<Timeline> timeline2 = new Timeline();
+  scoped_refptr<Timeline> timeline3 = new Timeline();
+
+  int fence_fd_tl1_1 = timeline1->CreateFence(kFenceName, 1);
+  int fence_fd_tl1_2 = timeline1->CreateFence(kFenceName, 2);
+  int fence_fd_tl1_3 = timeline1->CreateFence(kFenceName, 3);
+  int fence_fd_tl2_1 = timeline2->CreateFence(kFenceName, 1);
+  int fence_fd_tl2_2 = timeline2->CreateFence(kFenceName, 2);
+  int fence_fd_tl2_3 = timeline2->CreateFence(kFenceName, 3);
+  int fence_fd_tl3_1 = timeline3->CreateFence(kFenceName, 1);
+  int fence_fd_tl3_2 = timeline3->CreateFence(kFenceName, 2);
+  int fence_fd_tl3_3 = timeline3->CreateFence(kFenceName, 3);
+
+  EXPECT_EQ(3U, GetMapEntryCount(timeline1));
+  file_system_->close(fence_fd_tl1_1);
+  EXPECT_EQ(2U, GetMapEntryCount(timeline1));
+  file_system_->close(fence_fd_tl1_2);
+  EXPECT_EQ(1U, GetMapEntryCount(timeline1));
+  file_system_->close(fence_fd_tl1_3);
+  EXPECT_EQ(0U, GetMapEntryCount(timeline1));
+
+  EXPECT_EQ(3U, GetMapEntryCount(timeline2));
+  file_system_->close(fence_fd_tl2_1);
+  EXPECT_EQ(2U, GetMapEntryCount(timeline2));
+  file_system_->close(fence_fd_tl2_2);
+  EXPECT_EQ(1U, GetMapEntryCount(timeline2));
+  file_system_->close(fence_fd_tl2_3);
+  EXPECT_EQ(0U, GetMapEntryCount(timeline2));
+
+  EXPECT_EQ(3U, GetMapEntryCount(timeline3));
+  file_system_->close(fence_fd_tl3_1);
+  EXPECT_EQ(2U, GetMapEntryCount(timeline3));
+  file_system_->close(fence_fd_tl3_2);
+  EXPECT_EQ(1U, GetMapEntryCount(timeline3));
+  file_system_->close(fence_fd_tl3_3);
+  EXPECT_EQ(0U, GetMapEntryCount(timeline3));
+}
+
+TEST_BACKGROUND_F(TimelineTest, CreateFence_AtPastPoint) {
+  scoped_refptr<Timeline> timeline = new Timeline();
+  timeline->IncrementCounter(10);
+
+  int fence_fd = timeline->CreateFence(kFenceName, 5);
+  EXPECT_TRUE(IsSignaled(fence_fd));
+}
+
+TEST_BACKGROUND_F(TimelineTest, IncrementCounterTest) {
+  scoped_refptr<Timeline> timeline = new Timeline();
+
+  int fence_fd1 = timeline->CreateFence(kFenceName, 2);
+  int fence_fd2 = timeline->CreateFence(kFenceName, 5);
+
+  EXPECT_EQ(0U, GetCounterValue(timeline));
+  EXPECT_FALSE(IsSignaled(fence_fd1));
+
+  timeline->IncrementCounter(1);
+  EXPECT_EQ(1U, GetCounterValue(timeline));
+  EXPECT_FALSE(IsSignaled(fence_fd1));
+  EXPECT_FALSE(IsSignaled(fence_fd2));
+
+  timeline->IncrementCounter(2);
+  EXPECT_EQ(3U, GetCounterValue(timeline));
+  EXPECT_TRUE(IsSignaled(fence_fd1));
+  EXPECT_FALSE(IsSignaled(fence_fd2));
+
+  timeline->IncrementCounter(3);
+  EXPECT_EQ(6U, GetCounterValue(timeline));
+  EXPECT_TRUE(IsSignaled(fence_fd1));
+  EXPECT_TRUE(IsSignaled(fence_fd2));
+}
+
+TEST_BACKGROUND_F(TimelineTest, Threaded_AttachRemoveTest) {
+  scoped_refptr<Timeline> timeline = new Timeline();
+
+  // Increment counter to 200 for testing of past sync points.
+  const size_t kInitialTimelineCounter = 50U;
+  timeline->IncrementCounter(kInitialTimelineCounter);
+
+  base::WaitableEvent event(true /* manual reset */, false /* Not signaled */);
+
+  // Increment 100 with 5 threads.
+  const size_t kIncrementorCount = 5U;
+  ScopedVector<ThreadedIncrementor> incrementor;
+  incrementor.resize(kIncrementorCount);
+  const size_t kIncrementCountPerThread = 20U;
+  const size_t kFinalTimelineCounter = kInitialTimelineCounter +
+      kIncrementorCount * kIncrementCountPerThread;
+  for (size_t i = 0; i < kIncrementorCount; ++i) {
+    incrementor[i] = new ThreadedIncrementor(
+        timeline, 1, kIncrementCountPerThread, &event);
+    incrementor[i]->Start();
+  }
+
+  // The permanent fences won't be removed.
+  const size_t kPermanentFenceCount = 200U;
+  std::vector<int> permanent_fence_fds(kPermanentFenceCount);
+  for (size_t i = 0; i < kPermanentFenceCount; ++i) {
+    permanent_fence_fds[i] = timeline->CreateFence(kFenceName, i);
+    ASSERT_LE(0, permanent_fence_fds[i]);
+  }
+
+  // Merge 200 sync points in 5 threads.
+  const size_t kMergerCount = 5U;
+  const size_t kMergeSyncPointCountPerThread = 4U;
+  ScopedVector<ThreadedMerger> mergers;
+  mergers.resize(kMergerCount);
+  size_t kMergedNotSignaledSyncPointCount = 0;
+  for (size_t i = 0; i < kMergerCount; ++i) {
+    std::vector<int> fd1(kMergeSyncPointCountPerThread);
+    std::vector<int> fd2(kMergeSyncPointCountPerThread);
+
+    const size_t kChunkMax = (i + 1) * kMergeSyncPointCountPerThread - 1;
+    const size_t kChunkMin = i * kMergeSyncPointCountPerThread;
+    for (size_t j = 0; j < kMergeSyncPointCountPerThread; ++j) {
+      fd1[j] = permanent_fence_fds[kChunkMax - j];
+      fd2[j] = permanent_fence_fds[kChunkMin + j];
+      if (kChunkMax - j > kFinalTimelineCounter ||
+          kChunkMin + j > kFinalTimelineCounter) {
+        kMergedNotSignaledSyncPointCount++;
+      }
+    }
+
+    mergers[i] = new ThreadedMerger(fd1, fd2, &event);
+    mergers[i]->Start();
+  }
+
+  // Remove 200 sync points in 5 threads.
+  const size_t kRemoverCount = 5U;
+  ScopedVector<ThreadedRemover> removers;
+  removers.resize(kRemoverCount);
+
+  const size_t kRemoveSyncPointCountPerThread = 40U;
+  for (size_t i = 0; i < kRemoverCount; ++i) {
+    std::vector<int> fences_to_be_removed(kRemoveSyncPointCountPerThread);
+    for (size_t j = 0; j < kRemoveSyncPointCountPerThread; ++j) {
+      fences_to_be_removed[j] =
+          timeline->CreateFence(kFenceName, j * kRemoverCount + i);
+      ASSERT_LE(0, fences_to_be_removed[j]);
+    }
+    removers[i] = new ThreadedRemover(fences_to_be_removed, &event);
+    removers[i]->Start();
+  }
+
+  // Attach 200 sync points in 5 threads.
+  const size_t kAttahcerCount = 5U;
+  ScopedVector<ThreadedAttacher> attachers;
+  attachers.resize(kAttahcerCount);
+
+  const size_t kAttachSyncPointCountPerThread = 40U;
+  for (size_t i = 0; i < kAttahcerCount; ++i) {
+    attachers[i] = new ThreadedAttacher(
+        timeline, i, kAttahcerCount, kAttachSyncPointCountPerThread, &event);
+    attachers[i]->Start();
+  }
+
+  EXPECT_EQ(
+      kPermanentFenceCount + kRemoverCount * kRemoveSyncPointCountPerThread,
+      GetMapEntryCount(timeline));
+
+  event.Signal();  // Wake up all threads.
+
+  // Join all threads.
+  for (size_t i = 0; i < kRemoverCount; ++i) {
+    removers[i]->Join();
+  }
+  for (size_t i = 0; i < kAttahcerCount; ++i) {
+    attachers[i]->Join();
+  }
+  for (size_t i = 0; i < kIncrementorCount; ++i) {
+    incrementor[i]->Join();
+  }
+  for (size_t i = 0; i < kMergerCount; ++i) {
+    mergers[i]->Join();
+  }
+
+  EXPECT_EQ(
+      kPermanentFenceCount + kAttahcerCount * kAttachSyncPointCountPerThread +
+      kMergerCount * kMergeSyncPointCountPerThread,
+      GetMapEntryCount(timeline));
+
+  EXPECT_EQ(kFinalTimelineCounter, GetCounterValue(timeline));
+
+  base::AutoLock lock(GetMutex(timeline));
+  std::multimap<uint32_t, SyncPoint*>* sync_points =
+      GetInternalMapLocked(timeline);
+
+  // All sync points on [0, 400] must be signaled.
+  uint32_t signaled_count = 0;
+  std::multimap<uint32_t, SyncPoint*>::iterator signaled_lower =
+      sync_points->lower_bound(0);
+  std::multimap<uint32_t, SyncPoint*>::iterator signaled_upper =
+      sync_points->upper_bound(kFinalTimelineCounter);
+  for (std::multimap<uint32_t, SyncPoint*>::iterator it = signaled_lower;
+       it != signaled_upper; ++it) {
+    EXPECT_TRUE(it->second->IsSignaled());
+    signaled_count++;
+  }
+  // Add 1U because the sync point whose signaling_time is 400 is fired.
+  const size_t kPermanentSyncPointSignaledCount = kFinalTimelineCounter + 1U;
+  const size_t kAddedSyncPointSignaledCount = kFinalTimelineCounter + 1U;
+  const size_t kMergedSyncPoitnSignaledCount =
+      kMergerCount * kMergeSyncPointCountPerThread -
+      kMergedNotSignaledSyncPointCount;
+  EXPECT_EQ(kPermanentSyncPointSignaledCount + kAddedSyncPointSignaledCount +
+            kMergedSyncPoitnSignaledCount, signaled_count);
+
+  // All sync points on (400,] must not be signaled.
+  uint32_t non_signaled_count = 0;
+  std::multimap<uint32_t, SyncPoint*>::iterator not_signaled_lower =
+      sync_points->lower_bound(kFinalTimelineCounter + 1);
+  for (std::multimap<uint32_t, SyncPoint*>::iterator it = not_signaled_lower;
+       it != sync_points->end(); ++it) {
+    EXPECT_FALSE(it->second->IsSignaled());
+    non_signaled_count++;
+  }
+
+  const size_t kPermanentSyncPointNotSignaledCount =
+      kPermanentFenceCount - kPermanentSyncPointSignaledCount;
+  const size_t kAddedSyncPointNotSignaledCount =
+      kAttahcerCount * kAttachSyncPointCountPerThread -
+      kAddedSyncPointSignaledCount;
+
+  EXPECT_EQ(
+      kPermanentSyncPointNotSignaledCount + kAddedSyncPointNotSignaledCount +
+      kMergedNotSignaledSyncPointCount, non_signaled_count);
+}
+
+TEST_BACKGROUND_F(TimelineTest, Threaded_IncrementCounterTest) {
+  scoped_refptr<Timeline> timeline = new Timeline();
+  int fence_fd = timeline->CreateFence(kFenceName, 500);
+
+  EXPECT_EQ(0U, GetCounterValue(timeline));
+
+  base::WaitableEvent event(true /* manual reset */, false /* Not signaled */);
+
+  const size_t kThreadCount = 20U;
+  ScopedVector<ThreadedIncrementor> incrementor;
+  incrementor.resize(kThreadCount);
+  for (size_t i = 0; i < kThreadCount; ++i) {
+    // Totally increments 1000 each.
+    incrementor[i] = new ThreadedIncrementor(timeline, 10, 100, &event);
+    incrementor[i]->Start();
+  }
+
+  event.Signal();  // Wake up all incrementor threads.
+
+  for (size_t i = 0; i < kThreadCount; ++i) {
+    incrementor[i]->Join();
+  }
+
+  EXPECT_EQ(20000U, GetCounterValue(timeline));
+  EXPECT_TRUE(IsSignaled(fence_fd));
+}
+
+class FenceStreamTest
+    : public FileSystemBackgroundTestCommon<FenceStreamTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(ConstructDestruct);
+  DECLARE_BACKGROUND_TEST(Unknown_Ioctl);
+  DECLARE_BACKGROUND_TEST(CloseFence);
+  DECLARE_BACKGROUND_TEST(CloseFence_DuringWait);
+  DECLARE_BACKGROUND_TEST(SpuriousWakeup_ForeverWait);
+  DECLARE_BACKGROUND_TEST(SpuriousWakeup_TimedWait);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_WAIT_WithNullTimeout);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_WAIT_Timeout);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_WAIT_Timeout0ms);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_WAIT_Threaded);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_WAIT_Threaded_Forever);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_FENCE_INFO_WithNullArg);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_FENCE_INFO_WithTooSmallSize);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_FENCE_INFO_Normal);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_FENCE_INFO_NoMemory);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_MERGE_SameBackend);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_MERGE_SingleTimeline);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_MERGE_MultiTimeline);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_MERGE_DuringWait);
+  DECLARE_BACKGROUND_TEST(SYNC_IOC_MERGE_ThreadedMerge);
+
+ protected:
+  virtual void SetUp() OVERRIDE {
+    FileSystemBackgroundTestCommon<FenceStreamTest>::SetUp();
+    timeline_ = new TestableTimeline();
+  }
+
+  virtual void TearDown() OVERRIDE {
+    timeline_ = NULL;
+    FileSystemBackgroundTestCommon<FenceStreamTest>::TearDown();
+  }
+
+  SyncPoint* GetSyncPoint(int fence_fd, uint32_t index) {
+    base::AutoLock lock(file_system_->mutex());
+    scoped_refptr<FenceStream> fence = GetFenceStreamLocked(fence_fd);
+    EXPECT_LT(index, fence->sync_points_.size());
+    SyncPoint* result = fence->sync_points_[index]->sync_point.get();
+    ALOG_ASSERT(result);
+    return result;
+  }
+
+  void EmulateSpuriousWakeup(int fence_fd) {
+    base::AutoLock lock(file_system_->mutex());
+    scoped_refptr<FenceStream> fence = GetFenceStreamLocked(fence_fd);
+    fence->fence_cond_.Broadcast();
+  }
+
+  int Ioctl(int fd, int request, ...) {
+    va_list ap;
+    va_start(ap, request);
+    int r = file_system_->ioctl(fd, request, ap);
+    va_end(ap);
+    return r;
+  }
+
+  sync_fence_info_data* AllocateFenceInfoDataBuffer(
+      std::vector<uint8_t>* buffer, size_t size) {
+    buffer->resize(size);
+    sync_fence_info_data* info =
+        reinterpret_cast<sync_fence_info_data*>(&(*buffer)[0]);
+    info->len = size;
+    return info;
+  }
+
+  scoped_refptr<Timeline> GetTimeline(int fence_fd, SyncPoint* sp) {
+    base::AutoLock lock(file_system_->mutex());
+    scoped_refptr<FenceStream> fence = GetFenceStreamLocked(fence_fd);
+    for (size_t i = 0; i < fence->sync_points_.size(); ++i) {
+      if (fence->sync_points_[i]->sync_point == sp)
+        return fence->sync_points_[i]->timeline;
+    }
+    return NULL;
+  }
+
+ private:
+  scoped_refptr<FenceStream> GetFenceStreamLocked(int fence_fd) {
+    return static_cast<FenceStream*>(
+        file_system_->GetStreamLocked(fence_fd).get());
+  }
+
+  scoped_refptr<FenceStream> GetFenceStream(int fence_fd) {
+    base::AutoLock lock(file_system_->mutex());
+    return GetFenceStreamLocked(fence_fd);
+  }
+
+  scoped_refptr<TestableTimeline> timeline_;
+};
+
+TEST_BACKGROUND_F(FenceStreamTest, ConstructDestruct) {
+  ScopedVector<FenceStream::SyncPointTimeline> sync_points;
+  scoped_refptr<FenceStream> fence =
+      new FenceStream(kFenceName, sync_points.Pass());
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, Unknown_Ioctl) {
+  // Unknown ioctl request to sync driver FD returns ENOTTY on Linux.
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, FIONREAD));
+  EXPECT_EQ(ENOTTY, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, CloseFence) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+
+  EXPECT_TRUE(timeline_->HasSyncPointAt(1));
+  file_system_->close(fence_fd);
+  // After FenceStream destruction, the attached fence points should be release
+  // from timeline.
+  EXPECT_FALSE(timeline_->HasSyncPointAt(1));
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, CloseFence_DuringWait) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+
+  ThreadedWaiter waiter(fence_fd, kDefaultTimeoutInMs);
+  waiter.StartAndBlockUntilReady();
+
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+
+  // Even after close the file descriptor, the already waiting thread keeps
+  // waiting and fence stream is still alive. This is compatible with upstream
+  // Linux Kernel behavior.
+  timeline_->IncrementCounter(1);
+  waiter.Join();
+  EXPECT_EQ(0, waiter.result());
+
+  // After FenceStream destruction, the attached fence points should be release
+  // from timeline.
+  EXPECT_FALSE(timeline_->HasSyncPointAt(1));
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SpuriousWakeup_ForeverWait) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  ThreadedWaiter waiter(fence_fd, -1 /* Never timeout. */);
+  waiter.StartAndBlockUntilReady();
+
+  EmulateSpuriousWakeup(fence_fd);
+
+  EXPECT_TRUE(waiter.IsWaiting());
+
+  timeline_->IncrementCounter(1);
+  waiter.Join();
+  EXPECT_EQ(0, waiter.result());
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SpuriousWakeup_TimedWait) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  ThreadedWaiter waiter(fence_fd, kDefaultTimeoutInMs);
+  waiter.StartAndBlockUntilReady();
+
+  EmulateSpuriousWakeup(fence_fd);
+
+  EXPECT_TRUE(waiter.IsWaiting());
+
+  timeline_->IncrementCounter(1);
+  waiter.Join();
+  EXPECT_EQ(0, waiter.result());
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_WAIT_WithNullTimeout) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  // If we pass NULL timeout, we always return EFAULT.
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_WAIT, NULL));
+  EXPECT_EQ(EFAULT, errno);
+
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  fence_fd = timeline_->CreateFence(kFenceName, 1);
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_WAIT, NULL));
+  EXPECT_EQ(EFAULT, errno);
+
+  // Signaled sync point.
+  timeline_->IncrementCounter(1);
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_WAIT, NULL));
+  EXPECT_EQ(EFAULT, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_WAIT_Timeout) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  int timeout = 20;  // 20 millisec.
+  errno = 0;
+  ASSERT_NE(0, Ioctl(fence_fd, SYNC_IOC_WAIT, &timeout));
+  EXPECT_EQ(ETIME, errno);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_WAIT_Timeout0ms) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  int timeout = 0;
+  errno = 0;
+  ASSERT_NE(0, Ioctl(fence_fd, SYNC_IOC_WAIT, &timeout));
+  EXPECT_EQ(ETIME, errno);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_WAIT_Threaded) {
+  // This test verifies signaling sync point will certainly wake up the waiting
+  // thread.
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+
+  ThreadedWaiter waiter(fence_fd, kDefaultTimeoutInMs);
+  waiter.StartAndBlockUntilReady();
+
+  EXPECT_TRUE(waiter.IsWaiting());
+
+  timeline_->IncrementCounter(1);
+  waiter.Join();
+  EXPECT_EQ(0, waiter.result());
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_WAIT_Threaded_Forever) {
+  // This test verifies signaling sync point will certainly wake up the waiting
+  // thread.
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+
+  ThreadedWaiter waiter(fence_fd, -2 /* Indefinitely waiting */);
+  waiter.StartAndBlockUntilReady();
+
+  EXPECT_TRUE(waiter.IsWaiting());
+
+  timeline_->IncrementCounter(1);
+  waiter.Join();
+  EXPECT_EQ(0, waiter.result());
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_FENCE_INFO_WithNullArg) {
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_FENCE_INFO, NULL));
+  EXPECT_EQ(EFAULT, errno);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest,
+                  SYNC_IOC_FENCE_INFO_WithTooSmallSize) {
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) - 1);
+
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_FENCE_INFO_Normal) {
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info));
+
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(fence_fd, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(0, errno);
+
+  EXPECT_EQ(sizeof(sync_fence_info_data) + sizeof(sync_pt_info), info->len);
+  EXPECT_EQ(1U, SyncPtInfoCount(info));
+  EXPECT_STREQ(kFenceName, info->name);
+  EXPECT_EQ(0, info->status);
+
+  const sync_pt_info* pt_info = GetSyncPtInfo(info, 0);
+
+  ASSERT_TRUE(pt_info);
+  EXPECT_EQ(sizeof(sync_pt_info), pt_info->len);
+  EXPECT_STREQ(kTimelineName, pt_info->obj_name);
+  EXPECT_STREQ(kDriverName, pt_info->driver_name);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_FENCE_INFO_NoMemory) {
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data));
+
+  int fence_fd = timeline_->CreateFence(kFenceName, 1);
+
+  errno = 0;
+  EXPECT_EQ(-1, Ioctl(fence_fd, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(ENOMEM, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_MERGE_SameBackend) {
+  int fence_fd1 = timeline_->CreateFence(kFenceName, 1);
+  int fence_fd2 = file_system_->dup(fence_fd1);
+
+  EXPECT_NE(fence_fd1, fence_fd2);
+  sync_merge_data merge_data = {};
+  merge_data.fd2 = fence_fd2;
+  base::strlcpy(merge_data.name, kFenceName, sizeof(merge_data.name));
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(fence_fd1, SYNC_IOC_MERGE, &merge_data));
+  EXPECT_EQ(0, errno);
+  int merged_fence_fd = merge_data.fence;
+  ASSERT_TRUE(merged_fence_fd);
+  EXPECT_NE(merged_fence_fd, fence_fd1);
+  EXPECT_NE(merged_fence_fd, fence_fd2);
+
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info) * 2);
+
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(merged_fence_fd, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(1U, SyncPtInfoCount(info));
+
+  SyncPoint* pt = GetSyncPoint(merged_fence_fd, 0);
+  EXPECT_EQ(1U, pt->signaling_time());
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fence_fd2));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(merged_fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_MERGE_SingleTimeline) {
+  int fence_fd1 = timeline_->CreateFence(kFenceName, 1);
+  int fence_fd2 = timeline_->CreateFence(kFenceName, 2);
+
+  sync_merge_data merge_data = {};
+  merge_data.fd2 = fence_fd2;
+  base::strlcpy(merge_data.name, kFenceName, sizeof(merge_data.name));
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(fence_fd1, SYNC_IOC_MERGE, &merge_data));
+  EXPECT_EQ(0, errno);
+  int merged_fence_fd = merge_data.fence;
+  ASSERT_TRUE(merged_fence_fd);
+
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info) * 2);
+
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(merged_fence_fd, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(1U, SyncPtInfoCount(info));
+
+  // Only future point should be remained.
+  SyncPoint* pt = GetSyncPoint(merged_fence_fd, 0);
+  EXPECT_EQ(2U, pt->signaling_time());
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fence_fd2));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(merged_fence_fd));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_MERGE_MultiTimeline) {
+  scoped_refptr<TestableTimeline> timeline2 = new TestableTimeline();
+
+  int fence_fd1 = timeline_->CreateFence(kFenceName, 1);
+  int fence_fd2 = timeline2->CreateFence(kFenceName, 2);
+
+  sync_merge_data merge_data = {};
+  merge_data.fd2 = fence_fd2;
+  base::strlcpy(merge_data.name, kFenceName, sizeof(merge_data.name));
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(fence_fd1, SYNC_IOC_MERGE, &merge_data));
+  EXPECT_EQ(0, errno);
+  int merged_fence = merge_data.fence;
+
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info) * 2);
+
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(merged_fence, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(2U, SyncPtInfoCount(info));
+
+  SyncPoint* tl1_pt = GetSyncPoint(merged_fence, 0);
+  SyncPoint* tl2_pt = GetSyncPoint(merged_fence, 1);
+  // The order of the sync point is not specified.
+  if (GetTimeline(merged_fence, tl1_pt) != timeline_)
+    std::swap(tl1_pt, tl2_pt);
+
+  EXPECT_EQ(1U, tl1_pt->signaling_time());
+  EXPECT_EQ(2U, tl2_pt->signaling_time());
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fence_fd2));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(merged_fence));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_MERGE_DuringWait) {
+  int fence_fd1 = timeline_->CreateFence(kFenceName, 1);
+  int fence_fd2 = timeline_->CreateFence(kFenceName, 2);
+
+  ThreadedWaiter waiter1(fence_fd1, kDefaultTimeoutInMs);
+  ThreadedWaiter waiter2(fence_fd2, kDefaultTimeoutInMs);
+  waiter1.StartAndBlockUntilReady();
+  waiter2.StartAndBlockUntilReady();
+
+  sync_merge_data merge_data = {};
+  merge_data.fd2 = fence_fd2;
+  base::strlcpy(merge_data.name, kFenceName, sizeof(merge_data.name));
+  int request = SYNC_IOC_MERGE;
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(fence_fd1, request, &merge_data));
+  EXPECT_EQ(0, errno);
+  int merged_fence = merge_data.fence;
+
+  std::vector<uint8_t> buffer;
+  sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+      &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info) * 2);
+
+  errno = 0;
+  EXPECT_EQ(0, Ioctl(merged_fence, SYNC_IOC_FENCE_INFO, info));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(1U, SyncPtInfoCount(info));
+
+  // Only future point should be remained.
+  SyncPoint* pt = GetSyncPoint(merged_fence, 0);
+  EXPECT_EQ(2U, pt->signaling_time());
+
+  timeline_->IncrementCounter(10000);
+  waiter1.Join();
+  waiter2.Join();
+  EXPECT_EQ(0, waiter1.result());
+  EXPECT_EQ(0, waiter2.result());
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fence_fd2));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(merged_fence));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FenceStreamTest, SYNC_IOC_MERGE_ThreadedMerge) {
+  scoped_refptr<TestableTimeline> timeline2 = new TestableTimeline();
+
+  int fence_fd1 = timeline_->CreateFence(kFenceName, 1);
+  int fence_fd2 = timeline2->CreateFence(kFenceName, 2);
+
+  base::WaitableEvent event(true /* manual reset */, false /* Not signaled */);
+
+  const size_t kThreadCount = 10U;
+  ScopedVector<ThreadedMerger> mergers;
+  mergers.resize(kThreadCount);
+  for (size_t i = 0; i < kThreadCount; ++i) {
+    std::vector<int> fd1(1);
+    fd1[0] = fence_fd1;
+    std::vector<int> fd2(1);
+    fd2[0] = fence_fd2;
+    mergers[i] = new ThreadedMerger(fd1, fd2, &event);
+    mergers[i]->Start();
+  }
+
+  event.Signal();  // Wake up all merger threads.
+
+  for (size_t i = 0; i < kThreadCount; ++i) {
+    mergers[i]->Join();
+
+    int merged_fence_fd = mergers[i]->GetMergedFenceFd(0);
+
+    std::vector<uint8_t> buffer;
+    sync_fence_info_data* info = AllocateFenceInfoDataBuffer(
+        &buffer, sizeof(sync_fence_info_data) + sizeof(sync_pt_info) * 2);
+
+    errno = 0;
+    EXPECT_EQ(0, Ioctl(merged_fence_fd, SYNC_IOC_FENCE_INFO, info));
+    EXPECT_EQ(0, errno);
+    EXPECT_EQ(2U, SyncPtInfoCount(info));
+
+    SyncPoint* tl1_pt = GetSyncPoint(merged_fence_fd, 0);
+    SyncPoint* tl2_pt = GetSyncPoint(merged_fence_fd, 1);
+
+    // The order of the sync point is not specified.
+    if (GetTimeline(merged_fence_fd, tl1_pt) != timeline_)
+      std::swap(tl1_pt, tl2_pt);
+
+    EXPECT_EQ(1U, tl1_pt->signaling_time());
+    EXPECT_EQ(2U, tl2_pt->signaling_time());
+
+    errno = 0;
+    EXPECT_EQ(0, file_system_->close(merged_fence_fd));
+    EXPECT_EQ(0, errno);
+  }
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fence_fd1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fence_fd2));
+  EXPECT_EQ(0, errno);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/file_stream.cc b/src/posix_translation/file_stream.cc
new file mode 100644
index 0000000..f4c7993
--- /dev/null
+++ b/src/posix_translation/file_stream.cc
@@ -0,0 +1,462 @@
+// Copyright 2014 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.
+//
+// Implements functions of the base file API interface.
+
+#include "posix_translation/file_stream.h"
+
+#include <algorithm>
+
+#include "base/memory/scoped_ptr.h"
+#include "common/alog.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+namespace {
+
+// Verifies the |iov| and |iovcnt|, and returns appropriate error code
+// If verification successfully passes, returns 0.
+// Along with the verification, |total_size| is returned on success.
+// Note that this does not modify |errno|.
+int VerifyIoVec(const struct iovec* iov, int iovcnt, ssize_t* out_total_size) {
+  ALOG_ASSERT(out_total_size != NULL);
+  if (iovcnt < 0 || UIO_MAXIOV < iovcnt)
+    return EINVAL;
+
+  // Then check size overflow.
+  ssize_t total_size = 0;
+  for (int i = 0; i < iovcnt; ++i) {
+    if (iov[i].iov_len > static_cast<size_t>(SSIZE_MAX - total_size))
+      return EINVAL;
+    total_size += iov[i].iov_len;
+  }
+
+  *out_total_size = total_size;
+  return 0;
+}
+
+}  // namespace
+
+FileStream::FileStream(int oflag, const std::string& pathname)
+    : oflag_(oflag), inode_(kBadInode), pathname_(pathname),
+      is_listening_enabled_(false), file_ref_count_(0),
+      had_file_refs_(false) {
+  // When the stream is not associated with a file (e.g. socket), |pathname|
+  // is empty.
+  if (!pathname_.empty()) {
+    // Claim a unique inode for the pathname before the file is unlinked.
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    inode_ = sys->GetInodeLocked(pathname_);
+  }
+}
+
+FileStream::~FileStream() {
+  // Make sure it was never properly opened, or has no remaining file refs.
+  ALOG_ASSERT(!had_file_refs_ || file_ref_count_ == 0);
+}
+
+bool FileStream::IsAllowedOnMainThread() const {
+  return false;
+}
+
+bool FileStream::ReturnsSameAddressForMultipleMmaps() const {
+  return false;
+}
+
+bool FileStream::IsClosed() const {
+  return had_file_refs_ && file_ref_count_ == 0;
+}
+
+void FileStream::CheckNotClosed() const {
+  ALOG_ASSERT(!IsClosed());
+}
+
+void FileStream::AddFileRef() {
+  CheckNotClosed();
+  file_ref_count_++;
+  had_file_refs_ = true;
+}
+
+void FileStream::ReleaseFileRef() {
+  CheckNotClosed();
+  ALOG_ASSERT(had_file_refs_);
+  ALOG_ASSERT(file_ref_count_ > 0);
+
+  file_ref_count_--;
+  if (file_ref_count_ != 0)
+    return;
+
+  // Clear listeners first to prevent OnLastFileRef() from notifying them.
+  for (FileMap::iterator it = listeners_.begin();
+       it != listeners_.end(); it++) {
+    it->second->HandleNotificationFrom(this, true);
+  }
+  listeners_.clear();
+
+  OnLastFileRef();
+}
+
+int FileStream::accept(sockaddr* addr, socklen_t* addrlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::bind(const sockaddr* addr, socklen_t addrlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::connect(const sockaddr* addr, socklen_t addrlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::epoll_ctl(
+    int op, scoped_refptr<FileStream> file, struct epoll_event* event) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::epoll_wait(struct epoll_event* events, int maxevents,
+                           int timeout) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::fcntl(int cmd, va_list ap) {
+  switch (cmd) {
+    case F_GETFD:
+    case F_SETFD:
+      // Ignore since we do not support exec().
+      return 0;
+    case F_GETLK: {
+      struct flock* lk = va_arg(ap, struct flock*);
+      if (lk) {
+        memset(lk, 0, sizeof(struct flock));
+        lk->l_type = F_UNLCK;
+      }
+      return 0;
+    }
+    case F_SETLK:
+    case F_SETLKW:
+    case F_SETLK64:
+    case F_SETLKW64:
+      return 0;
+    case F_GETFL:
+      // TODO(yusukes): Exclude file creation flags.
+      return oflag();
+    case F_SETFL:
+      set_oflag(va_arg(ap, long));  // NOLINT(runtime/int)
+      return 0;
+  }
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::fdatasync() {
+  return 0;
+}
+
+int FileStream::fstat(struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  return 0;
+}
+
+int FileStream::fsync() {
+  return 0;
+}
+
+int FileStream::ftruncate(off64_t length) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::getdents(dirent* buf, size_t count) {
+  errno = ENOTDIR;
+  return -1;
+}
+
+int FileStream::getpeername(sockaddr* name, socklen_t* namelen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::getsockname(sockaddr* name, socklen_t* namelen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::getsockopt(int level, int optname, void* optval,
+                           socklen_t* optlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::ioctl(int request, va_list ap) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::listen(int backlog) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+off64_t FileStream::lseek(off64_t offset, int whence) {
+  // There is no good default error code for most files. Sockets should
+  // return ESPIPE, but there is no documented errno for non-seekable files.
+  errno = EINVAL;
+  return -1;
+}
+
+int FileStream::madvise(void* addr, size_t length, int advice) {
+  // Accept advices that are supported, or do not have visible side effects.
+  switch (advice) {
+    case MADV_NORMAL:
+    case MADV_RANDOM:
+    case MADV_SEQUENTIAL:
+    case MADV_WILLNEED:
+    case MADV_SOFT_OFFLINE:
+    case MADV_MERGEABLE:
+    case MADV_UNMERGEABLE:
+    case MADV_NOHUGEPAGE:
+      // Theese advices can be ignored safely.
+      break;
+    case MADV_DONTNEED:
+      // Has a FileStream dependent side affects. Should be handlded by
+      // inheritance.
+      // TODO(crbug.com/425955): Only PassthroughStream and DevAshmem have
+      // implementation. If needed, implement this function for other streams.
+      errno = EINVAL;
+      return -1;
+    case MADV_REMOVE:
+      // Linux supports it only on shmfs/tmpfs.
+      errno = ENOSYS;
+      return -1;
+    case MADV_DONTFORK:
+      // Contrary to the madvise(2) man page, MADV_DONTFORK does influence the
+      // semantics of the application. MADV_DONTFORK'ed pages must not be
+      // available to the child process, and if the process touches the page,
+      // it must crash. Returning 0 for now since we do not support fork().
+      ALOGE("MADV_DONTFORK for address %p is ignored.", addr);
+      break;
+    case MADV_DOFORK:
+      // The same. Write an error message just in case.
+      ALOGE("MADV_DOFORK for address %p is ignored.", addr);
+      break;
+    default:
+      // Handle an unknown advice, and MADV_HWPOISON, MADV_HUGEPAGE, and
+      // MADV_DONTDUMP that are not defined in NaCl.
+      errno = EINVAL;
+      return -1;
+  }
+  return 0;
+}
+
+void* FileStream::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  errno = ENODEV;
+  return MAP_FAILED;
+}
+
+int FileStream::mprotect(void* addr, size_t length, int prot) {
+  return ::mprotect(addr, length, prot);
+}
+
+int FileStream::munmap(void* addr, size_t length) {
+  errno = ENODEV;
+  return -1;
+}
+
+ssize_t FileStream::pread(void* buf, size_t count, off64_t offset) {
+  // Implementing pread with lseek-lseek-read-lseek is somewhat slow but works
+  // thanks to the giant mutex lock in VirtualFileSystem.
+  // TODO(crbug.com/269075): Switch to pread IRT once it is implemented for
+  // better performance.
+  const off64_t original = this->lseek(0, SEEK_CUR);
+  if (original == -1)
+    return -1;
+  if (this->lseek(offset, SEEK_SET) == -1)
+    return -1;
+  const ssize_t result = this->read(buf, count);
+  const off64_t now = this->lseek(original, SEEK_SET);
+  ALOG_ASSERT(original == now);
+  return result;
+}
+
+ssize_t FileStream::pwrite(const void* buf, size_t count, off64_t offset) {
+  // Linux kernel ignores |offset| when the file is opened with O_APPEND.
+  // Emulate the behavior.
+  if (oflag_ & O_APPEND) {
+    ARC_STRACE_REPORT("in O_APPEND mode. redirecting to write");
+    return this->write(buf, count);
+  }
+  return this->PwriteImpl(buf, count, offset);
+}
+
+ssize_t FileStream::PwriteImpl(const void* buf, size_t count, off64_t offset) {
+  const off64_t original = this->lseek(0, SEEK_CUR);
+  if (original == -1)
+    return -1;
+  if (this->lseek(offset, SEEK_SET) == -1)
+    return -1;
+  const ssize_t result = this->write(buf, count);
+  const off64_t now = this->lseek(original, SEEK_SET);
+  ALOG_ASSERT(original == now);
+  return result;
+}
+
+ssize_t FileStream::readv(const struct iovec* iov, int count) {
+  ssize_t total;
+  int error = VerifyIoVec(iov, count, &total);
+  if (error != 0) {
+    errno = error;
+    return -1;
+  }
+  if (total == 0)
+    return 0;
+
+  scoped_ptr<char[]> buffer(new char[total]);
+  ssize_t result = this->read(buffer.get(), total);
+  if (result < 0) {
+    // An error is found in read(). |errno| should be set in it.
+    return result;
+  }
+
+  // Copy to the iov.
+  ssize_t current = 0;
+  for (int i = 0; i < count && current < result; ++i) {
+    size_t copy_size = std::min<size_t>(result - current, iov[i].iov_len);
+    memcpy(iov[i].iov_base, &buffer[current], copy_size);
+    current += copy_size;
+  }
+
+  ALOG_ASSERT(current == result);
+  return result;
+}
+
+ssize_t FileStream::writev(const struct iovec* iov, int count) {
+  ssize_t total;
+  int error = VerifyIoVec(iov, count, &total);
+  if (error != 0) {
+    errno = error;
+    return -1;
+  }
+  if (total == 0) {
+    return 0;
+  }
+
+  scoped_ptr<char[]> buffer(new char[total]);
+  size_t offset = 0;
+  for (int i = 0; i < count; i++) {
+    memcpy(&buffer[offset], iov[i].iov_base, iov[i].iov_len);
+    offset += iov[i].iov_len;
+  }
+  return this->write(buffer.get(), total);
+}
+
+ssize_t FileStream::recv(void* buf, size_t len, int flags) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+ssize_t FileStream::recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                             socklen_t* addrlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+ssize_t FileStream::recvmsg(struct msghdr* msg, int flags) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+ssize_t FileStream::send(const void* buf, size_t len, int flags) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+ssize_t FileStream::sendto(const void* buf, size_t len, int flags,
+                           const sockaddr* dest_addr, socklen_t addrlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+ssize_t FileStream::sendmsg(const struct msghdr* msg, int flags) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+int FileStream::setsockopt(int level, int optname, const void* optval,
+                           socklen_t optlen) {
+  errno = ENOTSOCK;
+  return -1;
+}
+
+bool FileStream::IsSelectReadReady() const {
+  return true;
+}
+
+bool FileStream::IsSelectWriteReady() const {
+  return true;
+}
+
+bool FileStream::IsSelectExceptionReady() const {
+  return false;
+}
+
+size_t FileStream::GetSize() const {
+  return 0;
+}
+
+std::string FileStream::GetAuxInfo() const {
+  return std::string();
+}
+
+void FileStream::OnLastFileRef() {
+}
+
+void FileStream::OnUnmapByOverwritingMmap(void* addr, size_t length) {
+}
+
+int16_t FileStream::GetPollEvents() const {
+  return POLLIN | POLLOUT;
+}
+
+void FileStream::NotifyListeners() {
+  ALOG_ASSERT(is_listening_enabled_,
+              "Cannot notify listeners when file cannot be listened to");
+  if (IsClosed())
+    return;  // Likely processing the last read event.
+  for (FileMap::iterator it = listeners_.begin();
+      it != listeners_.end(); it++) {
+    it->second->CheckNotClosed();
+    it->second->HandleNotificationFrom(this, false);
+  }
+}
+
+void FileStream::HandleNotificationFrom(
+    scoped_refptr<FileStream> file, bool is_closing) {
+  // Whoever added itself as a listener must be able to handle notifications.
+  ALOG_ASSERT(false, "FileStream listener '%s' does not handle notifications",
+              GetStreamType());
+}
+
+bool FileStream::StartListeningTo(scoped_refptr<FileStream> file) {
+  if (!file->is_listening_enabled_)
+    return false;
+  CheckNotClosed();
+  file->CheckNotClosed();
+  ALOG_ASSERT(file->listeners_.count(this) == 0,
+              "Cannot add the same listener twice");
+  file->listeners_.insert(std::make_pair(this, this));
+  return true;
+}
+
+void FileStream::StopListeningTo(scoped_refptr<FileStream> file) {
+  file->listeners_.erase(this);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/file_stream.h b/src/posix_translation/file_stream.h
new file mode 100644
index 0000000..18471ed
--- /dev/null
+++ b/src/posix_translation/file_stream.h
@@ -0,0 +1,211 @@
+// Copyright 2014 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.
+//
+// Base interface for file API.
+
+#ifndef POSIX_TRANSLATION_FILE_STREAM_H_
+#define POSIX_TRANSLATION_FILE_STREAM_H_
+
+#include <dirent.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include <sys/mman.h>  // MAP_FAILED
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "common/arc_strace.h"
+#include "posix_translation/permission_info.h"
+
+namespace posix_translation {
+
+class FileStream : public base::RefCounted<FileStream> {
+ public:
+  FileStream(int oflag, const std::string& pathname);
+
+  const PermissionInfo& permission() const {
+    return permission_;
+  }
+  void set_permission(const PermissionInfo& permission) {
+    permission_ = permission;
+  }
+
+  virtual bool IsAllowedOnMainThread() const;
+
+  // Returns true if FileStream that returns the same address when mmap() is
+  // called twice or more. Such a stream needs a special handling in
+  // MemoryRegion.
+  virtual bool ReturnsSameAddressForMultipleMmaps() const;
+
+  // Adds a file reference to allow the object to call OnLastFileRef() later
+  // when the file reference count is dropped to zero.
+  void AddFileRef();
+  // Releases a file reference. OnLastFileRef() might be called.
+  void ReleaseFileRef();
+
+  // Sorted by syscall name.
+  virtual int accept(sockaddr* addr, socklen_t* addrlen);
+  virtual int bind(const sockaddr* addr, socklen_t addrlen);
+  virtual int connect(const sockaddr* addr, socklen_t addrlen);
+  virtual int epoll_ctl(
+      int op, scoped_refptr<FileStream> file, struct epoll_event* event);
+  virtual int epoll_wait(struct epoll_event* events, int maxevents,
+                         int timeout);
+  virtual int fcntl(int cmd, va_list ap);
+  virtual int fdatasync();
+  virtual int fstat(struct stat* out);
+  virtual int fsync();
+  virtual int ftruncate(off64_t length);
+  virtual int getdents(dirent* buf, size_t count);
+  virtual int getpeername(sockaddr* name, socklen_t* namelen);
+  virtual int getsockname(sockaddr* name, socklen_t* namelen);
+  virtual int getsockopt(int level, int optname, void* optval,
+                         socklen_t* optlen);
+  virtual int ioctl(int request, va_list ap);
+  virtual int listen(int backlog);
+  virtual off64_t lseek(off64_t offset, int whence);
+  // If madvise returns 1, VFS should abort immediately.
+  virtual int madvise(void* addr, size_t length, int advice);
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset);
+  // If mprotect returns 1, VFS should abort immediately.
+  virtual int mprotect(void* addr, size_t length, int prot);
+  virtual int munmap(void* addr, size_t length);
+  virtual ssize_t pread(void* buf, size_t count, off64_t offset);
+  virtual ssize_t read(void* buf, size_t count) = 0;
+  virtual ssize_t readv(const struct iovec* iov, int count);
+  virtual ssize_t recv(void* buf, size_t len, int flags);
+  virtual ssize_t recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                           socklen_t* addrlen);
+  virtual ssize_t recvmsg(struct msghdr* msg, int flags);
+  virtual ssize_t send(const void* buf, size_t len, int flags);
+  virtual ssize_t sendto(const void* buf, size_t len, int flags,
+                         const sockaddr* dest_addr, socklen_t addrlen);
+  virtual ssize_t sendmsg(const struct msghdr* msg, int flags);
+  virtual int setsockopt(int level, int optname, const void* optval,
+                         socklen_t optlen);
+  // Note: In write() and writev(), do not call any function which
+  // directly or indirectly calls ::write() or libc's write().
+  // It will be trapped at IRT layer and may loop back to VirtualFileSystem and
+  // FileStream. printf() and fprintf() are good examples. ARC logging
+  // functions in common/alog.h and common/arc_strace.h are safe to call.
+  virtual ssize_t write(const void* buf, size_t count) = 0;
+  virtual ssize_t writev(const struct iovec* iov, int count);
+
+  // For the implementation of select().
+  // Streams which support select must override these 3 functions.
+  // Implementations of these IsSelect*Ready() functions *must* return
+  // immediately without communicating with the main thread. Otherwise, select
+  // syscall families with short timeout might not work as expected.
+  virtual bool IsSelectReadReady() const;
+  virtual bool IsSelectWriteReady() const;
+  virtual bool IsSelectExceptionReady() const;
+
+  // For the implementation of poll().
+  // Returns the bits of poll events, e.g. (POLLIN | POLLOUT).
+  // This function *must* return immediately without communicating with the
+  // main thread.
+  virtual int16_t GetPollEvents() const;
+
+  // TODO(crbug.com/359400): Currently, poll uses IsSelect*Ready() family
+  // incorrectly, due to historical reason. Fix the implementation.
+
+  // Called when the memory region [addr, addr+length) associated when the
+  // stream is implicitly unmapped without munmap. This happens with the
+  // region is overwritten by another mmap call with MAP_FIXED. File handlers
+  // that do not support the implicit unmap with MAP_FIXED should override
+  // this function to call abort.
+  // TODO(crbug.com/418801): Remove once we fix 418801 and change dev_ashmem.cc
+  // to use a shared memory IRT which does not exist today.
+  virtual void OnUnmapByOverwritingMmap(void* addr, size_t length);
+
+  // For debugging.
+  virtual const char* GetStreamType() const = 0;
+  virtual size_t GetSize() const;
+  virtual std::string GetAuxInfo() const;
+
+  // A non-virtual wrapper around write() and PwriteImpl().
+  ssize_t pwrite(const void* buf, size_t count, off64_t offset);
+
+  // Debug check verifying that this file has not lost its last reference.
+  void CheckNotClosed() const;
+
+  int oflag() const { return oflag_; }
+  void set_oflag(int oflag) { oflag_ = oflag; }
+  ino_t inode() const { return inode_; }
+  const std::string& pathname() const { return pathname_; }
+
+ protected:
+  friend class base::RefCounted<FileStream>;
+  virtual ~FileStream();
+
+  // Invoked by the non-virtual pwrite() above.
+  virtual ssize_t PwriteImpl(const void* buf, size_t count, off64_t offset);
+
+  // Invoked upon release of the last file reference.
+  virtual void OnLastFileRef();
+
+  // TODO(crbug.com/284239): Functions below are mostly for socket
+  // related classes. Create a base class for them and move them to
+  // the new class.
+
+  // Allows this file to be listened to.
+  void EnableListenerSupport() {
+    is_listening_enabled_ = true;
+  }
+
+  // Listener invokes this on itself to start listening to a particular file.
+  // Returns false if file does not support listeners (cannot notify them).
+  bool StartListeningTo(scoped_refptr<FileStream> file);
+
+  // Listener invokes this on itself to stop listening to a particular file.
+  void StopListeningTo(scoped_refptr<FileStream> file);
+
+  // Notifies all registered listeners.
+  void NotifyListeners();
+
+  // Called on listener to notify about a change in file.
+  virtual void HandleNotificationFrom(
+      scoped_refptr<FileStream> file, bool is_closing);
+
+  // Returns true if this file has lost its last reference.
+  bool IsClosed() const;
+
+ private:
+  // The key is FileStream*, obfuscated to avoid direct use.
+  typedef std::map<void*, scoped_refptr<FileStream> > FileMap;
+
+  int oflag_;
+  // -1 when the stream is not associated with a file (e.g. socket).
+  ino_t inode_;
+  // "" when the stream is not associated with a file (e.g. socket).
+  const std::string pathname_;
+  bool is_listening_enabled_;
+  FileMap listeners_;
+  // Permission of this file. VirtualFileSystem sets this value for
+  // FileStream created by FileSystemHandler. Other FileStream should fill
+  // this by themselves.
+  PermissionInfo permission_;
+  // The number of open-file references this stream currently has.
+  // It is different from base::RefCounted, as the latter merely counts
+  // the code references and prevents the object from being destroyed.
+  // file_ref_count_, on the other hand, allows tracking of the actual
+  // use count, such as with open or duplicated fd's.
+  int file_ref_count_;
+  // True if this stream ever had positive file_ref_count_.
+  // This field is needed for integrity checks only.
+  bool had_file_refs_;
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_FILE_STREAM_H_
diff --git a/src/posix_translation/file_stream_test.cc b/src/posix_translation/file_stream_test.cc
new file mode 100644
index 0000000..424066c
--- /dev/null
+++ b/src/posix_translation/file_stream_test.cc
@@ -0,0 +1,271 @@
+// Copyright 2014 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 <errno.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include <limits>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "gtest/gtest.h"
+#include "posix_translation/file_stream.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+class FileStreamTest : public FileSystemTestCommon {
+ protected:
+  FileStreamTest() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileStreamTest);
+};
+
+class TestFileStream : public FileStream {
+ public:
+  TestFileStream()
+      : FileStream(O_RDONLY, "/dummy/file.name"),
+        last_read_buf_(NULL), last_read_count_(0), last_write_count_(0),
+        on_last_file_ref_(0), on_handle_notification_from_(0), last_file_(NULL),
+        last_is_closing_(false) {
+  }
+  virtual ~TestFileStream() {
+  }
+
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE {
+    last_read_buf_ = buf;
+    last_read_count_ = count;
+    return count;
+  }
+
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE {
+    // In this test, |buf| always points to an ASCII string.
+    last_write_buf_ = std::string(static_cast<const char*>(buf), count);
+    last_write_count_ = count;
+    return count;
+  }
+
+  virtual const char* GetStreamType() const OVERRIDE {
+    return "test";
+  }
+
+  virtual void OnLastFileRef() OVERRIDE {
+    ++on_last_file_ref_;
+  }
+
+  virtual void HandleNotificationFrom(
+      scoped_refptr<FileStream> file, bool is_closing) OVERRIDE {
+    ++on_handle_notification_from_;
+    last_file_ = file.get();
+    last_is_closing_ = is_closing;
+  }
+
+  void EnableListenerSupport() {
+    FileStream::EnableListenerSupport();
+  }
+
+  bool StartListeningTo(scoped_refptr<FileStream> file) {
+    return FileStream::StartListeningTo(file);
+  }
+
+  void StopListeningTo(scoped_refptr<FileStream> file) {
+    FileStream::StopListeningTo(file);
+  }
+
+  void* last_read_buf_;
+  size_t last_read_count_;
+  std::string last_write_buf_;
+  size_t last_write_count_;
+  int on_last_file_ref_;
+  int on_handle_notification_from_;
+  FileStream* last_file_;  // use a raw pointer not to change the ref count.
+  bool last_is_closing_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestFileStream);
+};
+
+TEST_F(FileStreamTest, TestConstruct) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  stream->CheckNotClosed();
+  EXPECT_EQ(0, stream->on_last_file_ref_);
+}
+
+TEST_F(FileStreamTest, TestFileRef) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  stream->AddFileRef();
+  stream->CheckNotClosed();
+  stream->ReleaseFileRef();
+  EXPECT_EQ(1, stream->on_last_file_ref_);
+}
+
+TEST_F(FileStreamTest, TestMultipleFileRef) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  stream->AddFileRef();
+  {
+    scoped_refptr<FileStream> another_ref = stream;
+    stream->AddFileRef();
+    stream->CheckNotClosed();
+    stream->ReleaseFileRef();
+  }
+  EXPECT_EQ(0, stream->on_last_file_ref_);
+  stream->CheckNotClosed();
+  stream->ReleaseFileRef();
+  EXPECT_EQ(1, stream->on_last_file_ref_);
+}
+
+TEST_F(FileStreamTest, TestNotifications) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  stream->EnableListenerSupport();
+  stream->AddFileRef();
+  scoped_refptr<TestFileStream> stream2 = new TestFileStream;
+  scoped_refptr<TestFileStream> stream3 = new TestFileStream;
+  scoped_refptr<TestFileStream> stream4 = new TestFileStream;
+  EXPECT_TRUE(stream2->StartListeningTo(stream));
+  EXPECT_TRUE(stream3->StartListeningTo(stream));
+  EXPECT_TRUE(stream4->StartListeningTo(stream));
+  stream3->StopListeningTo(stream);
+
+  // Remove a file ref from |stream|,
+  EXPECT_EQ(0, stream2->on_handle_notification_from_);
+  EXPECT_EQ(0, stream3->on_handle_notification_from_);
+  EXPECT_EQ(0, stream4->on_handle_notification_from_);
+  stream->CheckNotClosed();
+  stream->ReleaseFileRef();
+
+  // then confirm that |stream2| and |stream4| receive notifications from
+  // |stream|.
+  EXPECT_EQ(1, stream2->on_handle_notification_from_);
+  EXPECT_EQ(0, stream3->on_handle_notification_from_);
+  EXPECT_EQ(1, stream4->on_handle_notification_from_);
+  EXPECT_EQ(stream.get(), stream2->last_file_);
+  EXPECT_TRUE(NULL == stream3->last_file_);  // unchanged
+  EXPECT_EQ(stream.get(), stream4->last_file_);
+  EXPECT_TRUE(stream2->last_is_closing_);
+  EXPECT_FALSE(stream3->last_is_closing_);  // unchanged
+  EXPECT_TRUE(stream4->last_is_closing_);
+}
+
+TEST_F(FileStreamTest, TestNotificationsWithTighterScope) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  stream->EnableListenerSupport();
+  stream->AddFileRef();
+  {
+    scoped_refptr<TestFileStream> stream2 = new TestFileStream;
+    EXPECT_TRUE(stream2->StartListeningTo(stream));
+  }
+  // Confirm this does not crash. The second stream is still alive here since
+  // stream->listeners_ holds a reference to the second stream.
+  stream->ReleaseFileRef();
+}
+
+TEST_F(FileStreamTest, TestPermission) {
+  scoped_refptr<FileStream> stream = new TestFileStream;
+  // Test the default permission info.
+  EXPECT_FALSE(stream->permission().IsValid());
+  EXPECT_FALSE(stream->permission().is_writable());
+
+  // Test setter/getter.
+  static const uid_t kFileUid = 54321;
+  static const bool kIsWritable = true;
+  stream->set_permission(PermissionInfo(kFileUid, kIsWritable));
+  EXPECT_TRUE(stream->permission().IsValid());
+  EXPECT_EQ(kFileUid, stream->permission().file_uid());
+  EXPECT_EQ(kIsWritable, stream->permission().is_writable());
+}
+
+TEST_F(FileStreamTest, TestReadV) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  struct iovec iov[2] = {};
+
+  // Test invalid args.
+  errno = 0;
+  EXPECT_EQ(-1, stream->readv(iov, -1));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, stream->readv(NULL, -1));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test 0 byte readv.
+  errno = 0;
+  EXPECT_EQ(0, stream->readv(iov, 0));
+  EXPECT_EQ(0, errno);
+
+  // Test a valid readv call.
+  errno = 0;
+  char buf0[128] = {};
+  char buf1[64] = {};
+  iov[0].iov_base = buf0;
+  iov[0].iov_len = sizeof(buf0);
+  iov[1].iov_base = buf1;
+  iov[1].iov_len = sizeof(buf1);
+  const ssize_t result = stream->readv(iov, 2);
+  // Confirm that the result is 0 < result <= sum_of_the_buffer_lengths.
+  // Note that our current implementation always performs short-read which
+  // should be POSIX compliant.
+  EXPECT_LT(0, result);
+  EXPECT_GE(static_cast<ssize_t>(sizeof(buf0) + sizeof(buf1)), result);
+  // Do the same check for last_read_count_.
+  EXPECT_LT(0U, stream->last_read_count_);
+  EXPECT_GE(sizeof(buf0) + sizeof(buf1), stream->last_read_count_);
+  EXPECT_EQ(0, errno);
+}
+
+TEST_F(FileStreamTest, TestWriteV) {
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  struct iovec iov[UIO_MAXIOV + 1] = {};  // 1023 items.
+
+  // Test invalid args.
+  errno = 0;
+  EXPECT_EQ(-1, stream->writev(iov, -1));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, stream->writev(NULL, -1));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, stream->writev(iov, UIO_MAXIOV + 1));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test 0 byte writev.
+  errno = 0;
+  EXPECT_EQ(0, stream->writev(iov, 0));
+  EXPECT_EQ(0, errno);
+
+  // Test integer overflows.
+  errno = 0;
+  iov[0].iov_len = std::numeric_limits<size_t>::max() / 2;
+  iov[1].iov_len = iov[0].iov_len + 1;
+  EXPECT_EQ(-1, stream->writev(iov, 2));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test a valid writev call.
+  errno = 0;
+  char buf0[] = "buf0";
+  char buf1[] = "buf1";
+  iov[0].iov_base = buf0;
+  iov[0].iov_len = strlen(buf0);
+  iov[1].iov_base = buf1;
+  iov[1].iov_len = strlen(buf1);
+  const ssize_t result = stream->writev(iov, 2);
+  EXPECT_EQ(static_cast<ssize_t>(strlen(buf0) + strlen(buf1)), result);
+  EXPECT_EQ(strlen(buf0) + strlen(buf1), stream->last_write_count_);
+  EXPECT_STREQ("buf0buf1", stream->last_write_buf_.c_str());
+  EXPECT_EQ(0, errno);
+}
+
+TEST_F(FileStreamTest, TestMadvise) {
+  static const size_t kSize = 8;
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  EXPECT_EQ(0, stream->madvise(0, kSize, MADV_NORMAL));
+  EXPECT_EQ(-1, stream->madvise(0, kSize, MADV_DONTNEED));
+  EXPECT_EQ(EINVAL, errno);
+  EXPECT_EQ(-1, stream->madvise(0, kSize, MADV_REMOVE));
+  EXPECT_EQ(ENOSYS, errno);
+  EXPECT_EQ(-1, stream->madvise(0, kSize, ~0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/file_system_handler.cc b/src/posix_translation/file_system_handler.cc
new file mode 100644
index 0000000..5bd91fe
--- /dev/null
+++ b/src/posix_translation/file_system_handler.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 The Chromium OS 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 "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+FileSystemHandler::FileSystemHandler(const std::string& name)
+    : name_(name) {
+}
+
+FileSystemHandler::~FileSystemHandler() {
+}
+
+bool FileSystemHandler::IsInitialized() const {
+  return true;
+}
+
+void FileSystemHandler::Initialize() {
+}
+
+void FileSystemHandler::OnMounted(const std::string& path) {
+}
+
+void FileSystemHandler::OnUnmounted(const std::string& path) {
+}
+
+void FileSystemHandler::InvalidateCache() {
+}
+
+void FileSystemHandler::AddToCache(const std::string& path,
+                                   const PP_FileInfo& file_info,
+                                   bool exists) {
+}
+
+bool FileSystemHandler::IsWorldWritable(const std::string& pathname) {
+  struct stat st;
+  if (!this->stat(pathname, &st)) {
+    const mode_t mode = st.st_mode;
+    return (mode & S_IWUSR) && (mode & S_IWGRP) && (mode & S_IWOTH);
+  }
+  return false;  // |pathname| does not exist.
+}
+
+std::string FileSystemHandler::SetPepperFileSystem(
+    const pp::FileSystem* file_system,
+    const std::string& path_in_pepperfs,
+    const std::string& path_in_vfs) {
+  ALOGE("%s does not support Pepper filesystem.", name().c_str());
+  return "";
+}
+
+int FileSystemHandler::mkdir(const std::string& pathname, mode_t mode) {
+  errno = EEXIST;
+  return -1;
+}
+
+ssize_t FileSystemHandler::readlink(const std::string& pathname,
+                                    std::string* resolved) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileSystemHandler::remove(const std::string& pathname) {
+  errno = EACCES;
+  return -1;
+}
+
+int FileSystemHandler::rename(const std::string& oldpath,
+                              const std::string& newpath) {
+  if (oldpath == newpath)
+    return 0;
+  errno = EACCES;
+  return -1;
+}
+
+int FileSystemHandler::rmdir(const std::string& pathname) {
+  errno = EACCES;
+  return -1;
+}
+
+int FileSystemHandler::symlink(const std::string& oldpath,
+                               const std::string& newpath) {
+  errno = EPERM;
+  return -1;
+}
+
+int FileSystemHandler::truncate(const std::string& pathname, off64_t length) {
+  errno = EINVAL;
+  return -1;
+}
+
+int FileSystemHandler::unlink(const std::string& pathname) {
+  errno = EACCES;
+  return -1;
+}
+
+int FileSystemHandler::utimes(const std::string& pathname,
+                              const struct timeval times[2]) {
+  errno = EPERM;
+  return -1;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/file_system_handler.h b/src/posix_translation/file_system_handler.h
new file mode 100644
index 0000000..8a1cde6
--- /dev/null
+++ b/src/posix_translation/file_system_handler.h
@@ -0,0 +1,120 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_FILE_SYSTEM_HANDLER_H_
+#define POSIX_TRANSLATION_FILE_SYSTEM_HANDLER_H_
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/strings/string16.h"
+#include "posix_translation/file_stream.h"
+
+namespace pp {
+class FileSystem;
+}  // namespace pp
+
+struct PP_FileInfo;
+
+namespace posix_translation {
+
+class Dir;
+
+// This class is a contract used by the VirtualFileSystem to make a
+// concrete/physical file system available at a certain path in the virtual
+// file system.
+class FileSystemHandler {
+ public:
+  explicit FileSystemHandler(const std::string& name);
+  virtual ~FileSystemHandler();
+
+  const std::string& name() const { return name_; }
+
+  // Returns true if and only if syscalls below are ready to be called.
+  virtual bool IsInitialized() const;
+  // A derived class can override this method to do an initialization on a
+  // non-main thread with VirtualFileSystem::mutex_ locked.
+  virtual void Initialize();
+
+  // Called when the handler is mounted/unmounted to/from the |path|.
+  virtual void OnMounted(const std::string& path);
+  virtual void OnUnmounted(const std::string& path);
+
+  // Called when all cached data in the handler should be discarded.
+  virtual void InvalidateCache();
+  // Cache |file_info|.
+  // TODO(yusukes): Change the type of |file_info| to a non-Pepper one.
+  virtual void AddToCache(const std::string& path,
+                          const PP_FileInfo& file_info,
+                          bool exists);
+
+  // Returns true if |pathname| is writable regardless of the caller's UID.
+  // When |pathname| does not exist, returns false.
+  virtual bool IsWorldWritable(const std::string& pathname);
+
+  // Sets the Pepper filesystem.
+  // This function is available only when the backend is Pepper file system,
+  // e.g. PepperFileHandler, CrxFileHandler or ExternalFileWrapperHandler.
+  // The |mount_source_in_pepper_file_system| is the absolute path of the file
+  // or directory in |pepper_file_system|. The |mount_dest_in_vfs| is the
+  // absolute path to the mount destination path in virtual file system. You can
+  // pass an empty string to |mount_dest_in_vfs| if you do not care about the
+  // mount position.
+  // This function returns absolute mounted path in virtual file system. The
+  // returned path is the same as |mount_dest_in_vfs| when it is not empty. When
+  // it is empty, the generated path in VFS is returned. If this function fails
+  // to mount the Pepper file system, returns an empty string.
+  virtual std::string SetPepperFileSystem(
+      const pp::FileSystem* pepper_file_system,
+      const std::string& mount_source_in_pepper_file_system,
+      const std::string& mount_dest_in_vfs);
+
+  // Sorted by syscall name. Note that we should always prefer
+  // 'const std::string&' over 'const char*' for a string parameter
+  // which is always non-NULL.
+  virtual int mkdir(const std::string& pathname, mode_t mode);
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) = 0;
+
+  // Called when the handler needs to provide the contents of the given
+  // directory which was provided to a DirectoryFileStream.
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) = 0;
+
+  // On success, returns the length of |resolved|. On error, returns -1 and
+  // updates errno.
+  virtual ssize_t readlink(const std::string& pathname, std::string* resolved);
+  virtual int remove(const std::string& pathname);
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath);
+  virtual int rmdir(const std::string& pathname);
+  // If permission bits of out->st_mode are not set in a handler,
+  // VirtualFileSystem will set the bits based of its file type.
+  virtual int stat(const std::string& pathname, struct stat* out) = 0;
+  virtual int statfs(const std::string& pathname, struct statfs* out) = 0;
+  virtual int symlink(const std::string& oldpath, const std::string& newpath);
+  virtual int truncate(const std::string& pathname, off64_t length);
+  virtual int unlink(const std::string& pathname);
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]);
+
+ private:
+  const std::string name_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileSystemHandler);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_FILE_SYSTEM_HANDLER_H_
diff --git a/src/posix_translation/file_wrap.cc b/src/posix_translation/file_wrap.cc
new file mode 100644
index 0000000..f8a1a7b
--- /dev/null
+++ b/src/posix_translation/file_wrap.cc
@@ -0,0 +1,1521 @@
+/* Copyright 2014 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.
+ *
+ * Wrappers for various file system calls.
+ */
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <nacl_stat.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/safe_strerror_posix.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "common/danger.h"
+#include "common/dlfcn_injection.h"
+#include "common/export.h"
+#include "common/file_util.h"
+#include "common/logd_write.h"
+#include "common/memory_state.h"
+#include "common/options.h"
+#include "common/process_emulator.h"
+#include "common/thread_local.h"
+#include "common/trace_event.h"
+#include "posix_translation/libc_dispatch_table.h"
+#include "posix_translation/virtual_file_system.h"
+
+// A macro to wrap an IRT function. Note that the macro does not wrap IRT
+// calls made by the Bionic loader. For example, wrapping mmap with DO_WRAP
+// does not hook the mmap IRT calls in phdr_table_load_segments() in
+// mods/android/bionic/linker/linker_phdr.c. This is because the loader has
+// its own set of IRT function pointers that are not visible from non-linker
+// code.
+#define DO_WRAP(name)                                   \
+  __nacl_irt_ ## name ## _real = __nacl_irt_ ## name;   \
+  __nacl_irt_ ## name  = __nacl_irt_ ## name ## _wrap
+
+// A macro to define an IRT wrapper and a function pointer to store
+// the real IRT function. Note that initializing __nacl_irt_<name>_real
+// with __nacl_irt_<name> by default is not a good idea because it requires
+// a static initializer.
+#define IRT_WRAPPER(name, ...)                              \
+  extern int (*__nacl_irt_ ## name)(__VA_ARGS__);           \
+  static int (*__nacl_irt_ ## name ## _real)(__VA_ARGS__);  \
+  int (__nacl_irt_ ## name ## _wrap)(__VA_ARGS__)
+
+// A helper macro to show both DIR pointer and its file descriptor in
+// ARC strace.
+#define PRETIFY_DIRP(dirp) (dirp) ? dirfd(dirp) : -1, (dirp)
+
+// Note about large file support in ARC:
+//
+// Unlike glibc, Bionic does not support _LARGEFILE64_SOURCE and
+// _FILE_OFFSET_BITS=64 macros. Instead, it always provides both foo() and
+// foo64() functions. It is user code's responsibility to call foo64()
+// explicitly instead of foo() when large file support is necessary.
+// Note that Android's JNI code properly calls these 64-bit variants.
+//
+// For Bionic, we should provide both
+//    __wrap_foo(type_t param1, another_type_t param2);
+// and
+//    __wrap_foo64(type64_t param1, another_type64_t param2);
+// functions because both could be called.
+
+extern "C" {
+// sorted by syscall name.
+ARC_EXPORT int __wrap_access(const char* pathname, int mode);
+ARC_EXPORT int __wrap_chdir(const char* path);
+ARC_EXPORT int __wrap_chown(const char* path, uid_t owner, gid_t group);
+ARC_EXPORT int __wrap_closedir(DIR* dirp);
+ARC_EXPORT int __wrap_dirfd(DIR* dirp);
+ARC_EXPORT int __wrap_dlclose(void* handle);
+ARC_EXPORT void* __wrap_dlopen(const char* filename, int flag);
+ARC_EXPORT void* __wrap_dlsym(void* handle, const char* symbol);
+ARC_EXPORT DIR* __wrap_fdopendir(int fd);
+ARC_EXPORT char* __wrap_getcwd(char* buf, size_t size);
+ARC_EXPORT int __wrap_open(const char* pathname, int flags, ...);
+ARC_EXPORT DIR* __wrap_opendir(const char* name);
+ARC_EXPORT struct dirent* __wrap_readdir(DIR* dirp);
+ARC_EXPORT int __wrap_readdir_r(
+    DIR* dirp, struct dirent* entry,
+    struct dirent** result);
+ARC_EXPORT ssize_t __wrap_readlink(const char* path, char* buf, size_t bufsiz);
+ARC_EXPORT char* __wrap_realpath(const char* path, char* resolved_path);
+ARC_EXPORT int __wrap_remove(const char* pathname);
+ARC_EXPORT int __wrap_rename(const char* oldpath, const char* newpath);
+ARC_EXPORT void __wrap_rewinddir(DIR* dirp);
+ARC_EXPORT int __wrap_rmdir(const char* pathname);
+ARC_EXPORT int __wrap_scandir(
+    const char* dirp, struct dirent*** namelist,
+    int (*filter)(const struct dirent*),
+    int (*compar)(const struct dirent**, const struct dirent**));
+ARC_EXPORT int __wrap_statfs(const char* path, struct statfs* stat);
+ARC_EXPORT int __wrap_statvfs(const char* path, struct statvfs* stat);
+ARC_EXPORT int __wrap_symlink(const char* oldp, const char* newp);
+ARC_EXPORT mode_t __wrap_umask(mode_t mask);
+ARC_EXPORT int __wrap_unlink(const char* pathname);
+ARC_EXPORT int __wrap_utime(const char* filename, const struct utimbuf* times);
+ARC_EXPORT int __wrap_utimes(
+    const char* filename,
+    const struct timeval times[2]);
+
+// Bionic's off_t is 32bit but bionic also provides 64 bit version of
+// functions which take off64_t. We need to define the wrapper of
+// the 64 bit versions as well.
+ARC_EXPORT int __wrap_ftruncate(int fd, off_t length);
+ARC_EXPORT off_t __wrap_lseek(int fd, off_t offset, int whence);
+ARC_EXPORT int __wrap_truncate(const char* path, off_t length);
+
+ARC_EXPORT int __wrap_ftruncate64(int fd, off64_t length);
+ARC_EXPORT off64_t __wrap_lseek64(int fd, off64_t offset, int whence);
+ARC_EXPORT ssize_t __wrap_pread(int fd, void* buf, size_t count, off_t offset);
+ARC_EXPORT ssize_t __wrap_pwrite(
+    int fd, const void* buf, size_t count, off_t offset);
+ARC_EXPORT ssize_t __wrap_pread64(
+    int fd, void* buf, size_t count, off64_t offset);
+ARC_EXPORT ssize_t __wrap_pwrite64(
+    int fd, const void* buf, size_t count, off64_t offset);
+ARC_EXPORT int __wrap_truncate64(const char* path, off64_t length);
+
+// sorted by syscall name.
+ARC_EXPORT int __wrap_close(int fd);
+ARC_EXPORT int __wrap_creat(const char* pathname, mode_t mode);
+ARC_EXPORT int __wrap_fcntl(int fd, int cmd, ...);
+ARC_EXPORT FILE* __wrap_fdopen(int fildes, const char* mode);
+ARC_EXPORT int __wrap_fdatasync(int fd);
+ARC_EXPORT int __wrap_flock(int fd, int operation);
+ARC_EXPORT int __wrap_fsync(int fd);
+ARC_EXPORT int __wrap_ioctl(int fd, int request, ...);
+ARC_EXPORT int __wrap_madvise(void* addr, size_t length, int advice);
+ARC_EXPORT void* __wrap_mmap(
+    void* addr, size_t length, int prot, int flags, int fd, off_t offset);
+ARC_EXPORT int __wrap_mprotect(const void* addr, size_t length, int prot);
+ARC_EXPORT int __wrap_munmap(void* addr, size_t length);
+ARC_EXPORT int __wrap_poll(struct pollfd* fds, nfds_t nfds, int timeout);
+ARC_EXPORT ssize_t __wrap_read(int fd, void* buf, size_t count);
+ARC_EXPORT ssize_t __wrap_readv(int fd, const struct iovec* iov, int iovcnt);
+ARC_EXPORT ssize_t __wrap_write(int fd, const void* buf, size_t count);
+ARC_EXPORT ssize_t __wrap_writev(int fd, const struct iovec* iov, int iovcnt);
+
+static int real_close(int fd);
+static int real_fstat(int fd, struct stat *buf);
+static char* real_getcwd(char *buf, size_t size);
+static off64_t real_lseek64(int fd, off64_t offset, int whence);
+static int real_lstat(const char *pathname, struct stat *buf);
+static int real_mkdir(const char *pathname, mode_t mode);
+static int real_open(const char *pathname, int oflag, mode_t cmode);
+static ssize_t real_read(int fd, void *buf, size_t count);
+static int real_stat(const char *pathname, struct stat *buf);
+static ssize_t real_write(int fd, const void *buf, size_t count);
+}  // extern "C"
+
+using posix_translation::VirtualFileSystem;
+
+namespace {
+
+// Counts the depth of __wrap_write() calls to avoid infinite loop back.
+DEFINE_THREAD_LOCAL(int, g_wrap_write_nest_count);
+
+// Helper function for converting from nacl_abi_stat to stat.
+void NaClAbiStatToStat(struct nacl_abi_stat* nacl_stat, struct stat* st) {
+  st->st_dev = nacl_stat->nacl_abi_st_dev;
+  st->st_mode = nacl_stat->nacl_abi_st_mode;
+  st->st_nlink = nacl_stat->nacl_abi_st_nlink;
+  st->st_uid = nacl_stat->nacl_abi_st_uid;
+  st->st_gid = nacl_stat->nacl_abi_st_gid;
+  st->st_rdev = nacl_stat->nacl_abi_st_rdev;
+  st->st_size = nacl_stat->nacl_abi_st_size;
+  st->st_blksize = nacl_stat->nacl_abi_st_blksize;
+  st->st_blocks = nacl_stat->nacl_abi_st_blocks;
+  st->st_atime = nacl_stat->nacl_abi_st_atime;
+  st->st_atime_nsec = 0;
+  st->st_mtime = nacl_stat->nacl_abi_st_mtime;
+  st->st_mtime_nsec = 0;
+  st->st_ctime = nacl_stat->nacl_abi_st_ctime;
+  st->st_ctime_nsec = 0;
+  st->st_ino = nacl_stat->nacl_abi_st_ino;
+}
+
+// Helper function for converting from stat to nacl_abi_stat.
+void StatToNaClAbiStat(struct stat* st, struct nacl_abi_stat* nacl_stat) {
+  nacl_stat->nacl_abi_st_dev = st->st_dev;
+  nacl_stat->nacl_abi_st_mode= st->st_mode;
+  nacl_stat->nacl_abi_st_nlink = st->st_nlink;
+  nacl_stat->nacl_abi_st_uid = st->st_uid;
+  nacl_stat->nacl_abi_st_gid = st->st_gid;
+  nacl_stat->nacl_abi_st_rdev = st->st_rdev;
+  nacl_stat->nacl_abi_st_size = st->st_size;
+  nacl_stat->nacl_abi_st_blksize = st->st_blksize;
+  nacl_stat->nacl_abi_st_blocks = st->st_blocks;
+  nacl_stat->nacl_abi_st_atime = st->st_atime;
+  nacl_stat->nacl_abi_st_mtime = st->st_mtime;
+  nacl_stat->nacl_abi_st_ctime = st->st_ctime;
+  nacl_stat->nacl_abi_st_ino = st->st_ino;
+}
+
+// Helper function for stripping "/system/lib/" prefix from |path| if exists.
+const char* StripSystemLibPrefix(const char* path) {
+  const char kSystemLib[] = "/system/lib/";
+  return !StartsWithASCII(path, kSystemLib, true) ?
+      path : path + sizeof(kSystemLib) - 1;
+}
+
+// Controls syscall interception. If set to true, file syscalls are just passed
+// through to libc.
+//
+// A mutex lock is not necessary here since |g_pass_through_enabled| is set by
+// the main thread before the first pthread_create() call is made. It is ensured
+// that a non-main thread can see correct |g_pass_through_enabled| value because
+// pthread_create() call to create the thread itself is a memory barrier.
+//
+// TODO(crbug.com/423063): We should be able to remove this after
+// libwrap/libposix_translation merge is finished.
+bool g_pass_through_enabled = false;
+
+VirtualFileSystem* GetFileSystem() {
+  if (g_pass_through_enabled) {
+    return NULL;
+  }
+  return VirtualFileSystem::GetVirtualFileSystem();
+}
+
+}  // namespace
+
+// sorted by syscall name.
+
+int __wrap_access(const char* pathname, int mode) {
+  ARC_STRACE_ENTER("access", "\"%s\", %s",
+                   SAFE_CSTR(pathname),
+                   arc::GetAccessModeStr(mode).c_str());
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->access(pathname, mode);
+  } else {
+    std::string newpath(pathname);
+    result = access(newpath.c_str(), mode);
+  }
+  if (result == -1 && errno != ENOENT) {
+    DANGERF("path=%s mode=%d: %s",
+            SAFE_CSTR(pathname), mode, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_chdir(const char* path) {
+  ARC_STRACE_ENTER("chdir", "\"%s\"", SAFE_CSTR(path));
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->chdir(path);
+  } else {
+    DANGERF("chdir: not supported");
+    errno = ENOSYS;
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_chown(const char* path, uid_t owner, gid_t group) {
+  ARC_STRACE_ENTER("chown", "\"%s\", %u, %u", SAFE_CSTR(path), owner, group);
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->chown(path, owner, group);
+  else
+    errno = ENOSYS;
+  ARC_STRACE_RETURN(result);
+}
+
+// Wrap this just for ARC strace.
+int __wrap_closedir(DIR* dirp) {
+  ARC_STRACE_ENTER("closedir", "%d, %p", PRETIFY_DIRP(dirp));
+  int result = closedir(dirp);
+  ARC_STRACE_RETURN(result);
+}
+
+// Wrap this just for ARC strace.
+int __wrap_dirfd(DIR* dirp) {
+  ARC_STRACE_ENTER("dirfd", "%p", dirp);
+  int result = dirfd(dirp);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_dlclose(void* handle) {
+  ARC_STRACE_ENTER("dlclose", "%p \"%s\"",
+                   handle, arc::GetDlsymHandleStr(handle).c_str());
+  int result = dlclose(handle);
+  if (!result)
+    ARC_STRACE_UNREGISTER_DSO_HANDLE(handle);
+  // false since dlclose never sets errno.
+  ARC_STRACE_RETURN_INT(result, false);
+}
+
+void* __wrap_dlopen(const char* filename, int flag) {
+  ARC_STRACE_ENTER("dlopen", "\"%s\", %s",
+                   SAFE_CSTR(filename),
+                   arc::GetDlopenFlagStr(flag).c_str());
+  // dlopen is known to be slow under NaCl.
+  TRACE_EVENT2(ARC_TRACE_CATEGORY, "wrap_dlopen",
+               "filename", TRACE_STR_COPY(SAFE_CSTR(filename)),
+               "flag", flag);
+  if (filename && (
+      (filename[0] != '/' && arc::IsStaticallyLinkedSharedObject(filename)) ||
+      (filename[0] == '/' && arc::IsStaticallyLinkedSharedObject(
+          StripSystemLibPrefix(filename))))) {
+    // ARC statically links some libraries into the main
+    // binary. When an app dlopen such library, we should return the
+    // handle of the main binary so that apps can find symbols.
+    // TODO(crbug.com/400947): Remove this temporary hack once we have stopped
+    //                         converting shared objects to archives.
+    filename = NULL;
+  }
+  void* result = dlopen(filename, flag);
+  if (result)
+    ARC_STRACE_REGISTER_DSO_HANDLE(result, filename);
+
+  // false since dlopen never sets errno.
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+void* __wrap_dlsym(void* handle, const char* symbol) {
+  ARC_STRACE_ENTER("dlsym", "%p \"%s\", \"%s\"",
+                   handle,
+                   arc::GetDlsymHandleStr(handle).c_str(),
+                   SAFE_CSTR(symbol));
+  void* result = dlsym(handle, symbol);
+  // false since dlsym never sets errno.
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+// Wrap this just for ARC strace.
+DIR* __wrap_fdopendir(int fd) {
+  ARC_STRACE_ENTER_FD("fdopendir", "%d", fd);
+  DIR* dirp = fdopendir(fd);
+  ARC_STRACE_RETURN_PTR(dirp, !dirp);
+}
+
+char* __wrap_getcwd(char* buf, size_t size) {
+  ARC_STRACE_ENTER("getcwd", "%p, %zu", buf, size);
+  char* result = NULL;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->getcwd(buf, size);
+  else
+    result = real_getcwd(buf, size);
+  ARC_STRACE_REPORT("result=\"%s\"", SAFE_CSTR(result));
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+extern "C" {
+IRT_WRAPPER(getdents, int fd, struct dirent* dirp, size_t count,
+            size_t* nread) {
+  // We intentionally use Bionic's dirent instead of NaCl's. See
+  // bionic/libc/arch-nacl/syscalls/getdents.c for detail.
+  ARC_STRACE_ENTER_FD("getdents", "%d, %p, %u, %p", fd, dirp, count, nread);
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->getdents(fd, dirp, count);
+  else
+    errno = ENOSYS;
+  if (result >= 0) {
+    *nread = result;
+    ARC_STRACE_REPORT("nread=\"%zu\"", *nread);
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(result >= 0 ? 0 : errno);
+}
+
+IRT_WRAPPER(getcwd, char* buf, size_t size) {
+  return __wrap_getcwd(buf, size) ? 0 : errno;
+}
+}  // extern "C"
+
+IRT_WRAPPER(lstat, const char* path, struct nacl_abi_stat* buf) {
+  ARC_STRACE_ENTER("lstat", "\"%s\", %p",
+                   SAFE_CSTR(path), buf);
+  int result;
+  struct stat st;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->lstat(path, &st);
+  } else {
+    std::string newpath(path);
+    result = real_lstat(newpath.c_str(), &st);
+  }
+  if (result == -1) {
+    if (errno != ENOENT) {
+      DANGERF("path=%s: %s", SAFE_CSTR(path), safe_strerror(errno).c_str());
+    }
+  } else {
+    StatToNaClAbiStat(&st, buf);
+    ARC_STRACE_REPORT("buf=%s", arc::GetNaClAbiStatStr(buf).c_str());
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(result == 0 ? 0 : errno);
+}
+
+IRT_WRAPPER(mkdir, const char* pathname, mode_t mode) {
+  ARC_STRACE_ENTER("mkdir", "\"%s\", 0%o", SAFE_CSTR(pathname), mode);
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->mkdir(pathname, mode);
+  else
+    result = real_mkdir(pathname, mode);
+  if (result == -1 && errno != EEXIST) {
+    DANGERF("path=%s mode=%d: %s",
+            SAFE_CSTR(pathname), mode, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(result == 0 ? 0 : errno);
+}
+
+int __wrap_open(const char* pathname, int flags, ...) {
+  va_list argp;
+  va_start(argp, flags);
+  mode_t mode = 0;
+  if (flags & O_CREAT) {
+    // Passing mode_t to va_arg with bionic makes compile fail.
+    // As bionic's mode_t is short, the value is promoted when it was
+    // passed to this vaarg function and fetching it as a short value
+    // is not valid. This definition can be bad if mode_t is a 64bit
+    // value, but such environment might not exist.
+    COMPILE_ASSERT(sizeof(mode) <= sizeof(int),  // NOLINT(runtime/sizeof)
+                   mode_t_is_too_big);
+    mode = va_arg(argp, int);
+  }
+  va_end(argp);
+
+  ARC_STRACE_ENTER("open", "\"%s\", %s, 0%o",
+                   SAFE_CSTR(pathname),
+                   arc::GetOpenFlagStr(flags).c_str(), mode);
+  int fd = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system &&
+      arc::IsStaticallyLinkedSharedObject(StripSystemLibPrefix(pathname))) {
+    // CtsSecurityTest verifies some libraries are ELF format. To pass that
+    // check, returns FD of runnable-ld.so instead.
+    // TODO(crbug.com/400947): Remove this temporary hack once we have stopped
+    //                         converting shared objects to archives.
+    ALOGE("open is called for %s. Opening runnable-ld.so instead.", pathname);
+    fd = file_system->open("/system/lib/runnable-ld.so", flags, mode);
+  } else if (file_system) {
+    fd = file_system->open(pathname, flags, mode);
+  } else {
+    fd = real_open(pathname, flags, mode);
+  }
+  if (fd == -1 && errno != ENOENT) {
+    DANGERF("pathname=%s flags=%d: %s",
+            SAFE_CSTR(pathname), flags, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_REGISTER_FD(fd, SAFE_CSTR(pathname));
+  ARC_STRACE_RETURN(fd);
+}
+
+// Wrap this just for ARC strace.
+DIR* __wrap_opendir(const char* name) {
+  ARC_STRACE_ENTER("opendir", "%s", SAFE_CSTR(name));
+  DIR* dirp = opendir(name);
+  ARC_STRACE_RETURN_PTR(dirp, !dirp);
+}
+
+// Wrap this just for ARC strace.
+struct dirent* __wrap_readdir(DIR* dirp) {
+  ARC_STRACE_ENTER_FD("readdir", "%d, %p", PRETIFY_DIRP(dirp));
+  struct dirent* ent = readdir(dirp);  // NOLINT(runtime/threadsafe_fn)
+  ARC_STRACE_RETURN_PTR(ent, false);
+}
+
+// Wrap this just for ARC strace.
+int __wrap_readdir_r(DIR* dirp, struct dirent* entry, struct dirent** ents) {
+  ARC_STRACE_ENTER_FD("readdir_r", "%d, %p, %p, %p",
+                      PRETIFY_DIRP(dirp), entry, ents);
+  int result = readdir_r(dirp, entry, ents);
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_readlink(const char* path, char* buf, size_t bufsiz) {
+  ARC_STRACE_ENTER("readlink", "\"%s\", %p, %zu",
+                   SAFE_CSTR(path), buf, bufsiz);
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->readlink(path, buf, bufsiz);
+  else
+    result = readlink(path, buf, bufsiz);
+  if (result == -1) {
+    DANGERF("path=%s bufsiz=%zu: %s",
+            SAFE_CSTR(path), bufsiz, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+char* __wrap_realpath(const char* path, char* resolved_path) {
+  ARC_STRACE_ENTER("realpath", "\"%s\", %p", SAFE_CSTR(path), resolved_path);
+  char* result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->realpath(path, resolved_path);
+  else
+    result = realpath(path, resolved_path);
+  if (!result) {
+    DANGERF("path=%s resolved_path=%p: %s",
+            SAFE_CSTR(path), resolved_path, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN_PTR(result, !result);
+}
+
+int __wrap_remove(const char* pathname) {
+  ARC_STRACE_ENTER("remove", "\"%s\"", SAFE_CSTR(pathname));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->remove(pathname);
+  else
+    result = remove(pathname);
+  if (result == -1 && errno != ENOENT)
+    DANGERF("path=%s: %s", SAFE_CSTR(pathname), safe_strerror(errno).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_rename(const char* oldpath, const char* newpath) {
+  ARC_STRACE_ENTER("rename", "\"%s\", \"%s\"",
+                   SAFE_CSTR(oldpath), SAFE_CSTR(newpath));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->rename(oldpath, newpath);
+  else
+    result = rename(oldpath, newpath);
+  if (result == -1) {
+    DANGERF("oldpath=%s newpath=%s: %s",
+            SAFE_CSTR(oldpath), SAFE_CSTR(newpath),
+            safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+// Wrap this just for ARC strace.
+void __wrap_rewinddir(DIR* dirp) {
+  ARC_STRACE_ENTER_FD("rewinddir", "%d, %p", PRETIFY_DIRP(dirp));
+  rewinddir(dirp);
+  ARC_STRACE_RETURN_VOID();
+}
+
+// Wrap this just for ARC strace.
+int __wrap_scandir(
+    const char* dirp, struct dirent*** namelist,
+    int (*filter)(const struct dirent*),
+    int (*compar)(const struct dirent**, const struct dirent**)) {
+  ARC_STRACE_ENTER("scandir", "%s, %p, %p, %p",
+                   SAFE_CSTR(dirp), namelist, filter, compar);
+  int result = scandir(dirp, namelist, filter, compar);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_statfs(const char* pathname, struct statfs* stat) {
+  ARC_STRACE_ENTER("statfs", "\"%s\", %p", SAFE_CSTR(pathname), stat);
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->statfs(pathname, stat);
+  else
+    result = statfs(pathname, stat);
+  if (result == -1 && errno != ENOENT)
+    DANGERF("path=%s: %s", SAFE_CSTR(pathname), safe_strerror(errno).c_str());
+  ARC_STRACE_REPORT(
+      "stat={type=%lld bsize=%lld blocks=%llu bfree=%llu bavail=%llu "
+      "files=%llu ffree=%llu fsid=%d,%d namelen=%lld frsize=%lld "
+      // Note: Unlike glibc and older Bionic, f_spare[] in Bionic 4.4 has
+      // only 4 elements, not 5.
+      "spare=%lld,%lld,%lld,%lld}",
+      static_cast<int64_t>(stat->f_type),
+      static_cast<int64_t>(stat->f_bsize),
+      stat->f_blocks, stat->f_bfree,
+      stat->f_bavail, stat->f_files, stat->f_ffree,
+      stat->f_fsid.__val[0], stat->f_fsid.__val[1],
+      static_cast<int64_t>(stat->f_namelen),
+      static_cast<int64_t>(stat->f_frsize),
+      static_cast<int64_t>(stat->f_spare[0]),
+      static_cast<int64_t>(stat->f_spare[1]),
+      static_cast<int64_t>(stat->f_spare[2]),
+      static_cast<int64_t>(stat->f_spare[3]));
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_statvfs(const char* pathname, struct statvfs* stat) {
+  ARC_STRACE_ENTER("statvfs", "\"%s\", %p", SAFE_CSTR(pathname), stat);
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->statvfs(pathname, stat);
+  else
+    result = statvfs(pathname, stat);
+  ARC_STRACE_REPORT(
+      "stat={bsize=%llu frsize=%llu blocks=%llu bfree=%llu bavail=%llu "
+      "files=%llu ffree=%llu favail=%llu fsid=%llu flag=%llu namemax=%llu}",
+      static_cast<int64_t>(stat->f_bsize),
+      static_cast<int64_t>(stat->f_frsize),
+      static_cast<int64_t>(stat->f_blocks),
+      static_cast<int64_t>(stat->f_bfree),
+      static_cast<int64_t>(stat->f_bavail),
+      static_cast<int64_t>(stat->f_files),
+      static_cast<int64_t>(stat->f_ffree),
+      static_cast<int64_t>(stat->f_favail),
+      static_cast<int64_t>(stat->f_fsid),
+      static_cast<int64_t>(stat->f_flag),
+      static_cast<int64_t>(stat->f_namemax));
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_symlink(const char* oldp, const char* newp) {
+  ARC_STRACE_ENTER("symlink", "\"%s\", \"%s\"",
+                   SAFE_CSTR(oldp), SAFE_CSTR(newp));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->symlink(oldp, newp);
+  } else {
+    errno = EPERM;
+    result = -1;
+  }
+  if (!result)
+    ALOGE("Added a non-persistent symlink from %s to %s", newp, oldp);
+  ARC_STRACE_RETURN(result);
+}
+
+template <typename OffsetType>
+static int TruncateImpl(const char* pathname, OffsetType length) {
+  ARC_STRACE_ENTER("truncate", "\"%s\", %lld",
+                   SAFE_CSTR(pathname), static_cast<int64_t>(length));
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->truncate(pathname, length);
+  else
+    errno = ENOSYS;
+  if (result == -1) {
+    DANGERF("path=%s length=%lld: %s",
+            SAFE_CSTR(pathname), static_cast<int64_t>(length),
+            safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_truncate(const char* pathname, off_t length) {
+  return TruncateImpl(pathname, length);
+}
+
+int __wrap_truncate64(const char* pathname, off64_t length) {
+  return TruncateImpl(pathname, length);
+}
+
+int __wrap_unlink(const char* pathname) {
+  ARC_STRACE_ENTER("unlink", "\"%s\"", SAFE_CSTR(pathname));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->unlink(pathname);
+  else
+    result = unlink(pathname);
+  if (result == -1 && errno != ENOENT)
+    DANGERF("path=%s: %s", SAFE_CSTR(pathname), safe_strerror(errno).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_utimes(const char* filename, const struct timeval times[2]) {
+  ARC_STRACE_ENTER("utimes", "\"%s\", %p", SAFE_CSTR(filename), times);
+  int result = 0;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->utimes(filename, times);
+  } else {
+    DANGERF("utimes: filename=%s times=%p", SAFE_CSTR(filename), times);
+    // NB: Returning -1 breaks some NDK apps.
+  }
+  if (result == -1 && errno != ENOENT) {
+    DANGERF("path=%s: %s",
+            SAFE_CSTR(filename), safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+IRT_WRAPPER(stat, const char* pathname, struct nacl_abi_stat* buf) {
+  ARC_STRACE_ENTER("stat", "\"%s\", %p", SAFE_CSTR(pathname), buf);
+  int result;
+  struct stat st;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->stat(pathname, &st);
+  else
+    result = real_stat(pathname, &st);
+  if (result == -1) {
+    if (errno != ENOENT) {
+      DANGERF("path=%s: %s", SAFE_CSTR(pathname), safe_strerror(errno).c_str());
+    }
+  } else {
+    StatToNaClAbiStat(&st, buf);
+    ARC_STRACE_REPORT("buf=%s", arc::GetNaClAbiStatStr(buf).c_str());
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(result == 0 ? 0 : errno);
+}
+
+int __wrap_close(int fd) {
+  ARC_STRACE_ENTER_FD("close", "%d", fd);
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->close(fd);
+  else
+    result = real_close(fd);
+  if (result == -1) {
+    // Closing with a bad file descriptor may be indicating a double
+    // close, which is more dangerous than it seems since everything
+    // shares one address space and we reuse file descriptors quickly.
+    // It can cause a newly allocated file descriptor in another
+    // thread to now be unallocated.
+    // We just use DANGERF() instead of LOG_FATAL_IF() because
+    // cts.CtsNetTestCases:android.net.rtp.cts.AudioStreamTest#testDoubleRelease
+    // hits the case.
+    if (errno == EBADF)
+      DANGERF("Close of bad file descriptor may indicate double close");
+    DANGERF("fd=%d: %s", fd, safe_strerror(errno).c_str());
+  }
+  if (!result)
+    ARC_STRACE_UNREGISTER_FD(fd);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_creat(const char* pathname, mode_t mode) {
+  ARC_STRACE_ENTER("creat", "\"%s\", 0%o", SAFE_CSTR(pathname), mode);
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    result = file_system->open(pathname, O_CREAT | O_WRONLY | O_TRUNC,
+                                        mode);
+  } else {
+    errno = ENOSYS;
+  }
+  ARC_STRACE_REGISTER_FD(result, SAFE_CSTR(pathname));
+  ARC_STRACE_RETURN(result);
+}
+
+IRT_WRAPPER(dup, int oldfd, int* newfd) {
+  ARC_STRACE_ENTER_FD("dup", "%d", oldfd);
+  int fd = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    fd = file_system->dup(oldfd);
+  } else {
+    fd = dup(oldfd);
+  }
+  if (fd == -1)
+    DANGERF("oldfd=%d: %s", oldfd, safe_strerror(errno).c_str());
+  *newfd = fd;
+  ARC_STRACE_RETURN_IRT_WRAPPER(fd >= 0 ? 0 : errno);
+}
+
+IRT_WRAPPER(dup2, int oldfd, int newfd) {
+  ARC_STRACE_ENTER_FD("dup2", "%d, %d", oldfd, newfd);
+  int fd = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system) {
+    fd = file_system->dup2(oldfd, newfd);
+  } else {
+    DANGERF("oldfd=%d newfd=%d", oldfd, newfd);
+    errno = EBADF;
+  }
+  if (fd == -1) {
+    DANGERF("oldfd=%d newfd=%d: %s",
+            oldfd, newfd, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(fd >= 0 ? 0 : errno);
+}
+
+// Although Linux has fcntl64 syscall, user code does not use it directly.
+// Therefore, we do not have to wrap the 64bit variant.
+int __wrap_fcntl(int fd, int cmd, ...) {
+  // TODO(crbug.com/241955): Support variable args?
+  ARC_STRACE_ENTER_FD("fcntl", "%d, %s, ...",
+                      fd, arc::GetFcntlCommandStr(cmd).c_str());
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+
+  if (file_system) {
+    va_list ap;
+    va_start(ap, cmd);
+    result = file_system->fcntl(fd, cmd, ap);
+    va_end(ap);
+  } else {
+    DANGER();
+    errno = EINVAL;
+  }
+
+  if (result == -1)
+    DANGERF("fd=%d cmd=%d: %s", fd, cmd, safe_strerror(errno).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_fdatasync(int fd) {
+  ARC_STRACE_ENTER_FD("fdatasync", "%d", fd);
+  int result = 0;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->fdatasync(fd);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_fsync(int fd) {
+  ARC_STRACE_ENTER_FD("fsync", "%d", fd);
+  int result = 0;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->fsync(fd);
+  ARC_STRACE_RETURN(result);
+}
+
+IRT_WRAPPER(fstat, int fd, struct nacl_abi_stat *buf) {
+  ARC_STRACE_ENTER_FD("fstat", "%d, %p", fd, buf);
+  int result;
+  struct stat st;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->fstat(fd, &st);
+  else
+    result = real_fstat(fd, &st);
+  if (result) {
+    result = errno;
+    DANGERF("fd=%d: %s", fd, safe_strerror(errno).c_str());
+  } else {
+    StatToNaClAbiStat(&st, buf);
+    ARC_STRACE_REPORT("buf=%s", arc::GetNaClAbiStatStr(buf).c_str());
+  }
+  ARC_STRACE_RETURN_IRT_WRAPPER(result);
+}
+
+template <typename OffsetType>
+static int FtruncateImpl(int fd, OffsetType length) {
+  ARC_STRACE_ENTER_FD("ftruncate", "%d, %lld",
+                      fd, static_cast<int64_t>(length));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->ftruncate(fd, length);
+  else
+    result = ftruncate64(fd, length);
+  if (result == -1) {
+    DANGERF("fd=%d length=%lld: %s", fd, static_cast<int64_t>(length),
+            safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_ftruncate(int fd, off_t length) {
+  return FtruncateImpl(fd, length);
+}
+
+int __wrap_ftruncate64(int fd, off64_t length) {
+  return FtruncateImpl(fd, length);
+}
+
+int __wrap_ioctl(int fd, int request, ...) {
+  // TODO(crbug.com/241955): Pretty-print variable args?
+  ARC_STRACE_ENTER_FD("ioctl", "%d, %s, ...",
+                      fd, arc::GetIoctlRequestStr(request).c_str());
+  int result = -1;
+  va_list ap;
+  va_start(ap, request);
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->ioctl(fd, request, ap);
+  else
+    errno = EINVAL;
+  va_end(ap);
+  if (result == -1)
+    DANGERF("fd=%d request=%d: %s", fd, request, safe_strerror(errno).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+template <typename OffsetType>
+static OffsetType LseekImpl(int fd, OffsetType offset, int whence) {
+  ARC_STRACE_ENTER_FD("lseek", "%d, %lld, %s",
+                      fd, static_cast<int64_t>(offset),
+                      arc::GetLseekWhenceStr(whence).c_str());
+  OffsetType result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->lseek(fd, offset, whence);
+  else
+    result = real_lseek64(fd, offset, whence);
+  if (result == -1) {
+    DANGERF("fd=%d offset=%lld whence=%d: %s",
+            fd, static_cast<int64_t>(offset), whence,
+            safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+off64_t __wrap_lseek64(int fd, off64_t offset, int whence) {
+  return LseekImpl(fd, offset, whence);
+}
+
+int __wrap_madvise(void* addr, size_t length, int advice) {
+  ARC_STRACE_ENTER("madvise", "%p, %zu, %s", addr, length,
+                   arc::GetMadviseAdviceStr(advice).c_str());
+  int result = -1;
+  int saved_errno = errno;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->madvise(addr, length, advice);
+  if (result != 0) {
+    DANGERF("errno=%d addr=%p length=%zu advice=%d: %s",
+            errno, addr, length, advice, safe_strerror(errno).c_str());
+    if (!file_system || (errno == ENOSYS && advice != MADV_REMOVE)) {
+      // TODO(crbug.com/362862): Stop special-casing ENOSYS once the bug is
+      // fixed.
+      // Note: We should call mprotect IRT here once the IRT is supported and
+      // crbug.com/36282 is still open.
+      errno = saved_errno;
+      result = 0;
+    }
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+// NB: Do NOT use off64_t for |offset|. It is not compatible with Bionic.
+// Bionic's mmap() does not support large file, and it does not provide
+// mmap64() either.
+void* __wrap_mmap(
+    void* addr, size_t length, int prot, int flags, int fd, off_t offset) {
+  ARC_STRACE_ENTER("mmap", "%p, %zu(0x%zx), %s, %s, %d \"%s\", 0x%llx",
+                   addr, length, length,
+                   arc::GetMmapProtStr(prot).c_str(),
+                   arc::GetMmapFlagStr(flags).c_str(),
+                   fd, arc::GetFdStr(fd).c_str(),
+                   static_cast<int64_t>(offset));
+  // WRITE + EXEC mmap is not allowed.
+  if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) {
+    ALOGE("mmap with PROT_WRITE + PROT_EXEC! "
+          "addr=%p length=%zu prot=%d flags=%d fd=%d offset=%lld",
+          addr, length, prot, flags, fd, static_cast<int64_t>(offset));
+    // However, with Bare Metal, our JIT engines or NDK apps may want WX mmap.
+#if defined(__native_client__)
+    ALOG_ASSERT(false, "PROT_WRITE + PROT_EXEC mmap is not allowed");
+    // This mmap call gracefully fails in release build.
+#endif
+  } else if (prot & PROT_EXEC) {
+    // There are two reasons we will see PROT_EXEC:
+    // - The Bionic loader use PROT_EXEC to map dlopen-ed files. Note
+    //   that we inject posix_translation based file operations to the
+    //   Bionic loader. See src/common/dlfcn_injection.cc for detail.
+    // - On Bare Metal ARM, v8 uses PROT_EXEC to run JIT-ed code directly.
+    //
+    // But it is still an interesting event. So, we log this by ALOGI.
+    ALOGI("mmap with PROT_EXEC! "
+          "addr=%p length=%zu prot=%d flags=%d fd=%d offset=%lld",
+          addr, length, prot, flags, fd, static_cast<int64_t>(offset));
+  }
+
+  if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC))
+    ALOGE("mmap with an unorthodox prot: %d", prot);
+  // We do not support MAP_NORESERVE but this flag is used often and
+  // we can safely ignore it.
+  const int supported_flag = (MAP_SHARED | MAP_PRIVATE | MAP_FIXED |
+                              MAP_ANONYMOUS | MAP_NORESERVE);
+  if (flags & ~supported_flag)
+    ALOGE("mmap with an unorthodox flags: %d", flags);
+
+  void* result = MAP_FAILED;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->mmap(addr, length, prot, flags, fd, offset);
+  else
+    result = mmap(addr, length, prot, flags, fd, offset);
+#if defined(USE_VERBOSE_MEMORY_VIEWER)
+  if (result != MAP_FAILED)
+    arc::MemoryMappingBacktraceMap::GetInstance()->
+        MapCurrentStackFrame(result, length);
+#endif
+
+  // Overwrite |errno| to emulate Bionic's behavior. See the comment in
+  // mods/android/bionic/libc/unistd/mmap.c.
+  if (result && (flags & (MAP_PRIVATE | MAP_ANONYMOUS))) {
+    if ((result != MAP_FAILED) &&
+        (flags & MAP_PRIVATE) && (flags & MAP_ANONYMOUS)) {
+      // In this case, madvise(MADV_MERGEABLE) in mmap.c will likely succeed.
+      // Do not update |errno|.
+    } else {
+      // Overwrite |errno| with EINVAL even when |result| points to a valid
+      // address.
+      errno = EINVAL;
+    }
+  }
+
+  if (result == MAP_FAILED) {
+    DANGERF("addr=%p length=%zu prot=%d flags=%d fd=%d offset=%lld: %s",
+            addr, length, prot, flags, fd, static_cast<int64_t>(offset),
+            safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN_PTR(result, result == MAP_FAILED);
+}
+
+int __wrap_mprotect(const void* addr, size_t len, int prot) {
+  ARC_STRACE_ENTER("mprotect", "%p, %zu(0x%zx), %s", addr, len, len,
+                   arc::GetMmapProtStr(prot).c_str());
+#if defined(__native_client__)
+  // PROT_EXEC mprotect is not allowed on NaCl, where all executable
+  // pages are validated through special APIs.
+  if (prot & PROT_EXEC) {
+    ALOGE("mprotect with PROT_EXEC! addr=%p length=%zu prot=%d",
+          addr, len, prot);
+    ALOG_ASSERT(false, "mprotect with PROT_EXEC is not allowed");
+    // This mmap call gracefully fails in release build.
+  }
+#else
+  if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) {
+    // TODO(crbug.com/365349): Currently, it seems Dalvik JIT is
+    // enabled on Bare Metal ARM. Disable it and increase the
+    // verbosity of this ALOG.
+    ALOGV("mprotect with PROT_WRITE + PROT_EXEC! addr=%p length=%zu prot=%d",
+          addr, len, prot);
+  }
+#endif
+
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  const int errno_orig = errno;
+  // mprotect in Bionic defines the first argument is const void*, but
+  // POSIX does it as void*. We use const void* for wrap, and use void* for
+  // posix_translation.
+  if (file_system)
+    result = file_system->mprotect(const_cast<void*>(addr), len, prot);
+  if (!file_system || (result != 0 && errno == ENOSYS)) {
+    // TODO(crbug.com/362862): Stop falling back to real mprotect on ENOSYS and
+    // do this only for unit tests.
+    ARC_STRACE_REPORT("falling back to real mprotect");
+    result = mprotect(addr, len, prot);
+    if (!result && errno == ENOSYS)
+      errno = errno_orig;  // restore |errno| overwritten by posix_translation
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_munmap(void* addr, size_t length) {
+  ARC_STRACE_ENTER("munmap", "%p, %zu(0x%zx)", addr, length, length);
+  ARC_STRACE_REPORT("RANGE (%p-%p)",
+                    addr, reinterpret_cast<char*>(addr) + length);
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  const int errno_orig = errno;
+  if (file_system)
+    result = file_system->munmap(addr, length);
+  if (!file_system || (result != 0 && errno == ENOSYS)) {
+    // TODO(crbug.com/362862): Stop falling back to real munmap on ENOSYS and
+    // do this only for unit tests.
+    ARC_STRACE_REPORT("falling back to real munmap");
+    result = munmap(addr, length);
+    if (!result && errno == ENOSYS)
+      errno = errno_orig;  // restore |errno| overwritten by posix_translation
+  }
+#if defined(USE_VERBOSE_MEMORY_VIEWER)
+  if (result == 0)
+    arc::MemoryMappingBacktraceMap::GetInstance()->Unmap(addr, length);
+#endif
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_poll(struct pollfd* fds, nfds_t nfds, int timeout) {
+  ARC_STRACE_ENTER("poll", "%p, %lld, %d",
+                   fds, static_cast<int64_t>(nfds), timeout);
+  if (arc::StraceEnabled()) {
+    for (nfds_t i = 0; i < nfds; ++i) {
+      ARC_STRACE_REPORT("polling fd %d \"%s\" for %s",
+                        fds[i].fd, arc::GetFdStr(fds[i].fd).c_str(),
+                        arc::GetPollEventStr(fds[i].events).c_str());
+    }
+  }
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->poll(fds, nfds, timeout);
+  else
+    result = poll(fds, nfds, timeout);
+  if (result == -1) {
+    DANGERF("fds=%p nfds=%u timeout=%d[ms]: %s",
+            fds, nfds, timeout, safe_strerror(errno).c_str());
+  } else if (arc::StraceEnabled()) {
+    for (int i = 0; i < result; ++i) {
+      if (!fds[i].revents)
+        continue;
+      ARC_STRACE_REPORT("fd %d \"%s\" is ready for %s",
+                        fds[i].fd, arc::GetFdStr(fds[i].fd).c_str(),
+                        arc::GetPollEventStr(fds[i].revents).c_str());
+    }
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+template <typename OffsetType>
+static ssize_t PreadImpl(int fd, void* buf, size_t count, OffsetType offset) {
+  ARC_STRACE_ENTER_FD("pread", "%d, %p, %zu, %lld",
+                      fd, buf, count, static_cast<int64_t>(offset));
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->pread(fd, buf, count, offset);
+  else
+    result = pread64(fd, buf, count, offset);
+  if (result == -1) {
+    DANGERF("fd=%d buf=%p count=%zu offset=%lld: %s",
+            fd, buf, count, static_cast<int64_t>(offset),
+            safe_strerror(errno).c_str());
+  }
+  if (result >= 0)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_pread(int fd, void* buf, size_t count, off_t offset) {
+  return PreadImpl(fd, buf, count, offset);
+}
+
+ssize_t __wrap_pread64(int fd, void* buf, size_t count, off64_t offset) {
+  return PreadImpl(fd, buf, count, offset);
+}
+
+template <typename OffsetType>
+static ssize_t PwriteImpl(int fd, const void* buf, size_t count,
+                          OffsetType offset) {
+  ARC_STRACE_ENTER_FD("pwrite", "%d, %p, %zu, %lld",
+                      fd, buf, count, static_cast<int64_t>(offset));
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->pwrite(fd, buf, count, offset);
+  else
+    result = pwrite64(fd, buf, count, offset);
+  if (result == -1) {
+    DANGERF("fd=%d buf=%p count=%zu offset=%lld: %s",
+            fd, buf, count, static_cast<int64_t>(offset),
+            safe_strerror(errno).c_str());
+  }
+  if (errno != EFAULT)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, count).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_pwrite(int fd, const void* buf, size_t count, off_t offset) {
+  return PwriteImpl(fd, buf, count, offset);
+}
+
+ssize_t __wrap_pwrite64(int fd, const void* buf, size_t count,
+                        off64_t offset) {
+  return PwriteImpl(fd, buf, count, offset);
+}
+
+ssize_t __wrap_read(int fd, void* buf, size_t count) {
+  ARC_STRACE_ENTER_FD("read", "%d, %p, %zu", fd, buf, count);
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->read(fd, buf, count);
+  else
+    result = real_read(fd, buf, count);
+  if (result == -1 && errno != EAGAIN) {
+    DANGERF("fd=%d buf=%p count=%zu: %s",
+            fd, buf, count, safe_strerror(errno).c_str());
+  }
+  if (result >= 0)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_readv(int fd, const struct iovec* iov, int iovcnt) {
+  // TODO(crbug.com/241955): Stringify |iov|?
+  ARC_STRACE_ENTER_FD("readv", "%d, %p, %d", fd, iov, iovcnt);
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->readv(fd, iov, iovcnt);
+  else
+    result = readv(fd, iov, iovcnt);
+  if (result == -1) {
+    DANGERF("fd=%d iov=%p iovcnt=%d: %s",
+            fd, iov, iovcnt, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_rmdir(const char* pathname) {
+  ARC_STRACE_ENTER("rmdir", "\"%s\"", SAFE_CSTR(pathname));
+  int result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->rmdir(pathname);
+  else
+    result = rmdir(pathname);
+  if (result == -1 && errno != ENOENT)
+    DANGERF("path=%s: %s", SAFE_CSTR(pathname), safe_strerror(errno).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_utime(const char* filename, const struct utimbuf* times) {
+  ARC_STRACE_ENTER("utime", "\"%s\", %p", SAFE_CSTR(filename), times);
+  int result = -1;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->utime(filename, times);
+  else
+    errno = ENOSYS;
+  if (result == -1 && errno != ENOENT) {
+    DANGERF("path=%s: %s",
+            SAFE_CSTR(filename), safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_write(int fd, const void* buf, size_t count) {
+  const int wrap_write_nest_count = g_wrap_write_nest_count.Get();
+  if (wrap_write_nest_count) {
+    // Calling write() to a stdio descriptor inside __wrap_write may cause
+    // infinite wrap loop. Here, we show a warning, and just return.
+    // It may happen when a chromium base DCHECK fails, e.g. inside AutoLock.
+    ALOGE("write() for stdio is called inside __wrap_write(): "
+          "fd=%d count=%zu buf=%p msg='%s'",
+          fd, count, buf,
+          std::string(static_cast<const char*>(buf), count).c_str());
+    return 0;
+  } else {
+    ARC_STRACE_ENTER_FD("write", "%d, %p, %zu", fd, buf, count);
+    g_wrap_write_nest_count.Set(wrap_write_nest_count + 1);
+    int result;
+    VirtualFileSystem* file_system = GetFileSystem();
+    if (file_system)
+      result = file_system->write(fd, buf, count);
+    else
+      result = real_write(fd, buf, count);
+    if (errno != EFAULT)
+      ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, count).c_str());
+    g_wrap_write_nest_count.Set(wrap_write_nest_count);
+    if (result == -1) {
+      DANGERF("fd=%d buf=%p count=%zu: %s",
+              fd, buf, count, safe_strerror(errno).c_str());
+    }
+    ARC_STRACE_RETURN(result);
+  }
+}
+
+ssize_t __wrap_writev(int fd, const struct iovec* iov, int iovcnt) {
+  // TODO(crbug.com/241955): Output the first N bytes in |iov|.
+  // TODO(crbug.com/241955): Stringify |iov|?
+  ARC_STRACE_ENTER_FD("writev", "%d, %p, %d", fd, iov, iovcnt);
+  ssize_t result;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    result = file_system->writev(fd, iov, iovcnt);
+  else
+    result = writev(fd, iov, iovcnt);
+  if (result == -1) {
+    DANGERF("fd=%d iov=%p iovcnt=%d: %s",
+            fd, iov, iovcnt, safe_strerror(errno).c_str());
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+mode_t __wrap_umask(mode_t mask) {
+  ARC_STRACE_ENTER("umask", "0%o", mask);
+  mode_t return_umask;
+  VirtualFileSystem* file_system = GetFileSystem();
+  if (file_system)
+    return_umask = file_system->umask(mask);
+  else
+    return_umask = umask(mask);
+  ARC_STRACE_RETURN(return_umask);
+}
+
+extern "C" {
+// The following is an example call stach when close() is called:
+//
+// our_function_that_calls_close()
+//   close()  // in Bionic
+//     __nacl_irt_close()  // function pointer call
+//        __nacl_irt_close_wrap()  // this function
+//          __wrap_close()  // in file_wrap.cc
+//             FileSystem::close()  // in posix_translation
+//
+// Also note that code in posix_translation/ is always able to call into the
+// original IRT by calling real_close() defined in file_wrap.cc.
+IRT_WRAPPER(close, int fd) {
+  int result = __wrap_close(fd);
+  return !result ? 0 : errno;
+}
+
+// See native_client/src/trusted/service_runtime/include/sys/fcntl.h
+#define NACL_ABI_O_SYNC 010000
+
+IRT_WRAPPER(open, const char *pathname, int oflag, mode_t cmode, int *newfd) {
+  // |oflag| is mostly compatible between NaCl and Bionic, O_SYNC is
+  // the only exception.
+  int bionic_oflag = oflag;
+  if ((bionic_oflag & NACL_ABI_O_SYNC)) {
+    bionic_oflag &= ~NACL_ABI_O_SYNC;
+    bionic_oflag |= O_SYNC;
+  }
+  *newfd = __wrap_open(pathname, bionic_oflag, cmode);
+  return *newfd >= 0 ? 0 : errno;
+}
+
+IRT_WRAPPER(read, int fd, void *buf, size_t count, size_t *nread) {
+  ssize_t result = __wrap_read(fd, buf, count);
+  *nread = result;
+  return result >= 0 ? 0 : errno;
+}
+
+IRT_WRAPPER(seek, int fd, off64_t offset, int whence, off64_t *new_offset) {
+  *new_offset = __wrap_lseek64(fd, offset, whence);
+  return *new_offset >= 0 ? 0 : errno;
+}
+
+IRT_WRAPPER(write, int fd, const void *buf, size_t count, size_t *nwrote) {
+  ssize_t result = __wrap_write(fd, buf, count);
+  *nwrote = result;
+  return result >= 0 ? 0 : errno;
+}
+
+// We implement IRT wrappers using __wrap_* functions. As the wrap
+// functions or posix_translation/ may call real functions, we
+// define them using real IRT interfaces.
+
+int real_close(int fd) {
+  ALOG_ASSERT(__nacl_irt_close_real);
+  int result = __nacl_irt_close_real(fd);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return 0;
+}
+
+int real_fstat(int fd, struct stat *buf) {
+  ALOG_ASSERT(__nacl_irt_fstat_real);
+  struct nacl_abi_stat nacl_buf;
+  int result = __nacl_irt_fstat_real(fd, &nacl_buf);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  NaClAbiStatToStat(&nacl_buf, buf);
+  return 0;
+}
+
+char* real_getcwd(char *buf, size_t size) {
+  ALOG_ASSERT(__nacl_irt_getcwd_real);
+  // Note: If needed, you can implement it with __nacl_irt_getcwd_real in the
+  // same way as android/bionic/libc/bionic/getcwd.cpp. __nacl_irt_getcwd_real
+  // and __getcwd (in Bionic) has the same interface.
+  ALOG_ASSERT(false, "not implemented");
+  return NULL;
+}
+
+int real_lstat(const char *pathname, struct stat *buf) {
+  ALOG_ASSERT(__nacl_irt_lstat_real);
+  struct nacl_abi_stat nacl_buf;
+  int result = __nacl_irt_lstat_real(pathname, &nacl_buf);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  NaClAbiStatToStat(&nacl_buf, buf);
+  return 0;
+}
+
+int real_mkdir(const char *pathname, mode_t mode) {
+  ALOG_ASSERT(__nacl_irt_mkdir_real);
+  int result = __nacl_irt_mkdir_real(pathname, mode);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return 0;
+}
+
+int real_open(const char *pathname, int oflag, mode_t cmode) {
+  ALOG_ASSERT(__nacl_irt_open_real);
+  int newfd;
+  // |oflag| is mostly compatible between NaCl and Bionic, O_SYNC is
+  // the only exception.
+  int nacl_oflag = oflag;
+  if ((nacl_oflag & O_SYNC)) {
+    nacl_oflag &= ~O_SYNC;
+    nacl_oflag |= NACL_ABI_O_SYNC;
+  }
+  int result = __nacl_irt_open_real(pathname, nacl_oflag, cmode, &newfd);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return newfd;
+}
+
+ssize_t real_read(int fd, void *buf, size_t count) {
+  ALOG_ASSERT(__nacl_irt_read_real);
+  size_t nread;
+  int result = __nacl_irt_read_real(fd, buf, count, &nread);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return nread;
+}
+
+int real_stat(const char *pathname, struct stat *buf) {
+  ALOG_ASSERT(__nacl_irt_stat_real);
+  struct nacl_abi_stat nacl_buf;
+  int result = __nacl_irt_stat_real(pathname, &nacl_buf);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  NaClAbiStatToStat(&nacl_buf, buf);
+  return 0;
+}
+
+off64_t real_lseek64(int fd, off64_t offset, int whence) {
+  ALOG_ASSERT(__nacl_irt_seek_real);
+  off64_t nacl_offset;
+  int result = __nacl_irt_seek_real(fd, offset, whence, &nacl_offset);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return nacl_offset;
+}
+
+ssize_t real_write(int fd, const void *buf, size_t count) {
+  ALOG_ASSERT(__nacl_irt_write_real);
+  size_t nwrote;
+  int result = __nacl_irt_write_real(fd, buf, count, &nwrote);
+  if (result) {
+    errno = result;
+    return -1;
+  }
+  return nwrote;
+}
+}  // extern "C"
+
+namespace {
+
+void direct_stderr_write(const void* buf, size_t count) {
+  ALOG_ASSERT(__nacl_irt_write_real);
+  size_t nwrote;
+  __nacl_irt_write_real(STDERR_FILENO, buf, count, &nwrote);
+}
+
+}  // namespace
+
+namespace arc {
+
+// The call stack gets complicated when IRT is hooked. See the comment near
+// IRT_WRAPPER(close) for more details.
+ARC_EXPORT void InitIRTHooks(bool pass_through) {
+  // This function must be called by the main thread before the first
+  // pthread_create() call is made. See the comment for g_pass_through_enabled
+  // above.
+  ALOG_ASSERT(!arc::ProcessEmulator::IsMultiThreaded());
+
+  DO_WRAP(close);
+  DO_WRAP(dup);
+  DO_WRAP(dup2);
+  DO_WRAP(fstat);
+  DO_WRAP(getcwd);
+  DO_WRAP(getdents);
+  DO_WRAP(lstat);
+  DO_WRAP(mkdir);
+  DO_WRAP(open);
+  DO_WRAP(read);
+  DO_WRAP(seek);
+  DO_WRAP(stat);
+  DO_WRAP(write);
+
+  g_pass_through_enabled = pass_through;
+
+  // We have replaced __nacl_irt_* above. Then, we need to inject them
+  // to the Bionic loader.
+  InitDlfcnInjection();
+
+  SetLogWriter(direct_stderr_write);
+}
+
+// This table is exported to higher levels to define how they should dispatch
+// through to libc.
+const LibcDispatchTable g_libc_dispatch_table = {
+  real_close,
+  real_fstat,
+  real_lseek64,
+  real_open,
+  real_read,
+  real_write,
+};
+
+}  // namespace arc
diff --git a/src/posix_translation/file_wrap_stub.cc b/src/posix_translation/file_wrap_stub.cc
new file mode 100644
index 0000000..e5b1571
--- /dev/null
+++ b/src/posix_translation/file_wrap_stub.cc
@@ -0,0 +1,225 @@
+// Copyright 2014 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/signalfd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/vfs.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "common/arc_strace.h"
+#include "common/danger.h"
+#include "common/export.h"
+#include "common/file_util.h"
+
+// Following stub functions are file related functions which are not
+// called so far. We make sure they are not called by assertion.
+
+extern "C" ARC_EXPORT int __wrap_fchdir(int fd) {
+  ARC_STRACE_ENTER_FD("fchdir", "%d", fd);
+  // TODO(crbug.com/178515): Implement this.
+  DANGERF("fchdir: fd=%d", fd);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_flock(int fd, int operation) {
+  // We do not have to implement flock() and similar functions because:
+  // - Each app has its own file system tree.
+  // - Two instances of the same app do not run at the same time.
+  // - App instance and Dexopt instance of an app do not access the file system
+  //   at the same time.
+  ARC_STRACE_ENTER_FD("flock", "%d, %s",
+                      fd, arc::GetFlockOperationStr(operation).c_str());
+  ARC_STRACE_REPORT("not implemented, always succeeds");
+  ARC_STRACE_RETURN(0);
+}
+
+extern "C" ARC_EXPORT int __wrap_fstatfs(int fd, struct statfs* buf) {
+  ARC_STRACE_ENTER_FD("fstatfs", "%d, %p", fd, buf);
+  DANGERF("fstatfs: fd=%d buf=%p", fd, buf);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_lchown(
+    const char* path, uid_t owner, gid_t group) {
+  ARC_STRACE_ENTER("lchown", "\"%s\", %u, %u",
+                   SAFE_CSTR(path), owner, group);
+  DANGERF("lchown: path=%s owner=%u group=%u",
+          SAFE_CSTR(path), owner, group);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_mlock(const void* addr, size_t len) {
+  ARC_STRACE_ENTER("mlock", "%p, %zu", addr, len);
+  DANGERF("mlock: addr=%p len=%zu", addr, len);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_mlockall(int flags) {
+  // TODO(crbug.com/241955): Stringify |flags|?
+  ARC_STRACE_ENTER("mlockall", "%d", flags);
+  DANGERF("mlockall: flags=%d", flags);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_mount(
+    const char* source, const char* target, const char* filesystemtype,
+    unsigned long mountflags,   // NOLINT(runtime/int)
+    const void* data) {
+  // TODO(crbug.com/241955): Stringify |mountflags|?
+  ARC_STRACE_ENTER("mount", "\"%s\", \"%s\", \"%s\", %lu, %p",
+                   SAFE_CSTR(source), SAFE_CSTR(target),
+                   SAFE_CSTR(filesystemtype), mountflags, data);
+  DANGERF("mount: source=%s target=%s "
+          "filesystemtype=%s mountflags=%lu data=%p",
+          SAFE_CSTR(source), SAFE_CSTR(target),
+          SAFE_CSTR(filesystemtype), mountflags, data);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT void* __wrap_mremap(
+    void* old_address, size_t old_size, size_t new_size, int flags,  ...) {
+  ARC_STRACE_ENTER("mremap", "%p, %zu, %zu, %s",
+                   old_address, old_size, new_size,
+                   arc::GetMremapFlagStr(flags).c_str());
+  DANGERF("mremap: old_address=%p old_size=%zu new_size=%zu flags=%d",
+          old_address, old_size, new_size, flags);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN_PTR(MAP_FAILED, true);
+}
+
+extern "C" ARC_EXPORT int __wrap_munlock(const void* addr, size_t len) {
+  ARC_STRACE_ENTER("munlock", "%p, %zu", addr, len);
+  DANGERF("munlock: addr=%p len=%zu", addr, len);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_munlockall() {
+  ARC_STRACE_ENTER("munlockall", "%s", "");
+  DANGERF("munlockall");
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_umount(const char* target) {
+  ARC_STRACE_ENTER("umount", "\"%s\"", SAFE_CSTR(target));
+  DANGERF("umount: target=%s", SAFE_CSTR(target));
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_umount2(const char* target, int flags) {
+  // TODO(crbug.com/241955): Stringify |flags|?
+  ARC_STRACE_ENTER("umount2", "\"%s\", %d", SAFE_CSTR(target), flags);
+  DANGERF("umount2: target=%s flags=%d", SAFE_CSTR(target), flags);
+  ALOG_ASSERT(0);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+// The following stub functions are being called so we are just
+// calling the real implementation or returning zero. For each function,
+// we need to either 1. remove it if NaCl's libc has the implementation,
+// or 2. implement it by ourselves.
+extern "C" ARC_EXPORT int __wrap_chmod(const char* path, mode_t mode) {
+  // TODO(crbug.com/242355): Implement this.
+  ARC_STRACE_ENTER("chmod", "\"%s\", 0%o", SAFE_CSTR(path), mode);
+  ARC_STRACE_REPORT("not implemented yet");
+  ARC_STRACE_RETURN(0);  // Returning -1 breaks SQLite.
+}
+
+extern "C" ARC_EXPORT int __wrap_eventfd(unsigned int initval, int flags) {
+  ARC_STRACE_ENTER("eventfd", "%u, %d", initval, flags);
+  ARC_STRACE_REPORT("not implemented yet");
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_fchmod(int fd, mode_t mode) {
+  // TODO(crbug.com/242355): Implement this.
+  ARC_STRACE_ENTER_FD("fchmod", "%d, 0%o", fd, mode);
+  ARC_STRACE_REPORT("not implemented yet");
+  ARC_STRACE_RETURN(0);
+}
+
+extern "C" ARC_EXPORT int __wrap_fchown(int fd, uid_t owner, gid_t group) {
+  // TODO(crbug.com/242355): Implement this.
+  ARC_STRACE_ENTER_FD("fchown", "%d, %u, %u", fd, owner, group);
+  ARC_STRACE_REPORT("not implemented yet");
+  ARC_STRACE_RETURN(0);
+}
+
+extern "C" ARC_EXPORT int __wrap_futimens(
+    int fd, const struct timespec times[2]) {
+  ARC_STRACE_ENTER_FD("futimens", "%d, %p", fd, times);
+  ARC_STRACE_REPORT("not implemented yet");
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_inotify_add_watch(
+    int fd, const char* pathname, uint32_t mask) {
+  ARC_STRACE_ENTER_FD("inotify_add_watch", "%d, \"%s\", %u",
+                      fd, SAFE_CSTR(pathname), mask);
+  // TODO(crbug.com/236903): Implement this.
+  DANGERF("inotify_add_watch: fd=%d pathname=%s mask=%u",
+          fd, SAFE_CSTR(pathname), mask);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_inotify_init() {
+  ARC_STRACE_ENTER("inotify_init", "%s", "");
+  // TODO(crbug.com/236903): Implement this.
+  DANGERF("inotify_init");
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_inotify_rm_watch(int fd, int wd) {
+  ARC_STRACE_ENTER_FD("inotify_rm_watch", "%d, %d", fd, wd);
+  // TODO(crbug.com/236903): Implement this.
+  DANGERF("inotify_rm_watch: fd=%d wd=%d", fd, wd);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+extern "C" ARC_EXPORT int __wrap_msync(void* addr, size_t length, int flags) {
+  ARC_STRACE_ENTER("msync", "%p, %zu, %d", addr, length, flags);
+  ARC_STRACE_REPORT("not implemented yet");
+  // msync is called by dexopt and some apps (crbug.com/363545). Although dexopt
+  // does not check the return value, the apps may. Return 0 without doing
+  // anything so that such apps will not fail. This should be safe as long as
+  // the app passes the mixed mmap/read/write checks in pepper_file.cc.
+  // TODO(crbug.com/242753): We might have to implement this through NaCl and
+  // Bare Metal IRT when we migrate to the real multi-process model.
+  ARC_STRACE_RETURN(0);
+}
diff --git a/src/posix_translation/host_resolver.cc b/src/posix_translation/host_resolver.cc
new file mode 100644
index 0000000..b5f22f1
--- /dev/null
+++ b/src/posix_translation/host_resolver.cc
@@ -0,0 +1,371 @@
+// Copyright 2014 The Chromium OS 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 "posix_translation/host_resolver.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "common/trace_event.h"
+#include "posix_translation/socket_util.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/host_resolver.h"
+#include "ppapi/cpp/net_address.h"
+
+namespace posix_translation {
+namespace {
+
+pthread_once_t g_host_ent_once = PTHREAD_ONCE_INIT;
+pthread_key_t g_host_ent_key;
+
+const addrinfo kDefaultHints = {
+  AI_V4MAPPED | AI_ADDRCONFIG,  // ai_flags
+  AF_UNSPEC,  // ai_family
+  // Remaining fields should be filled by 0.
+};
+
+void ClearHostEnt(struct hostent* value) {
+  free(value->h_name);
+  value->h_name = NULL;
+  char** addr_list = value->h_addr_list;
+  if (addr_list == NULL) {
+    return;
+  }
+  for (; *addr_list != NULL; addr_list++) {
+    delete[] *addr_list;
+  }
+  delete[] value->h_addr_list;
+  value->h_addr_list = NULL;
+}
+
+void HostEntDestructor(void* value) {
+  struct hostent* hostent = reinterpret_cast<struct hostent*>(value);
+  ClearHostEnt(hostent);
+  delete hostent->h_aliases;
+  delete hostent;
+}
+
+void InitHostEntKey() {
+  if (pthread_key_create(&g_host_ent_key, &HostEntDestructor) != 0) {
+    LOG_FATAL("Can't create HostEntKey");
+  }
+}
+
+struct hostent* GetCleanHostEnt() {
+  pthread_once(&g_host_ent_once, &InitHostEntKey);
+  struct hostent* hostent = reinterpret_cast<struct hostent*>(
+      pthread_getspecific(g_host_ent_key));
+  if (hostent == NULL) {
+    hostent = new struct hostent;
+    hostent->h_name = NULL;
+    hostent->h_aliases = new char*[1];
+    hostent->h_aliases[0] = NULL;
+    hostent->h_addr_list = NULL;
+    pthread_setspecific(g_host_ent_key, hostent);
+  } else {
+    ClearHostEnt(hostent);
+  }
+  return hostent;
+}
+
+}  // namespace
+
+HostResolver::HostResolver(const pp::InstanceHandle& instance)
+    : instance_(instance) {
+}
+
+HostResolver::~HostResolver() {
+}
+
+int HostResolver::getaddrinfo(const char* hostname, const char* servname,
+                              const addrinfo* hints, addrinfo** res) {
+  // TODO(crbug.com/356271): Use Bionic impl instead.
+  // We do not lock mutex_ in this function. resolver.Resolve() may take a few
+  // seconds.
+  if (hints == NULL)
+    hints = &kDefaultHints;
+
+  if (hints->ai_family != AF_UNSPEC &&
+      hints->ai_family != AF_INET &&
+      hints->ai_family != AF_INET6) {
+    ALOGW("getaddrinfo with unsupported family %d", hints->ai_family);
+    return EAI_FAMILY;
+  }
+
+  // Port in network order.
+  uint16_t sin_port = internal::ServiceNameToPort(servname);
+
+  sockaddr_storage storage;
+  if (hostname &&
+      internal::StringToSockAddrStorage(
+          hostname, sin_port, hints->ai_family, hints->ai_flags & AI_V4MAPPED,
+          &storage)) {
+    *res = internal::SockAddrStorageToAddrInfo(
+        storage, hints->ai_socktype, hints->ai_protocol, "");
+    ALOG_ASSERT(*res);
+    return 0;
+  }
+
+  bool is_ipv6 = hints->ai_family == AF_INET6;
+  if (hints->ai_flags & AI_PASSIVE) {
+    // Numeric case we considered above so the only remaining case is any.
+    memset(&storage, 0, sizeof(storage));
+    storage.ss_family = is_ipv6 ? AF_INET6 : AF_INET;
+    *res = internal::SockAddrStorageToAddrInfo(
+        storage, hints->ai_socktype, hints->ai_protocol, "");
+    ALOG_ASSERT(*res);
+    return 0;
+  }
+
+  if (!hostname) {
+    bool result = internal::StringToSockAddrStorage(
+        is_ipv6 ? "::1" : "127.0.0.1", sin_port,
+        hints->ai_family, hints->ai_flags & AI_V4MAPPED, &storage);
+    ALOG_ASSERT(result);
+    *res = internal::SockAddrStorageToAddrInfo(
+        storage, hints->ai_socktype, hints->ai_protocol, "");
+    ALOG_ASSERT(*res);
+    return 0;
+  }
+
+  // TODO(igorc): Remove this check for "1". CTS tests expect that this address
+  // is unresolvable, but PPAPI somehow resolves it to 0.0.0.1, which sounds
+  // incorrect. nslookup has no matching record. This could be related to
+  // the use PP_NETADDRESSFAMILY_UNSPECIFIED, but needs ot be checked.
+  if (!strcmp(hostname, "1"))
+    return EAI_NONAME;
+
+  if (hints->ai_flags & AI_NUMERICHOST)
+    return EAI_NONAME;
+
+  PP_HostResolver_Hint hint = {
+      PP_NETADDRESS_FAMILY_UNSPECIFIED,
+      hints->ai_flags & AI_CANONNAME ? PP_HOSTRESOLVER_FLAG_CANONNAME : 0
+  };
+
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "HostResolver::getaddrinfo - IPC",
+               "hostname", std::string(hostname));
+
+  // Should we retry IPv6, and then UNSPEC?
+  pp::HostResolver resolver(instance_);
+  // Resolve needs the port number in the host byte order
+  // unlike PP_NetAddress_IPv4/6 structures.
+  int32_t result = resolver.Resolve(
+      hostname, ntohs(sin_port), hint, pp::BlockUntilComplete());
+  if (result != PP_OK) {
+    // TODO(igorc): Check whether this should be EAI_NODATA
+    return EAI_NONAME;
+  }
+
+  int count = 0;
+  std::string host_name = resolver.GetCanonicalName().AsString();
+  uint32_t resolved_addr_count = resolver.GetNetAddressCount();
+  for (uint32_t i = 0; i < resolved_addr_count; i++) {
+    if (!internal::NetAddressToSockAddrStorage(
+            resolver.GetNetAddress(i),
+            hints->ai_family, hints->ai_flags & AI_V4MAPPED, &storage))
+      continue;
+    *res = internal::SockAddrStorageToAddrInfo(
+        storage, hints->ai_socktype, hints->ai_protocol, host_name);
+    res = &(*res)->ai_next;
+    ++count;
+    // TODO(igorc): Remove IPv4/IPv6 duplicates.
+  }
+
+  return (count == 0 ? EAI_NODATA : 0);
+}
+
+void HostResolver::freeaddrinfo(addrinfo* res) {
+  while (res != NULL) {
+    addrinfo* next = res->ai_next;
+    internal::ReleaseAddrInfo(res);
+    res = next;
+  }
+}
+
+hostent* HostResolver::gethostbyname(const char* name) {
+  struct hostent* res = gethostbyname2(name, AF_INET);
+  if (res == NULL) {
+    res = gethostbyname2(name, AF_INET6);
+  }
+  return res;
+}
+
+hostent* HostResolver::gethostbyname2(const char* name, int family) {
+  addrinfo* addr_info;
+  addrinfo hints = {};
+  hints.ai_family = family;
+  int res = this->getaddrinfo(name, NULL, &hints, &addr_info);
+
+  switch (res) {
+    case 0:
+      break;
+    case EAI_FAMILY:
+    case EAI_NONAME:
+      h_errno = HOST_NOT_FOUND;
+      return NULL;
+    case EAI_NODATA:
+      h_errno = NO_DATA;
+      return NULL;
+    case EAI_AGAIN:
+      h_errno = TRY_AGAIN;
+      return NULL;
+    default:
+      ALOGW("getaddrinfo returned error code %d (%s)", res, gai_strerror(res));
+      h_errno = NO_RECOVERY;
+      return NULL;
+  }
+
+  struct hostent* hostent = GetCleanHostEnt();
+  hostent->h_name = strdup(name);
+  hostent->h_addrtype = family;
+  hostent->h_length = (family == AF_INET ?
+      sizeof(struct in_addr) : sizeof(struct in6_addr));
+
+  int count = 0;
+  addrinfo* addr_info_i = addr_info;
+  while (addr_info_i) {
+    count++;
+    addr_info_i = addr_info_i->ai_next;
+  }
+
+  addr_info_i = addr_info;
+  hostent->h_addr_list = new char*[count + 1];
+  for (int i = 0; i < count; i++) {
+    hostent->h_addr_list[i] = new char[hostent->h_length];
+    if (family == AF_INET6) {
+      memcpy(hostent->h_addr_list[i],
+             &(reinterpret_cast<sockaddr_in6*>(
+                 addr_info_i->ai_addr))->sin6_addr,
+             hostent->h_length);
+    } else {
+      memcpy(hostent->h_addr_list[i],
+             &(reinterpret_cast<sockaddr_in*>(
+                 addr_info_i->ai_addr))->sin_addr,
+             hostent->h_length);
+    }
+    addr_info_i = addr_info_i->ai_next;
+  }
+  hostent->h_addr_list[count] = NULL;
+
+  this->freeaddrinfo(addr_info);
+  return hostent;
+}
+
+int HostResolver::gethostbyname_r(
+    const char* name, hostent* ret,
+    char* buf, size_t buflen, hostent** result, int* h_errnop) {
+  struct hostent* res = gethostbyname(name);
+  if (res == NULL) {
+    *result = NULL;
+    *h_errnop = h_errno;
+    return -1;
+  }
+  memcpy(ret, res, sizeof(struct hostent));
+  *result = ret;
+  return 0;
+}
+
+int HostResolver::gethostbyname2_r(
+    const char* host, int family, hostent* ret,
+    char* buf, size_t buflen, hostent** result, int* h_errnop) {
+  struct hostent* res = gethostbyname2(host, family);
+  if (res == NULL) {
+    *result = NULL;
+    *h_errnop = h_errno;
+    return -1;
+  }
+  memcpy(ret, res, sizeof(struct hostent));
+  *result = ret;
+  return 0;
+}
+
+hostent* HostResolver::gethostbyaddr(
+    const void* addr, socklen_t len, int type) {
+  if ((type != AF_INET && type != AF_INET6) ||
+      (type == AF_INET && len != sizeof(in_addr)) ||
+      (type == AF_INET6 && len != sizeof(in6_addr))) {
+    h_errno = EAI_FAMILY;
+    return NULL;
+  }
+
+  struct hostent* hostent = GetCleanHostEnt();
+
+  char host_name[256];
+  inet_ntop(type, addr, host_name, sizeof(host_name));
+  hostent->h_name = strdup(host_name);
+
+  hostent->h_addrtype = type;
+  hostent->h_length = len;
+  hostent->h_addr_list = new char*[2];
+  hostent->h_addr_list[0] = new char[len];
+  memcpy(hostent->h_addr_list[0], addr, len);
+  hostent->h_addr_list[1] = NULL;
+  return hostent;
+}
+
+int HostResolver::getnameinfo(const sockaddr* sa, socklen_t salen,
+                              char* host, size_t hostlen,
+                              char* serv, size_t servlen, int flags) {
+  // TODO(crbug.com/356271): Use Bionic impl instead.
+  if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6)
+    return EAI_FAMILY;
+
+  if ((sa->sa_family == AF_INET6 &&
+          static_cast<size_t>(salen) < sizeof(sockaddr_in6)) ||
+      (sa->sa_family == AF_INET &&
+          static_cast<size_t>(salen) < sizeof(sockaddr_in))) {
+    return EAI_FAMILY;
+  }
+
+  // Must ask for a name.
+  if ((host == NULL || hostlen == 0) && (serv == NULL || servlen == 0))
+    return EAI_NONAME;
+
+  if (serv) {
+    snprintf(serv, servlen, "%d",
+             ntohs((reinterpret_cast<const sockaddr_in*>(sa))->sin_port));
+  }
+
+  if (!host)
+    return 0;
+
+  if (flags & NI_NAMEREQD) {
+    if (sa->sa_family == AF_INET6) {
+      if (IN6_IS_ADDR_LOOPBACK(
+            &(reinterpret_cast<const sockaddr_in6*>(sa))->sin6_addr)) {
+        snprintf(host, hostlen, "ip6-localhost");
+        return 0;
+      }
+    } else {
+      uint32_t addr4 = static_cast<uint32_t>(ntohl(
+          (reinterpret_cast<const sockaddr_in*>(sa))->sin_addr.s_addr));
+      if (addr4 == 0x7F000001) {
+        snprintf(host, hostlen, "localhost");
+        return 0;
+      }
+    }
+  }
+
+  // NI_NUMERICHOST, also fallback when name was requested, but not available.
+  if (sa->sa_family == AF_INET6) {
+    inet_ntop(AF_INET6,
+              &(reinterpret_cast<const sockaddr_in6*>(sa))->sin6_addr,
+              host, hostlen);
+  } else {
+    inet_ntop(AF_INET,
+              &(reinterpret_cast<const sockaddr_in*>(sa))->sin_addr,
+              host, hostlen);
+  }
+
+  return 0;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/host_resolver.h b/src/posix_translation/host_resolver.h
new file mode 100644
index 0000000..18c56f4
--- /dev/null
+++ b/src/posix_translation/host_resolver.h
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium OS 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 POSIX_TRANSLATION_HOST_RESOLVER_H_
+#define POSIX_TRANSLATION_HOST_RESOLVER_H_
+
+#include <netdb.h>
+#include <sys/socket.h>
+
+#include "base/basictypes.h"
+#include "ppapi/cpp/instance_handle.h"
+
+namespace posix_translation {
+
+// This class implements posix functions which are related to hostname
+// resolving.
+class HostResolver {
+ public:
+  explicit HostResolver(const pp::InstanceHandle& instance);
+  ~HostResolver();
+
+  // List of supported posix functions.
+  int getaddrinfo(const char* hostname, const char* servname,
+                  const addrinfo* hints, addrinfo** res);
+  void freeaddrinfo(addrinfo* res);
+
+  hostent* gethostbyname(const char* name);
+  hostent* gethostbyname2(const char* name, int family);
+  int gethostbyname_r(
+      const char* name, hostent* ret,
+      char* buf, size_t buflen, hostent** result, int* h_errnop);
+  int gethostbyname2_r(
+      const char* host, int family, hostent* ret,
+      char* buf, size_t buflen, hostent** result, int* h_errnop);
+
+  hostent* gethostbyaddr(const void* addr, socklen_t len, int type);
+
+  int getnameinfo(const sockaddr* sa, socklen_t salen,
+                  char* host, size_t hostlen,
+                  char* serv, size_t servlen, int flags);
+
+ private:
+  pp::InstanceHandle instance_;
+
+  DISALLOW_COPY_AND_ASSIGN(HostResolver);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_HOST_RESOLVER_H_
diff --git a/src/posix_translation/libc_dispatch_layer.cc b/src/posix_translation/libc_dispatch_layer.cc
new file mode 100644
index 0000000..0d69d5d
--- /dev/null
+++ b/src/posix_translation/libc_dispatch_layer.cc
@@ -0,0 +1,86 @@
+// Copyright 2014 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.
+//
+// Defines libc compatible functions to override Bionic's. This allows
+// ::close(), ::fdatasync(), ::fstat(), etc. call in both posix_translation/
+// and base/ code to call directly into the original (non-hooked) IRT without
+// looping back to posix_translation.
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common/alog.h"
+#include "posix_translation/libc_dispatch_table.h"
+
+namespace posix_translation {
+
+using arc::g_libc_dispatch_table;
+
+extern "C" {
+int close(int fd) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_close);
+  return g_libc_dispatch_table.libc_close(fd);
+}
+int fstat(int fd, struct stat* buf) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_fstat);
+  return g_libc_dispatch_table.libc_fstat(fd, buf);
+}
+off64_t lseek64(int fd, off64_t offset, int whence) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_lseek);
+  return g_libc_dispatch_table.libc_lseek(fd, offset, whence);
+}
+int open(const char* pathname, int flags, mode_t mode) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_open);
+  return g_libc_dispatch_table.libc_open(pathname, flags, mode);
+}
+ssize_t read(int fd, void* buf, size_t count) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_read);
+  return g_libc_dispatch_table.libc_read(fd, buf, count);
+}
+ssize_t write(int fd, const void* buf, size_t count) {
+  ALOG_ASSERT(g_libc_dispatch_table.libc_write);
+  return g_libc_dispatch_table.libc_write(fd, buf, count);
+}
+
+// These FILE* functions are referenced in libchromium_base.a. For example,
+// some functions in base/logging.cc which posix_translation never calls depends
+// on fopen. Calling into these Bionic functions from libposix_translation.so
+// is not safe because these functions call into IRT and (hooked) IRT calls back
+// libposix_translation.so. To avoid hard-to-debug deadlocks, abort() early,
+// just in case.
+
+int fclose(FILE* fp) {
+  ALOG_ASSERT(false);
+  errno = ENOSYS;
+  return EOF;
+}
+int fflush(FILE* stream) {
+  ALOG_ASSERT(false);
+  errno = ENOSYS;
+  return EOF;
+}
+FILE* fopen(const char* path, const char* mode) {
+  ALOG_ASSERT(false, "path=%s", path);
+  errno = ENOSYS;
+  return NULL;
+}
+int fprintf(FILE* stream, const char* format, ...) {
+  ALOG_ASSERT(false, "format=%s", format);
+  return -1;
+}
+int fputs(const char* str, FILE* stream) {
+  ALOG_ASSERT(false, "%s", str);
+  return EOF;
+}
+int puts(const char* str) {
+  ALOG_ASSERT(false, "%s", str);
+  return EOF;
+}
+}  // extern "C"
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/libc_dispatch_table.h b/src/posix_translation/libc_dispatch_table.h
new file mode 100644
index 0000000..d0a7828
--- /dev/null
+++ b/src/posix_translation/libc_dispatch_table.h
@@ -0,0 +1,27 @@
+// Copyright 2014 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 POSIX_TRANSLATION_LIBC_DISPATCH_TABLE_H_
+#define POSIX_TRANSLATION_LIBC_DISPATCH_TABLE_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+namespace arc {
+
+// Actual libc function pointers for the corresponding functions.
+struct LibcDispatchTable {
+  int (*libc_close)(int fd);
+  int (*libc_fstat)(int fd, struct stat* buf);
+  off64_t (*libc_lseek)(int fd, off64_t offset, int whence);
+  int (*libc_open)(const char* pathname, int flags, mode_t mode);
+  ssize_t (*libc_read)(int fd, void* buf, size_t count);
+  ssize_t (*libc_write)(int fd, const void* buf, size_t count);
+};
+
+extern const LibcDispatchTable g_libc_dispatch_table;
+
+}  // namespace arc
+
+#endif  // POSIX_TRANSLATION_LIBC_DISPATCH_TABLE_H_
diff --git a/src/posix_translation/local_socket.cc b/src/posix_translation/local_socket.cc
new file mode 100644
index 0000000..ee6a935
--- /dev/null
+++ b/src/posix_translation/local_socket.cc
@@ -0,0 +1,330 @@
+// Copyright (c) 2013 The Chromium OS 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 "posix_translation/local_socket.h"
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "common/alog.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+LocalSocket::LocalSocket(int oflag, int socket_type,
+                         LocalSocketType local_socket_type)
+    : SocketStream(AF_UNIX, oflag), socket_type_(socket_type),
+      local_socket_type_(local_socket_type) {
+  // 224K is the default SO_SNDBUF/SO_RCVBUF in the linux kernel.
+  if (socket_type == SOCK_STREAM && local_socket_type != WRITE_ONLY)
+    buffer_.set_capacity(224*1024);
+}
+
+LocalSocket::~LocalSocket() {
+}
+
+bool LocalSocket::IsAllowedOnMainThread() const {
+  return true;
+}
+
+void LocalSocket::OnLastFileRef() {
+  if (peer_) {
+    peer_->peer_ = NULL;
+    peer_ = NULL;
+    VirtualFileSystem::GetVirtualFileSystem()->Broadcast();
+  }
+}
+
+void LocalSocket::set_peer(scoped_refptr<LocalSocket> peer) {
+  // Always called by VirtualFileSystem.
+  ALOG_ASSERT(peer != NULL);
+  peer_ = peer;
+}
+
+off64_t LocalSocket::lseek(off64_t offset, int whence) {
+  errno = ESPIPE;
+  return -1;
+}
+
+ssize_t LocalSocket::read(void* buf, size_t count) {
+  return this->recv(buf, count, 0);
+}
+
+ssize_t LocalSocket::recv(void* buf, size_t len, int flags) {
+  return this->recvfrom(buf, len, flags, NULL, NULL);
+}
+
+ssize_t LocalSocket::recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                              socklen_t* addrlen) {
+  if (addr || addrlen) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (len == 0)
+    return 0;
+
+  struct msghdr msg = {};
+  struct iovec iov;
+
+  iov.iov_base = buf;
+  iov.iov_len = len;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  return this->recvmsg(&msg, 0);
+}
+
+ssize_t LocalSocket::recvmsg(struct msghdr* msg, int flags) {
+  if (local_socket_type_ == WRITE_ONLY) {
+    // Reading from write socket of a pipe is not allowed.
+    errno = EBADF;
+    return -1;
+  }
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  if (is_block() && !(flags & MSG_DONTWAIT)) {
+    while (peer_ && !IsSelectReadReady()) {
+      sys->Wait();
+    }
+  }
+
+  ssize_t bytes_read = 0;
+  if (socket_type_ == SOCK_STREAM) {
+    if (buffer_.size() > 0) {
+      for (size_t i = 0; i < msg->msg_iovlen && buffer_.size() > 0; ++i) {
+        bytes_read += buffer_.read(static_cast<char*>(msg->msg_iov[i].iov_base),
+                                   msg->msg_iov[i].iov_len);
+      }
+    }
+  } else {
+    if (!queue_.empty()) {
+      const std::vector<char>& dgram = queue_.front();
+      std::vector<char>::const_iterator iter = dgram.begin();
+      size_t left = dgram.size();
+      for (size_t i = 0; i < msg->msg_iovlen && left > 0; ++i) {
+        size_t len = std::min(msg->msg_iov[i].iov_len, left);
+        std::copy(iter, iter + len,
+                  static_cast<char*>(msg->msg_iov[i].iov_base));
+        left -= len;
+        iter += len;
+      }
+      if (left > 0)
+        msg->msg_flags |= MSG_TRUNC;
+      bytes_read = dgram.size() - left;
+      queue_.pop_front();
+    }
+  }
+
+  // If no bytes are read in recvmsg, control messages are not returned either.
+  if (bytes_read > 0 && !cmsg_fd_queue_.empty()) {
+    std::vector<int>& fds = cmsg_fd_queue_.front();
+    size_t sizeof_int = sizeof(int);  // NOLINT(runtime/sizeof)
+
+    socklen_t cmsg_len = CMSG_LEN(fds.size() * sizeof_int);
+    while (CMSG_SPACE(cmsg_len) > msg->msg_controllen && !fds.empty()) {
+      // Cleanup file descriptors that are not passed back to the client so we
+      // do not leak them.  Close the last ones first so it acts like a FIFO.
+      // This is not part of any spec, but just makes the most intuitive sense.
+      int fd = fds.back();
+      sys->CloseLocked(fd);
+      fds.pop_back();
+      cmsg_len = CMSG_LEN(fds.size() * sizeof_int);
+      msg->msg_flags |= MSG_CTRUNC;
+    }
+
+    if (msg->msg_controllen) {
+      struct cmsghdr* cmsg = CMSG_FIRSTHDR(msg);
+      cmsg->cmsg_level = SOL_SOCKET;
+      cmsg->cmsg_type = SCM_RIGHTS;
+      cmsg->cmsg_len = cmsg_len;
+      memcpy(CMSG_DATA(cmsg), &fds[0], fds.size() * sizeof_int);
+    }
+    cmsg_fd_queue_.pop_front();
+  }
+
+  if (bytes_read > 0) {
+    // Notify any listeners waiting to write on the peer.
+    if (peer_)
+      peer_->NotifyListeners();
+    return bytes_read;
+  }
+
+  if (!peer_) {
+    // The other end of the socketpair has been closed, returns EOF(0).
+    return 0;
+  }
+  errno = EAGAIN;
+
+  return -1;
+}
+
+ssize_t LocalSocket::send(const void* buf, size_t len, int flags) {
+  return this->sendto(buf, len, flags, NULL, 0);
+}
+
+ssize_t LocalSocket::sendto(const void* buf, size_t len, int flags,
+                            const sockaddr* dest_addr, socklen_t addrlen) {
+  if (dest_addr || addrlen) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  if (len == 0)
+    return 0;
+
+  struct msghdr msg = {};
+  struct iovec iov;
+
+  // This is passed in as a member of a const struct msghdr below, so casting
+  // away constness is ok here.
+  iov.iov_base = const_cast<void*>(buf);
+  iov.iov_len = len;
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  return this->sendmsg(&msg, 0);
+}
+
+ssize_t LocalSocket::sendmsg(const struct msghdr* msg, int flags) {
+  if (local_socket_type_ == READ_ONLY) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (peer_)
+    return peer_->HandleSendmsgLocked(msg);
+  errno = ECONNRESET;
+  return -1;
+}
+
+ssize_t LocalSocket::write(const void* buf, size_t count) {
+  return this->send(buf, count, 0);
+}
+
+int LocalSocket::ioctl(int request, va_list ap) {
+  if (request == FIONREAD) {
+    int* out = va_arg(ap, int*);
+    if (socket_type_ == SOCK_STREAM) {
+      *out = buffer_.size();
+    } else {
+      if (!queue_.empty())
+        *out = queue_.front().size();
+      else
+        *out = 0;
+    }
+    return 0;
+  } else {
+    errno = EINVAL;
+    return -1;
+  }
+}
+
+bool LocalSocket::IsSelectReadReady() const {
+  if (socket_type_ == SOCK_STREAM)
+    return buffer_.size() > 0 || !peer_;
+  else
+    return !queue_.empty();
+}
+
+bool LocalSocket::IsSelectWriteReady() const {
+  if (local_socket_type_ == READ_ONLY || peer_ == NULL)
+    return false;
+  return peer_->CanWrite();
+}
+
+bool LocalSocket::IsSelectExceptionReady() const {
+  return !peer_;
+}
+
+int16_t LocalSocket::GetPollEvents() const {
+  // Currently we use IsSelect*Ready() family temporarily (and wrongly).
+  // TODO(crbug.com/359400): Fix the implementation.
+  return ((IsSelectReadReady() ? POLLIN : 0) |
+          (IsSelectWriteReady() ? POLLOUT : 0) |
+          (IsSelectExceptionReady() ? POLLERR : 0));
+}
+
+bool LocalSocket::CanWrite() const {
+  if (socket_type_ == SOCK_STREAM)
+    return buffer_.size() < buffer_.capacity();
+  return true;
+}
+
+ssize_t LocalSocket::HandleSendmsgLocked(const struct msghdr* msg) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  const struct iovec* buf = msg->msg_iov;
+  size_t len = msg->msg_iovlen;
+
+  ssize_t bytes_sent = 0;
+  size_t bytes_attempted = 0;
+  if (len > 0) {
+    if (socket_type_ == SOCK_STREAM) {
+      for (size_t i = 0; i < len; ++i) {
+        bytes_attempted += buf[i].iov_len;
+        bytes_sent += buffer_.write(static_cast<const char*>(buf[i].iov_base),
+                                    buf[i].iov_len);
+      }
+    } else {
+      queue_.resize(queue_.size() + 1);
+      for (size_t i = 0; i < len; ++i) {
+        const char* begin = static_cast<const char*>(buf[i].iov_base);
+        const char* end = begin + buf[i].iov_len;
+        queue_.back().insert(queue_.back().end(), begin, end);
+        bytes_attempted += buf[i].iov_len;
+        bytes_sent += buf[i].iov_len;
+      }
+    }
+  }
+
+  // If we did not send any bytes, do not process any control messages either.
+  if (bytes_sent && msg->msg_controllen > 0) {
+    size_t sizeof_int = sizeof(int);  // NOLINT(runtime/sizeof)
+    // The CMSG macros cannot deal with const msghdrs, so cast away constness
+    // for this section, but make all access to the underlying data through
+    // const local variables.
+    struct msghdr* nonconst_msg = const_cast<struct msghdr*>(msg);
+    cmsg_fd_queue_.resize(cmsg_fd_queue_.size() + 1);
+    std::vector<int>& fds = cmsg_fd_queue_.back();
+    for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(nonconst_msg);
+         cmsg;
+         cmsg = CMSG_NXTHDR(nonconst_msg, cmsg)) {
+      // We only support one control message, specifically of type
+      // SCM_RIGHTS to send file descriptors.
+      ALOG_ASSERT(cmsg->cmsg_level == SOL_SOCKET);
+      ALOG_ASSERT(cmsg->cmsg_type == SCM_RIGHTS);
+      if (cmsg->cmsg_level == SOL_SOCKET &&
+          cmsg->cmsg_type == SCM_RIGHTS &&
+          cmsg->cmsg_len >= CMSG_LEN(0)) {
+        size_t payload_len = cmsg->cmsg_len - CMSG_LEN(0);
+        ALOG_ASSERT(payload_len % sizeof_int == 0);
+        const int *wire_fds = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+        size_t wire_fds_len = payload_len / sizeof_int;
+        // Dup the file descriptors before adding them to the control message.
+        // This emulates what happens in Posix when sending file descriptors in
+        // the same process (as webviewchromium does).
+        for (size_t i = 0; i < wire_fds_len; ++i)
+          fds.push_back(sys->DupLocked(wire_fds[i], -1));
+      }
+    }
+  }
+
+  if (bytes_sent > 0) {
+    sys->Broadcast();
+    NotifyListeners();
+  }
+
+  if (bytes_sent == 0 && bytes_attempted != 0) {
+    errno = EAGAIN;
+    return -1;
+  }
+
+  return bytes_sent;
+}
+
+const char* LocalSocket::GetStreamType() const {
+  return "local_socket";
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/local_socket.h b/src/posix_translation/local_socket.h
new file mode 100644
index 0000000..10e1d95
--- /dev/null
+++ b/src/posix_translation/local_socket.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_LOCAL_SOCKET_H_
+#define POSIX_TRANSLATION_LOCAL_SOCKET_H_
+
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <deque>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/circular_buffer.h"
+#include "posix_translation/socket_stream.h"
+
+namespace posix_translation {
+
+class LocalSocket : public SocketStream {
+ public:
+  enum LocalSocketType {
+    READ_ONLY,
+    WRITE_ONLY,
+    READ_WRITE
+  };
+
+  LocalSocket(int oflag, int socket_type,
+              LocalSocketType local_socket_type);
+
+  bool is_block() { return !(oflag() & O_NONBLOCK); }
+
+  void set_peer(scoped_refptr<LocalSocket> peer);
+
+  // LocalSocket can work on main thread because it does not use Pepper file IO
+  // for its implementation.
+  virtual bool IsAllowedOnMainThread() const OVERRIDE;
+
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t recv(void* buf, size_t len, int flags) OVERRIDE;
+  virtual ssize_t recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                           socklen_t* addrlen) OVERRIDE;
+  virtual ssize_t recvmsg(struct msghdr* msg, int flags) OVERRIDE;
+  virtual ssize_t send(const void* buf, size_t len, int flags) OVERRIDE;
+  virtual ssize_t sendto(const void* buf, size_t len, int flags,
+                         const sockaddr* dest_addr, socklen_t addrlen) OVERRIDE;
+  virtual ssize_t sendmsg(const struct msghdr* msg, int flags) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+
+  virtual bool IsSelectReadReady() const OVERRIDE;
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+  virtual bool IsSelectExceptionReady() const OVERRIDE;
+  virtual int16_t GetPollEvents() const OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~LocalSocket();
+  virtual void OnLastFileRef() OVERRIDE;
+
+ private:
+  // Very limited control message support (SCM_RIGHTS passing file descriptors).
+  typedef std::deque<std::vector<int> > ControlMessageFDQueue;
+  typedef std::deque<std::vector<char> > MessageQueue;
+
+  bool CanWrite() const;
+  ssize_t HandleSendmsgLocked(const struct msghdr* msg);
+
+  int socket_type_;
+  arc::CircularBuffer buffer_;
+  LocalSocketType local_socket_type_;
+  scoped_refptr<LocalSocket> peer_;
+  MessageQueue queue_;
+  ControlMessageFDQueue cmsg_fd_queue_;
+
+  DISALLOW_COPY_AND_ASSIGN(LocalSocket);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_LOCAL_SOCKET_H_
diff --git a/src/posix_translation/memory_region.cc b/src/posix_translation/memory_region.cc
new file mode 100644
index 0000000..b615f6b
--- /dev/null
+++ b/src/posix_translation/memory_region.cc
@@ -0,0 +1,656 @@
+// Copyright 2014 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.
+
+#define __STDC_FORMAT_MACROS  // for PRIxPTR.
+
+#include "posix_translation/memory_region.h"
+
+#include <inttypes.h>
+#include <algorithm>  // for min and max
+#include <utility>
+
+#include "base/containers/hash_tables.h"
+#include "base/strings/stringprintf.h"
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "posix_translation/address_util.h"
+#include "posix_translation/file_stream.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace {
+// In NaCl, all text regions that are used with PROT_EXEC should be mapped at
+// lower memory < 256MB. Because of NaCl restriction, ::munmap() against text
+// regions always fails.
+#if defined(__native_client__)
+const uintptr_t kTextEndAddress = 256 * 1024 * 1024;
+#endif
+}  // namespace
+
+namespace posix_translation {
+
+namespace {
+
+std::string GetStreamPathname(scoped_refptr<FileStream> stream) {
+  const std::string result = stream->pathname().empty() ?
+      std::string("(anonymous mmap)") : stream->pathname();
+  const std::string aux = stream->GetAuxInfo();
+  if (!aux.empty())
+    return result + " [" + aux + "]";
+  return result;
+}
+
+class AdviseVisitor : public MemoryRegion::PageMapVisitor {
+ public:
+  explicit AdviseVisitor(int advice);
+  virtual ~AdviseVisitor();
+
+  int Finish();
+
+  virtual bool Visit(const MemoryRegion::PageMapValue& page_map,
+                     char* start_addr, char* end_addr) OVERRIDE;
+ private:
+  bool visited_;
+  int errno_;
+  const int advice_;
+};
+
+class ProtectionVisitor : public MemoryRegion::PageMapVisitor {
+ public:
+  ProtectionVisitor(int prot, std::set<ino_t>* out_write_mapped);
+  virtual ~ProtectionVisitor();
+
+  int Finish();
+
+  virtual bool Visit(const MemoryRegion::PageMapValue& page_map,
+                     char* start_addr, char* end_addr) OVERRIDE;
+
+ private:
+  bool visited_;
+  const int prot_;
+  std::set<ino_t>* write_mapped_;
+};
+
+AdviseVisitor::AdviseVisitor(int advice)
+    : visited_(false), errno_(0), advice_(advice) {
+}
+
+AdviseVisitor::~AdviseVisitor() {
+}
+
+bool AdviseVisitor::Visit(const MemoryRegion::PageMapValue& page_map,
+                          char* start_addr, char* end_addr) {
+  ARC_STRACE_REPORT_HANDLER(page_map.stream->GetStreamType());
+  ARC_STRACE_REPORT("(%p-%p \"%s\")",
+                    start_addr,
+                    end_addr + 1,
+                    GetStreamPathname(page_map.stream).c_str());
+  size_t length = end_addr - start_addr + 1;
+  int result = page_map.stream->madvise(start_addr, length, advice_);
+  if (result)
+    errno_ = errno;
+  if (result > 0) {
+    WriteFailureLog("madvise", visited_, start_addr, end_addr, page_map.stream);
+    return false;
+  }
+  visited_ = true;
+  return result == 0;
+}
+
+int AdviseVisitor::Finish() {
+  // TODO(crbug.com/362862): Stop returning ENOSYS. We report ENOSYS since
+  // MemoryRegion does not manage all regions, and madvise may be issued against
+  // these missing regions, e.g., main.nexe, DT_NEEDED DSOs in main.nexe
+  // loaded by ld-runnable.so, and so on. Returning ENOSYS helps to support
+  // these cases in __wrap_madvise() side.
+  if (!visited_)
+    errno = ENOSYS;
+  else if (errno_)
+    errno = errno_;
+  else
+    return 0;
+  return -1;
+}
+
+ProtectionVisitor::ProtectionVisitor(int prot, std::set<ino_t>* write_mapped)
+    : visited_(false), prot_(prot), write_mapped_(write_mapped) {
+}
+
+ProtectionVisitor::~ProtectionVisitor() {
+}
+
+bool ProtectionVisitor::Visit(const MemoryRegion::PageMapValue& page_map,
+                              char* start_addr, char* end_addr) {
+  // TODO(crbug.com/427417): Split page_map if prot is inconsistent.
+  ARC_STRACE_REPORT_HANDLER(page_map.stream->GetStreamType());
+  ARC_STRACE_REPORT("(%p-%p \"%s\")",
+                      start_addr,
+                      end_addr + 1,
+                      GetStreamPathname(page_map.stream).c_str());
+  size_t length = end_addr - start_addr + 1;
+  if (!page_map.stream->mprotect(start_addr, length, prot_)) {
+    if (prot_ & PROT_WRITE)
+      write_mapped_->insert(page_map.stream->inode());
+  } else {
+    WriteFailureLog(
+        "mprotect", visited_, start_addr, end_addr, page_map.stream);
+    return false;  // return early on error
+  }
+  visited_ = true;
+  return true;
+}
+
+int ProtectionVisitor::Finish() {
+  if (!visited_) {
+    // TODO(crbug.com/362862): See comments at AdviseVisitor::Finish().
+    errno = ENOSYS;
+    return -1;
+  }
+  return 0;
+}
+
+
+}  // namespace
+
+MemoryRegion::MemoryRegion() : abort_on_unexpected_memory_maps_(true) {
+}
+
+MemoryRegion::~MemoryRegion() {
+}
+
+bool MemoryRegion::AddFileStreamByAddr(
+    void* addr, size_t length, off64_t offset, int prot, int flags,
+    scoped_refptr<FileStream> stream) {
+  ALOG_ASSERT(!IsPageEndAddress(addr) && !(length % 2));
+  if (!length)
+    return false;
+
+  char* const addr_start = static_cast<char*>(addr);
+  if (stream) {
+    // Our mmap implementations usually only return an address that are not yet
+    // mapped. For example, calling mmap twice against a file in Pepper,
+    // Readonnly, and NaClManifest returns two different addresses. However, our
+    // current MemoryFile::mmap() implementation does not follow the POSIX
+    // convention. The method returns the same address when it is called twice
+    // or more. Handles the special case first. See also http://crbug.com/366557
+    PageMapValue* region = FindRegion(addr_start, length);
+    if (region) {
+      if (abort_on_unexpected_memory_maps_) {
+        ALOG_ASSERT(!(flags & MAP_FIXED));
+        ALOG_ASSERT(stream->ReturnsSameAddressForMultipleMmaps());
+        ALOG_ASSERT(region->stream->ReturnsSameAddressForMultipleMmaps());
+        ALOG_ASSERT(stream->pathname() == region->stream->pathname());
+      }
+      if (flags & MAP_FIXED ||
+          !stream->ReturnsSameAddressForMultipleMmaps() ||
+          !region->stream->ReturnsSameAddressForMultipleMmaps() ||
+          stream->pathname() != region->stream->pathname()) {
+        return false;
+      }
+      ++(region->ref);
+      return true;
+    }
+  }
+
+  const PageMapValue value(1, offset, stream);
+  typedef std::pair<PageToStream::iterator, bool> InsertResult;
+  InsertResult result_addr_start =
+      map_.insert(std::make_pair(addr_start, value));
+
+  // Fail if |addr_start| already exists in the map.
+  if (!result_addr_start.second)
+    return false;
+  PageToStream::iterator addr_start_it = result_addr_start.first;
+
+  char* const addr_end = addr_start + length - 1;
+  ALOG_ASSERT(IsPageEndAddress(addr_end));
+  InsertResult result_addr_end = map_.insert(std::make_pair(addr_end, value));
+
+  // Fail if |addr_end| already exists in the map.
+  if (!result_addr_end.second) {
+    map_.erase(addr_start_it);
+    return false;
+  }
+  PageToStream::iterator addr_end_it = result_addr_end.first;
+
+  // Fail if [addr_start, addr_end) overlaps with one of the existing regions.
+  // It happens for the following cases using MemoryFile.
+  //   fd = ashmem_create_region();
+  //   mmap(fd, 4096 /* length */);
+  //   mmap(fd, 8192 /* different length */);  // fail here
+  // If the second length is the same, FindRegion() in this function returns
+  // the first region and works.
+  if (IsOverlap(addr_start_it, addr_end_it)) {
+    map_.erase(addr_start_it);
+    map_.erase(addr_end_it);
+    return false;
+  }
+
+  if (stream) {
+    const ino_t inode = stream->inode();
+    if (prot & PROT_WRITE)
+      write_mapped_.insert(inode);
+  }
+
+  // You can uncomment this to print the memory mappings.
+  //  ALOGI("\n%s", GetMemoryMapAsString().c_str());
+  return true;
+}
+
+int MemoryRegion::RemoveFileStreamsByAddr(
+    void* addr, size_t length, bool call_munmap) {
+  // TODO(toyoshim): Rewrite this function to use PageMapVisitor.
+  ALOG_ASSERT(!IsPageEndAddress(addr) && !(length % 2));
+  if (!length) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  char* const remove_start = static_cast<char*>(addr);
+  PageMapValue* region = FindRegion(remove_start, length);
+  if (region && region->ref > 1) {
+    --(region->ref);
+    return 0;
+  }
+
+  char* const remove_end = remove_start + length - 1;
+
+  // Find the first region.
+  PageToStream::iterator it = map_.lower_bound(remove_start);
+  if (it == map_.end()) {
+    // TODO(crbug.com/362862): Stop returning ENOSYS.
+    errno = ENOSYS;
+    return -1;
+  }
+  if (IsPageEndAddress(it->first)) {
+    // An IsPageEndAddress element should not be the first one.
+    ALOG_ASSERT(it != map_.begin());
+
+    // This condition means that |remove_start| is in the midst of an existing
+    // region.
+    // <start A>                <end A>  <start B>    <end B>
+    //     *-----------------------*         *-----------*
+    //               ^             ^
+    //         |remove_start|  |it->first|
+    // The iterator should point <start A> so to shrink the region A.
+    --it;
+  }
+
+  bool is_region_found = false;
+  while (it != map_.end()) {
+    PageToStream::iterator region_start_it = it;
+    PageToStream::iterator region_end_it = ++it;
+    // Since |region_start_it|, which is a !IsPageEndAddress element, is a valid
+    // iterator, |region_end_it| (and |it| here) should also be valid.
+    ALOG_ASSERT(it != map_.end());
+
+    // Check if [region_start_it->first, region_end_it->first] overlaps
+    // [remove_start, remove_end].
+    if (remove_end < region_start_it->first)
+      break;  // No overlap. No more memory regions to modify.
+
+    // We do not support partial unmapping for a duplicated mmap region. Note
+    // that memory_file.cc would return the same address from the two mmap calls
+    // below:
+    //   fd = ashmem_create_region();
+    //   void* addr1 = mmap(fd, 4096*3 /* length */);
+    //   void* addr2 = mmap(fd, 4096*3 /* the same length */);
+    //   munmap(addr1 + 4096, 4096);  // fail
+    ALOG_ASSERT((!abort_on_unexpected_memory_maps_) ||
+                (region_start_it->second.ref == 1),
+                "Cannot partially unmap a ref-counted region: "
+                "unmap_addr=%p, unmap_length=%zu, "
+                "mapped_addr=%p, mapped_length=%td",
+                addr, length, region_start_it->first,
+                region_end_it->first - region_start_it->first + 1);
+    if (region_start_it->second.ref > 1) {
+      errno = ENOSYS;  // return ENOSYS for unit tests.
+      return -1;
+    }
+
+    char* remove_start_in_region =
+        std::max(remove_start, region_start_it->first);
+    char* remove_end_in_region = std::min(remove_end, region_end_it->first);
+
+    // These two variables have to be assigned/updated here since
+    // RemoveOneRegion might invalidate iterators.
+    scoped_refptr<FileStream> current_stream = region_start_it->second.stream;
+    ++it;  // for the next iteration.
+
+    const PageMapValue value(1, region_start_it->second.offset, current_stream);
+    RemoveOneRegion(value,
+                    remove_start_in_region, remove_end_in_region,
+                    region_start_it->first, region_end_it->first);
+    is_region_found = true;  // modified at least one memory region.
+
+    // IsMemoryRangeAvailable() may insert a null stream.
+    if (current_stream) {
+      // Call REPORT_HANDLER() so that the current function call is
+      // categorized as |current_stream->GetStreamType()| rather than
+      // |kVirtualFileSystemHandlerStr| in virtual_file_system.cc.
+      ARC_STRACE_REPORT_HANDLER(current_stream->GetStreamType());
+      ARC_STRACE_REPORT("(%p-%p \"%s\")",
+                        remove_start_in_region,
+                        remove_end_in_region + 1,
+                        GetStreamPathname(current_stream).c_str());
+      size_t length = remove_end_in_region - remove_start_in_region + 1;
+      if (call_munmap) {
+        if (current_stream->munmap(remove_start_in_region, length)) {
+#if defined(__native_client__)
+          if (remove_start_in_region <
+              reinterpret_cast<char*>(kTextEndAddress)) {
+            // This path is taken when a DSO is unloaded with dlclose(), but
+            // under NaCl, unmapping text in the DSO with ::munmap() always
+            // fails with EINVAL. Log with ALOGE since this is a memory leak.
+            // TODO(crbug.com/380799): Stop special-casing NaCl once the
+            // restriction is removed.
+            ALOGE("NaCl does not support munmap() for text. "
+                  "Leaked %zu bytes of memory: (%p-%p \"%s\")",
+                  length,
+                  remove_start_in_region,
+                  remove_end_in_region + 1,
+                  GetStreamPathname(current_stream).c_str());
+            ARC_STRACE_REPORT("Do not call munmap for text under NaCl");
+          } else  // NOLINT(readability/braces)
+#endif
+          {
+            // munmap with a page-aligned |addr| and non-zero |length| should
+            // never fail. Since this function only handles valid addr/length
+            // pairs (see VFS::munmap), munmap failure here means that a serious
+            // memory error has already occured. Abort here with ALOGE.
+            ALOGE("FileStream::munmap failed with %d: (%p-%p \"%s\")",
+                  errno,
+                  remove_start_in_region,
+                  remove_end_in_region + 1,
+                  GetStreamPathname(current_stream).c_str());
+            ALOG_ASSERT(false);
+            return -1;
+          }
+        }
+      } else {
+        // Call OnUnmapByOverwritingMmap instead when |call_munmap| is false.
+        current_stream->OnUnmapByOverwritingMmap(
+            remove_start_in_region, length);
+      }
+    }
+
+    ALOG_ASSERT(it == map_.end() || !IsPageEndAddress(it->first));
+  }
+
+  if (!is_region_found) {
+    // TODO(crbug.com/362862): Stop returning ENOSYS.
+    errno = ENOSYS;
+    return -1;
+  }
+  // You can uncomment this to print the updated memory mappings.
+  //  ALOGI("\n%s", GetMemoryMapAsString().c_str());
+  return 0;
+}
+
+void MemoryRegion::RemoveOneRegion(PageMapValue value,
+                                   char* remove_start, char* remove_end,
+                                   char* region_start, char* region_end) {
+  ALOG_ASSERT(!IsPageEndAddress(remove_start));
+  ALOG_ASSERT(IsPageEndAddress(remove_end));
+  ALOG_ASSERT(!IsPageEndAddress(region_start));
+  ALOG_ASSERT(IsPageEndAddress(region_end));
+
+  // Split [region_start, region_end] if needed.
+  //
+  // [new_region_right_start, new_region_right_end] might be created as a result
+  // of the removal. For example, if the original region is [0,4], and [2,2] is
+  // removed, new_region_left will be [0,1] and new_region_right will be [3,4].
+  char* const new_region_right_start = remove_end + 1;
+  char* const new_region_right_end = region_end;
+  ptrdiff_t new_region_right_len =
+      new_region_right_end - new_region_right_start + 1;
+  ALOG_ASSERT(new_region_right_len >= 0);
+
+  // [new_region_left_start, new_region_left_end] might also be created after
+  // the removal.
+  char* const new_region_left_start = region_start;
+  char* const new_region_left_end = remove_start - 1;
+  ptrdiff_t new_region_left_len =
+      new_region_left_end - new_region_left_start + 1;
+  ALOG_ASSERT(new_region_left_len >= 0);
+
+  // Delete the memory region (and create new one(s) if it's partial unmapping).
+  if (new_region_left_len > 0) {
+    ALOG_ASSERT(IsPageEndAddress(new_region_left_end));
+    bool result = map_.insert(
+        std::make_pair(new_region_left_end, value)).second;
+    ALOG_ASSERT(result);
+  } else {
+    size_t result = map_.erase(new_region_left_start);
+    ALOG_ASSERT(result == 1);
+  }
+
+  if (new_region_right_len > 0) {
+    ALOG_ASSERT(!IsPageEndAddress(new_region_right_start));
+    bool result = map_.insert(
+        std::make_pair(new_region_right_start, value)).second;
+    ALOG_ASSERT(result);
+  } else {
+    size_t result = map_.erase(new_region_right_end);
+    ALOG_ASSERT(result == 1);
+  }
+}
+
+int MemoryRegion::SetAdviceByAddr(void* addr, size_t length, int advice) {
+  // Note: zero-length madvise succeeds on Linux. It returns with 0 without
+  // setting advice.
+  if (!length)
+    return 0;
+
+  AdviseVisitor visitor(advice);
+  CallByAddr(static_cast<char*>(addr), length, &visitor);
+  return visitor.Finish();
+}
+
+int MemoryRegion::ChangeProtectionModeByAddr(
+    void* addr, size_t length, int prot) {
+  // Note: zero-length mprotect succeeds on Linux. It returns with 0 without
+  // changing protection mode.
+  if (!length)
+    return 0;
+
+  ProtectionVisitor visitor(prot, &write_mapped_);
+  CallByAddr(static_cast<char*>(addr), length, &visitor);
+  return visitor.Finish();
+}
+
+bool MemoryRegion::IsWriteMapped(ino_t inode) const {
+  return write_mapped_.count(inode) > 0;
+}
+
+bool MemoryRegion::IsCurrentlyMapped(ino_t inode) const {
+  for (PageToStream::const_iterator it = map_.begin(); it != map_.end(); ++it) {
+    scoped_refptr<FileStream> stream = it->second.stream;
+    if (stream && (stream->inode() == inode))
+      return true;
+  }
+  return false;
+}
+
+std::string MemoryRegion::GetMemoryMapAsString() const {
+  std::string result =
+    "Range                 Length           Offset     Backend  FileSize"
+    "         Ref  Name\n";
+  if (map_.empty()) {
+    result += "(No memory mapped files)\n";
+    return result;
+  }
+
+  typedef base::hash_map<std::string, size_t> BackendStat;  // NOLINT
+  BackendStat per_backend;
+
+  for (PageToStream::const_iterator it = map_.begin(); it != map_.end(); ++it) {
+    char* start = it->first;
+    int ref = it->second.ref;
+    off64_t off = it->second.offset;
+    scoped_refptr<FileStream> stream = it->second.stream;
+    ++it;
+    if (it == map_.end()) {
+      ALOG_ASSERT("map_ is corrupted" == NULL);
+      result += "memory map is corrupted!\n";
+      break;
+    }
+    char* end = it->first;
+    if (!stream)
+      continue;
+
+    const size_t len = end - start + 1;
+    const std::string backend = stream->GetStreamType();
+
+    result += base::StringPrintf(
+        "0x%08" PRIxPTR "-0x%08" PRIxPTR " 0x%08x %4zuM 0x%08llx %-8s 0x%08x "
+        "%4zuM %-4d %s\n",
+        reinterpret_cast<uintptr_t>(start),
+        // Add one to make it look more like /proc/<pid>/maps.
+        reinterpret_cast<uintptr_t>(end) + 1,
+        len,
+        len / 1024 / 1024,
+        static_cast<uint64_t>(off),
+        backend.c_str(),
+        stream->GetSize(),
+        stream->GetSize() / 1024 / 1024,
+        ref,
+        GetStreamPathname(stream).c_str());
+    per_backend[backend] += len;
+  }
+
+  if (!per_backend.empty()) {
+    result += "Virtual memory usage per backend:\n";
+    for (BackendStat::const_iterator it = per_backend.begin();
+         it != per_backend.end(); ++it) {
+      result += base::StringPrintf(
+          " %-8s: %4zuMB (%zu bytes, %zu pages)\n",
+          it->first.c_str(),
+          it->second >> 20,
+          it->second,
+          it->second >> util::GetPageSizeAsNumBits());
+    }
+  }
+
+  return result;
+}
+
+bool MemoryRegion::IsOverlap(PageToStream::const_iterator begin,
+                             PageToStream::const_iterator end) const {
+  // Return true if there is another element between |addr_start| and
+  // |addr_end|.
+  if (std::distance(begin, end) != 1)
+    return true;
+
+  // Return true if there are two "start" elements in a row.
+  if (begin != map_.begin()) {
+    --begin;
+    const char* previous = begin->first;
+    if (!IsPageEndAddress(previous))
+      return true;
+  }
+
+  // No overlap. Do one more sanity check then return false.
+  if (end != map_.end() && ++end != map_.end()) {
+    const char* next = end->first;
+    ALOG_ASSERT(!IsPageEndAddress(next));
+  }
+  return false;
+}
+
+void MemoryRegion::PageMapVisitor::WriteFailureLog(
+  const char* name, bool visited, char* start_addr, char* end_addr,
+  const scoped_refptr<FileStream> stream) {
+  // Since we do not have a way to undo the previous FileStream call(s), and
+  // can not provide a posix compatible behavior, abort here.
+  // It is very unlikely to see the failure in practice.
+  LOG_ALWAYS_FATAL("%sFileStream::%s %sfailed with %d: (%p-%p \"%s\")",
+        (visited ? "One of " : ""),
+        name,
+        (visited ? "calls " : ""),
+        errno,
+        start_addr,
+        end_addr + 1,
+        GetStreamPathname(stream).c_str());
+}
+
+MemoryRegion::PageMapValue*
+MemoryRegion::FindRegion(char* addr, size_t length) {
+  PageToStream::iterator it = map_.find(addr);
+  if (it == map_.end())
+    return NULL;  // |addr| is not registered.
+
+  // Check the 'end' node.
+  PageToStream::iterator end_it(it);
+  ++end_it;
+  ALOG_ASSERT(end_it != map_.end());
+  char* end_addr = addr + length - 1;
+
+  if (end_it->first != end_addr)
+    return NULL;
+
+  return &(it->second);
+}
+
+void MemoryRegion::CallByAddr(
+    char* addr, size_t length, PageMapVisitor* visitor) {
+  ALOG_ASSERT(!(length % 2));
+  ALOG_ASSERT(!IsPageEndAddress(addr) && visitor);
+
+  char* const start_addr = addr;
+  char* const end_addr = start_addr + length - 1;
+
+  // Find the first region.
+  PageToStream::const_iterator it = map_.lower_bound(start_addr);
+  if (it == map_.end())
+    return;
+
+  if (IsPageEndAddress(it->first)) {
+    // An IsPageEndAddress element should not be the first one.
+    ALOG_ASSERT(it != map_.begin());
+    --it;
+  }
+
+  while (it != map_.end()) {
+    PageToStream::const_iterator region_start_it = it;
+    PageToStream::const_iterator region_end_it = ++it;
+    // Since |region_start_it|, which is a !IsPageEndAddress element, is a valid
+    // iterator, |region_end_it| (and |it| here) should also be valid.
+    ALOG_ASSERT(it != map_.end());
+
+    // Check if [region_start_it->first, region_end_it->first] overlaps
+    // [start_addr, end_addr].
+    if (end_addr < region_start_it->first)
+      break;  // No overlap. No more memory regions to visit.
+
+    const PageMapValue& page_map = region_start_it->second;
+    scoped_refptr<FileStream> current_stream = page_map.stream;
+    // IsMemoryRangeAvailable() may insert a null stream.
+    if (current_stream) {
+      char* const start_in_region =
+          std::max<char*>(start_addr, region_start_it->first);
+      char* const end_in_region =
+          std::min<char*>(end_addr, region_end_it->first);
+      if (!visitor->Visit(page_map, start_in_region, end_in_region))
+        break;
+    }
+
+    ++it;  // for the next iteration.
+    ALOG_ASSERT(it == map_.end() || !IsPageEndAddress(it->first));
+  }
+}
+
+// static
+bool MemoryRegion::IsPageEndAddress(const void* addr) {
+  return reinterpret_cast<uintptr_t>(addr) & 1;
+}
+
+MemoryRegion::PageMapValue::PageMapValue(
+    size_t in_ref, off64_t in_offset, scoped_refptr<FileStream> in_stream)
+    : ref(in_ref), offset(in_offset), stream(in_stream) {
+}
+
+MemoryRegion::PageMapValue::~PageMapValue() {
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/memory_region.h b/src/posix_translation/memory_region.h
new file mode 100644
index 0000000..4d0411c
--- /dev/null
+++ b/src/posix_translation/memory_region.h
@@ -0,0 +1,163 @@
+// Copyright 2014 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.
+//
+// A class to manage memory regions allocated via mmap with FileStream.
+
+#ifndef POSIX_TRANSLATION_MEMORY_REGION_H_
+#define POSIX_TRANSLATION_MEMORY_REGION_H_
+
+#include <sys/stat.h>  // ino_t
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+
+namespace posix_translation {
+
+class FileStream;
+
+// A class that contains memory regions with corresponding FileStreams for
+// mmap(), and calls underlying munmap() and mprotect() implementation for each
+// FileStream.
+class MemoryRegion {
+ public:
+  MemoryRegion();
+  ~MemoryRegion();
+
+  // Adds [addr, addr+length) to the PageToStream |map_|. Returns true on
+  // success. Returns false if [addr, addr+length) overlaps an existing entry in
+  // |map_|. |addr| must be aligned to 2-byte boundary. |length| must be a
+  // multiple of 2. |offset| is just for printing debug information. |prot| is
+  // a protection mode for the mapping (e.g. PROT_READ).
+  // Note: In PageMapValue, when an address is aligned to 2-byte boundary, it is
+  // treated as a "start" address. When it is not, it is an "end" address. To
+  // fulfill this, |length| must be a multiple of 2. With the rule, we do not
+  // have to have an "address type" member in PageMapValue which simplifies
+  // the code a little.
+  bool AddFileStreamByAddr(void* addr, size_t length, off64_t offset, int prot,
+                           int flags, scoped_refptr<FileStream> stream);
+
+  // Removes all memory regions in [addr, addr+length) from |map_|. This method
+  // may call FileStream::munmap() against file streams in the |map_|. This
+  // method may also remove zero, one or more file streams from the |map_|.
+  // |addr| must be aligned to 2-byte boundary. |length| must be a multiple of
+  // 2. If |call_munmap| is true, an underlying munmap() implementation is
+  // called for each region found in [addr, addr+length). Returns 0 on success.
+  // Returns -1 with errno on error. A special errno, ENOSYS, is set when no
+  // memory region to remove is found.
+  int RemoveFileStreamsByAddr(void* addr, size_t length, bool call_munmap);
+
+  // Sets advice about use of memory regions in [addr, addr+length). |addr|,
+  // |length|, and |advice| are the same with Linux's madvise().
+  int SetAdviceByAddr(void* addr, size_t length, int advice);
+
+  // Changes the protection mode of [addr, addr+length) to |prot|. This method
+  // may call FileStream::mprotect() against file streams in the |map_|. |addr|
+  // must be aligned to 2-byte boundary. |length| must be a multiple of 2.
+  // Returns 0 on success. Returns -1 with errno on error. A special errno,
+  // ENOSYS, is set when no memory region to modify is found.
+  int ChangeProtectionModeByAddr(void* addr, size_t length, int prot);
+
+  // Returns true if the file associated with |inode| is or was mmapped with
+  // PROT_WRITE. Note that posix_translation never reuses inode numbers.
+  bool IsWriteMapped(ino_t inode) const;
+
+  // Returns true if the file associated with |inode| is currently mmapped
+  // regardless of the protection mode.
+  bool IsCurrentlyMapped(ino_t inode) const;
+
+  // Get a list of mapped files in a human readable format.
+  std::string GetMemoryMapAsString() const;
+
+  struct PageMapValue {
+    PageMapValue(size_t ref, off64_t offset, scoped_refptr<FileStream> stream);
+    ~PageMapValue();
+
+    size_t ref;  // this field is only for "start" nodes.
+    off64_t offset;
+    // Adding one ref count per a continuous memory region is necessary here.
+    // This is because:
+    //
+    // 1) In user code, the fd might be closed right after mmap.
+    //      fd = open(...);  // ref count == 1
+    //      addr = mmap(fd, PAGESIZE);  // ref count == 2
+    //      close(fd);  // ref count == 1
+    //      munmap(addr, PAGESIZE);  // ref count == 0, |this| object is deleted
+    //
+    // 2) In user code, the mapped address might be partially unmapped.
+    //      fd = open(...);  // ref count == 1
+    //      addr = mmap(fd, PAGESIZE*3);  // ref count == 2
+    //      close(fd);  // ref count == 1
+    //      munmap(addr + PAGESIZE, PAGESIZE);  // ref count == 2
+    //      munmap(addr, PAGESIZE);  // ref count == 1
+    //      munmap(addr + PAGESIZE*2, PAGESIZE);  // ref count == 0
+    scoped_refptr<FileStream> stream;
+  };
+
+  class PageMapVisitor {
+   public:
+    virtual ~PageMapVisitor() {}
+    // Returns false if no more map walk is needed.
+    virtual bool Visit(const PageMapValue& page_map,
+                       char* start_addr, char* end_addr) = 0;
+   protected:
+    void WriteFailureLog(const char* name, bool visited,
+                         char* start_addr, char* end_addr,
+                         const scoped_refptr<FileStream> stream);
+  };
+
+ private:
+  friend class MemoryRegionTest;
+  friend class FileSystemTestCommon;
+
+  typedef std::map<char*, PageMapValue> PageToStream;
+
+  // Removes [remove_start, remove_end] from an existing memory region,
+  // [region_start, region_end].
+  // Examples:
+  //   1. Complete removal.
+  //   RemoveOneRegion(stream, 0x1000, 0x4000-1, 0x1000, 0x4000-1);
+  //   2. Partial removal.
+  //   RemoveOneRegion(stream, 0x2000, 0x3000-1, 0x1000, 0x4000-1);
+  void RemoveOneRegion(PageMapValue value,
+                       char* remove_start, char* remove_end,
+                       char* region_start, char* region_end);
+
+  // Returns true if the memory region [begin->first, end->first] overlaps an
+  // existing region in |map_|.
+  bool IsOverlap(PageToStream::const_iterator begin,
+                 PageToStream::const_iterator end) const;
+
+  // Returns a PageMapValue object if the exact region, [addr, addr+length),
+  // already exists in |map_|. Otherwise returns NULL.
+  PageMapValue* FindRegion(char* addr, size_t length);
+
+  // Calls |task| on all FileStream in the memory region [addr, addr+length).
+  void CallByAddr(char* addr, size_t length, PageMapVisitor* visitor);
+
+  // Returns true if |addr| is not aligned to 2-byte boundary.
+  static bool IsPageEndAddress(const void* addr);
+
+  // This map is an equivalent to Linux kernel's vm_area_struct AVL tree.
+  // Unlike the tree in kernel which only uses a "start" address as a key,
+  // |map_| uses both "start" and "end" addresses. This is to make
+  // AddFileStreamByAddr, especially the code for detecting memory region
+  // overlaps, very simple.
+  PageToStream map_;
+  bool abort_on_unexpected_memory_maps_;  // For unit testing.
+
+  // A set of inode numbers that is (or was) mapped with PROT_WRITE. Note that
+  // this set is append-only. RemoveFileStreamsByAddr() does not modify this
+  // set.
+  std::set<ino_t> write_mapped_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemoryRegion);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_MEMORY_REGION_H_
diff --git a/src/posix_translation/memory_region_test.cc b/src/posix_translation/memory_region_test.cc
new file mode 100644
index 0000000..ded45fb
--- /dev/null
+++ b/src/posix_translation/memory_region_test.cc
@@ -0,0 +1,1759 @@
+// Copyright 2014 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 <sys/mman.h>
+
+#include "base/compiler_specific.h"
+#include "gtest/gtest.h"
+#include "posix_translation/memory_region.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+// A macro for testing MemoryRegion whose public functions only accept addresses
+// aligned to 2-byte boundary.
+#define ALIGN_(n) __attribute__((__aligned__(n)))
+
+namespace posix_translation {
+
+class MemoryRegionTest : public FileSystemTestCommon {
+ protected:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    file_system_->memory_region_->abort_on_unexpected_memory_maps_ = false;
+  }
+  std::string GetMemoryMapAsString() {
+    return file_system_->GetMemoryMapAsStringLocked();
+  }
+  bool IsMemoryRangeAvailable(void* addr, size_t length) {
+    return file_system_->IsMemoryRangeAvailableLocked(addr, length);
+  }
+  bool AddFileStreamByAddr(void* addr, size_t length,
+                           scoped_refptr<FileStream> stream) {
+    return file_system_->memory_region_->AddFileStreamByAddr(
+        addr, length, 0 /* offset */, PROT_READ, 0 /* flags */, stream);
+  }
+  bool AddFileStreamByAddrWithProt(void* addr, size_t length, int prot,
+                                   scoped_refptr<FileStream> stream) {
+    return file_system_->memory_region_->AddFileStreamByAddr(
+        addr, length, 0 /* offset */, prot, 0 /* flags */, stream);
+  }
+  bool RemoveFileStreamsByAddr(void* addr, size_t length) {
+    const int result = file_system_->memory_region_->RemoveFileStreamsByAddr(
+        addr, length, true);
+    if (result == -1 && errno == ENOSYS)
+      return false;
+    EXPECT_EQ(0, result);
+    return true;
+  }
+  bool RemoveFileStreamsByAddrWithoutMunmap(void* addr, size_t length) {
+    const int result = file_system_->memory_region_->RemoveFileStreamsByAddr(
+        addr, length, false);
+    if (result == -1 && errno == ENOSYS)
+      return false;
+    EXPECT_EQ(0, result);
+    return true;
+  }
+  bool SetAdviceByAddr(void* addr, size_t length, int advice) {
+    const int result = file_system_->memory_region_->SetAdviceByAddr(
+        addr, length, advice);
+    if (result == -1)
+      return false;
+    EXPECT_EQ(0, result);
+    return true;
+  }
+  bool ChangeProtectionModeByAddr(void* addr, size_t length, int prot) {
+    const int result = file_system_->memory_region_->ChangeProtectionModeByAddr(
+        addr, length, prot);
+    if (result == -1 && errno == ENOSYS)
+      return false;
+    EXPECT_EQ(0, result);
+    return true;
+  }
+  bool IsWriteMapped(ino_t inode) {
+    return file_system_->memory_region_->IsWriteMapped(inode);
+  }
+  bool IsCurrentlyMapped(ino_t inode) {
+    return file_system_->memory_region_->IsCurrentlyMapped(inode);
+  }
+  bool IsPageEndAddress(const void* addr) {
+    return MemoryRegion::IsPageEndAddress(addr);
+  }
+  void ClearAddrMap() {
+    file_system_->memory_region_->map_.clear();
+  }
+  size_t GetAddrMapSize() const {
+    return file_system_->memory_region_->map_.size();
+  }
+
+  // Returns true if a memory region [addr, addr+length) exists in the map.
+  bool HasMemoryRegion(void* addr, size_t length) const {
+    typedef MemoryRegion::PageToStream::iterator Iterator;
+    char* addr_start = static_cast<char*>(addr);
+    char* addr_end = static_cast<char*>(addr) + length - 1;
+    Iterator it_start = file_system_->memory_region_->map_.find(addr_start);
+    Iterator it_end = file_system_->memory_region_->map_.find(addr_end);
+    Iterator not_found = file_system_->memory_region_->map_.end();
+    return (it_start != not_found) && (it_end != not_found) &&
+        (it_start->first < it_end->first) &&
+        (std::distance(it_start, it_end) == 1);
+  }
+};
+
+// A class template for TYPED_TEST_F. This template is instantiated N times
+// with each type in |TestTypes| below.
+template <typename T>
+class MemoryRegionTypedTest : public MemoryRegionTest {
+ protected:
+  void TestAddStreamByAddr();
+  void TestRemoveStreamByAddr();
+  void TestModifyStreamByAddr();
+};
+
+namespace {
+
+class StubFileStream : public FileStream {
+ public:
+  explicit StubFileStream(bool emulate_memory_file)
+      : FileStream(0, ""),
+        emulate_memory_file_(emulate_memory_file) {
+    Reset();
+  }
+
+  // When you need FileStream::inode() to return a valid value, use this
+  // constructor.
+  explicit StubFileStream(const std::string& pathname)
+      : FileStream(0, pathname),
+        emulate_memory_file_(true) {
+    Reset();
+  }
+
+  virtual bool ReturnsSameAddressForMultipleMmaps() const OVERRIDE {
+    return emulate_memory_file_;
+  }
+
+  virtual int munmap(void* addr, size_t length) OVERRIDE {
+    // Records the last |addr| and |length| of the unmapped region, that can
+    // be referred from tests.
+    last_munmap_addr = addr;
+    last_munmap_length = length;
+    ++munmap_count;
+    return 0;
+  }
+  virtual int mprotect(void *addr, size_t length, int prot) OVERRIDE {
+    // The same as above.
+    last_mprotect_addr = addr;
+    last_mprotect_length = length;
+    last_mprotect_prot = prot;
+    ++mprotect_count;
+    return 0;
+  }
+  virtual ssize_t read(void*, size_t) OVERRIDE { return -1; }
+  virtual ssize_t write(const void*, size_t) OVERRIDE { return -1; }
+  virtual const char* GetStreamType() const OVERRIDE { return "stub"; }
+
+  void Reset() {
+    last_munmap_addr = MAP_FAILED;
+    last_munmap_length = 0;
+    munmap_count = 0;
+
+    last_mprotect_addr = MAP_FAILED;
+    last_mprotect_length = 0;
+    last_mprotect_prot = 0;
+    mprotect_count = 0;
+  }
+
+  void* last_munmap_addr;
+  size_t last_munmap_length;
+  size_t munmap_count;
+
+  const void* last_mprotect_addr;
+  size_t last_mprotect_length;
+  int last_mprotect_prot;
+  size_t mprotect_count;
+
+ private:
+  const bool emulate_memory_file_;
+};
+
+}  // namespace
+
+// The size of the array must be >=2 and must be even.
+typedef ::testing::Types<char[2], char[4], char[6], char[4096]> TestTypes;
+TYPED_TEST_CASE(MemoryRegionTypedTest, TestTypes);
+
+#define TYPED_TEST_F(_fixture, _test)                        \
+  TYPED_TEST(_fixture, _test) {                              \
+    this->_test();                                           \
+  }                                                          \
+  template <typename TypeParam>                              \
+  void _fixture<TypeParam>::_test()
+
+// Tests all corner cases of AddStreamByAddr.
+TYPED_TEST_F(MemoryRegionTypedTest, TestAddStreamByAddr) {
+  static const size_t kSize = sizeof(TypeParam);
+  struct TestAddresses {
+    TypeParam region0;  // TypeParam is char[N] (N = 2,4,..).
+    TypeParam region1;
+    TypeParam region2;
+    TypeParam region3;
+    TypeParam region4;
+    TypeParam region5;
+  } addresses ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream = new StubFileStream(true);
+  // First, insert region2.
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region2, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region2, kSize));
+
+  // Try to insert regions which overlap |region2|. They should all fail.
+  // except the "exactly the same" cases.
+
+  // Exactly the same.
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region2, kSize, stream));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region2, kSize));
+  // Left aligned.
+  EXPECT_FALSE(AddFileStreamByAddr(addresses.region2, kSize - 2, stream));
+  // Right aligned.
+  if (kSize == 2) {
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region2 + kSize - 2, 2, stream));
+    EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region2 + kSize - 2, 2));
+  } else {
+    EXPECT_FALSE(AddFileStreamByAddr(addresses.region2 + kSize - 2, 2, stream));
+  }
+  // Overlaps left, right aligned.
+  EXPECT_FALSE(AddFileStreamByAddr(
+      addresses.region1 + (kSize - 2), kSize + 2, stream));
+  // Overlaps right, left aligned.
+  EXPECT_FALSE(AddFileStreamByAddr(addresses.region2, kSize + 2, stream));
+  // Overlaps both.
+  EXPECT_FALSE(AddFileStreamByAddr(
+      addresses.region1 + (kSize - 2), kSize + 4, stream));
+  if (kSize > 2) {
+    // Overlaps left.
+    EXPECT_FALSE(AddFileStreamByAddr(
+        addresses.region1 + (kSize - 2), kSize, stream));
+    // Overlaps right.
+    EXPECT_FALSE(AddFileStreamByAddr(addresses.region2 + 2, kSize, stream));
+    if (kSize > 4) {
+      // Contained.
+      EXPECT_FALSE(AddFileStreamByAddr(addresses.region2 + 2,
+                                       kSize - 4, stream));
+    }
+  }
+  // Confirm that AddFileStreamByAddr failures don't corrupt the tree.
+  EXPECT_TRUE(HasMemoryRegion(addresses.region2, kSize));
+
+  // Try to insert regions that don't overlap |region2|.
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region0, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region0, kSize));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region4, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region4, kSize));
+  // Add with length==0 should always fail.
+  EXPECT_FALSE(AddFileStreamByAddr(addresses.region5, 0, stream));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region5, 2, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region5, 2));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region3, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region3, kSize));
+
+  // Check the tree status again, just in case.
+  EXPECT_TRUE(HasMemoryRegion(addresses.region2, kSize));
+
+  // Remove all regions.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region0, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region2, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region3, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region4, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region5, 2));
+}
+
+// Resets the map, then adds |stream1| and |stream2| to the map in the
+// following way:
+//
+// * region[0] to [1] are unused.
+// * region[2] to [4] are backed by |stream1|.
+// * region[5] to [7] are unused.
+// * region[8] to [10] are backed by |stream2|.
+// * region[11] to [12] are unused.
+//
+//  0 1 2 3 4 5 6 7 8 9 A B C
+// |E|E|1|1|1|E|E|E|2|2|2|E|E|
+#define RESET()                                                                \
+  do {                                                                         \
+    ClearAddrMap();                                                            \
+    stream1->Reset();                                                          \
+    stream2->Reset();                                                          \
+    stream3->Reset();                                                          \
+    expected_count1 = expected_count2 = expected_count3 = 0;                   \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[2], kSize * 3, stream1)); \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[8], kSize * 3, stream2)); \
+  } while (false)
+
+// Resets the map, then adds |stream1| and |stream2| to the map in the
+// following way:
+//
+// * region[0] to [1] are unused.
+// * region[2] to [4] are backed by |stream1|.
+// * region[5] to [7] are backed by |stream2|. No gap between the two streams.
+// * region[8] to [12] are unused.
+//
+//  0 1 2 3 4 5 6 7 8 9 A B C
+// |E|E|1|1|1|2|2|2|E|E|E|E|E|
+#define RESET2()                                                               \
+  do {                                                                         \
+    ClearAddrMap();                                                            \
+    stream1->Reset();                                                          \
+    stream2->Reset();                                                          \
+    stream3->Reset();                                                          \
+    expected_count1 = expected_count2 = expected_count3 = 0;                   \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[2], kSize * 3, stream1)); \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[5], kSize * 3, stream2)); \
+  } while (false)
+
+// Resets the map, then adds |stream1|, |stream2|, and |stream3| to the map in
+// the following way:
+//
+// * region[0] to [1] are unused.
+// * region[2] to [4] are backed by |stream1|.
+// * region[5] to [7] are backed by |stream2|.
+// * region[8] to [108] are backed by |stream3|.
+// * region[11] to [12] are unused.
+//
+//  0 1 2 3 4 5 6 7 8 9 A B C
+// |E|E|1|1|1|2|2|2|3|3|3|E|E|
+#define RESET3()                                                               \
+  do {                                                                         \
+    ClearAddrMap();                                                            \
+    stream1->Reset();                                                          \
+    stream2->Reset();                                                          \
+    stream3->Reset();                                                          \
+    expected_count1 = expected_count2 = expected_count3 = 0;                   \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[2], kSize * 3, stream1)); \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[5], kSize * 3, stream2)); \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region[8], kSize * 3, stream3)); \
+  } while (false)
+
+#define CHECK_MUNMAP_COUNT()                                                   \
+  do {                                                                         \
+    EXPECT_EQ(expected_count1, stream1->munmap_count);                         \
+    EXPECT_EQ(expected_count2, stream2->munmap_count);                         \
+    EXPECT_EQ(expected_count3, stream3->munmap_count);                         \
+  } while (false)
+
+#define CHECK_MPROTECT_COUNT()                                                 \
+  do {                                                                         \
+    EXPECT_EQ(expected_count1, stream1->mprotect_count);                       \
+    EXPECT_EQ(expected_count2, stream2->mprotect_count);                       \
+    EXPECT_EQ(expected_count3, stream3->mprotect_count);                       \
+  } while (false)
+
+// Tests if RemoveStreamByAddr removes one or more memory regions correctly.
+TYPED_TEST_F(MemoryRegionTypedTest, TestRemoveStreamByAddr) {
+  static const size_t kSize = sizeof(TypeParam);
+  struct TestAddresses {
+    TypeParam region[13];  // TypeParam is char[N] (N = 2,4,..).
+  } addresses ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream1 = new StubFileStream(true);
+  scoped_refptr<StubFileStream> stream2 = new StubFileStream(true);
+  scoped_refptr<StubFileStream> stream3 = new StubFileStream(true);
+  size_t expected_count1 = 0;  // for |stream1|
+  size_t expected_count2 = 0;  // for |stream2|
+  size_t expected_count3 = 0;  // for |stream3|
+
+  // Delete [0]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[0], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [0]-[1]. no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 2));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [0]-[2]. The first block of |stream1| should be removed.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 3));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[3], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [0]-[4]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 5));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [0]-[5]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 6));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [0]-[7]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 8));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [0]-[8]. |stream1| and the first block of |stream2| should be
+  // removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 9));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [0]-[10]. Both |stream1| and |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 11));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Delete [0]-[11]. Both |stream1| and |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[0], kSize * 12));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Change the base position to [1].
+
+  // Delete [1]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[1], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Change the base position to [2].
+
+  // Delete [2]. The first block of |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[3], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [2]-[4]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 3));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [2]-[5]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 4));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [2]-[7]. |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 6));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [2]-[8]. |stream1| and the first block of |stream2| should be
+  // removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 7));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [2]-[10]. Both |stream1| and |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 9));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Delete [2]-[11]. Both |stream1| and |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 10));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Change the base position to [3].
+
+  // Delete [3]. The second block of |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[3], kSize));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[3], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[4], kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(6U, GetAddrMapSize());  // The first region should split.
+
+  // Change the base position to [4].
+
+  // Delete [4]. The last block of |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [4]-[5]. The last block of |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 2));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [4]-[7]. The last block of |stream1| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 4));
+  ++expected_count1;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [4]-[8]. The last block of |stream1| and the first block of
+  // |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 5));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [4]-[10]. The last block of |stream1| and all blocks of |stream2|
+  // should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 7));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [4]-[11]. The last block of |stream1| and all blocks of |stream2|
+  // should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 8));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Change the base position to [5].
+
+  // Delete [5]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[5], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [5]-[6]. no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[5], kSize * 2));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [5]-[7]. no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[5], kSize * 3));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [5]-[8]. The first block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[5], kSize * 4));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [5]-[10]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[5], kSize * 6));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [5]-[11]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[5], kSize * 7));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Change the base position to [6].
+
+  // Delete [6]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[6], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [6]-[7]. no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[6], kSize * 2));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Change the base position to [7].
+
+  // Delete [7]. This should be no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[7], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [7]-[8]. The first block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[7], kSize * 2));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [7]-[10]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[7], kSize * 4));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [7]-[11]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[7], kSize * 5));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Change the base position to [8].
+
+  // Delete [8]. The first block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[8], kSize));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [8]-[10]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[8], kSize * 3));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Delete [8]-[11]. |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[8], kSize * 4));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Change the base position to [9].
+
+  // Delete [9]. The second block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[9], kSize));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[9], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[10], kSize));
+  EXPECT_EQ(6U, GetAddrMapSize());  // split
+
+  // Change the base position to [10].
+
+  // Delete [10]. The last block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[10], kSize));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[10], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [10]-[11]. The last block of |stream2| should be removed.
+  RESET();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[10], kSize * 2));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[10], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Change the base position to [11].
+
+  // Delete [11]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[11], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [11]-[12]. This should be no-op.
+  RESET();
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[11], kSize * 2));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Change the base position to [12].
+
+  // Delete [12]. This should be no-op.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region[12], kSize));
+  CHECK_MUNMAP_COUNT();
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [4]-[5]. The last block of |stream1| and the first block of
+  // |stream2| should be removed.
+  RESET2();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 2));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[5], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[6], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [2]-[7]. Both streams should be removed.
+  RESET2();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[2], kSize * 6));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[5], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Delete [1]-[8]. Both streams should be removed.
+  RESET2();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[1], kSize * 8));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[5], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+
+  // Delete [6]. The second block of |stream2| should be removed.
+  RESET3();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[6], kSize));
+  ++expected_count2;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[6], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[5], kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[7], kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[8], kSize * 3));
+  EXPECT_EQ(8U, GetAddrMapSize());  // split
+
+  // Delete [7]-[8]. The last block of |stream2| and the first block of
+  // |stream3| should be removed.
+  RESET3();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[7], kSize * 2));
+  ++expected_count2;
+  ++expected_count3;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[7], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize, stream2->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream3->last_munmap_addr);
+  EXPECT_EQ(kSize, stream3->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 3));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[5], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(6U, GetAddrMapSize());
+
+  // Delete [4]-[8]. The last block of |stream1| and the first block of
+  // |stream3| should be removed. |stream2| should be gone.
+  RESET3();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[4], kSize * 5));
+  ++expected_count1;
+  ++expected_count2;
+  ++expected_count3;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[5], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream3->last_munmap_addr);
+  EXPECT_EQ(kSize, stream3->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[2], kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region[9], kSize * 2));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Delete [1]-[11]. |stream1|, |stream2|, and |stream3| should be gone.
+  RESET3();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region[1], kSize * 11));
+  ++expected_count1;
+  ++expected_count2;
+  ++expected_count3;
+  CHECK_MUNMAP_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_munmap_length);
+  EXPECT_EQ(addresses.region[5], stream2->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_munmap_length);
+  EXPECT_EQ(addresses.region[8], stream3->last_munmap_addr);
+  EXPECT_EQ(kSize * 3, stream3->last_munmap_length);
+  EXPECT_EQ(0U, GetAddrMapSize());
+}
+
+// Tests if ModifyStreamByAddr modifies one or more memory regions properly.
+TYPED_TEST_F(MemoryRegionTypedTest, TestModifyStreamByAddr) {
+  static const size_t kSize = sizeof(TypeParam);
+  struct TestAddresses {
+    TypeParam region[13];  // TypeParam is char[N] (N = 2,4,..).
+  } addresses ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream1 = new StubFileStream(true);
+  scoped_refptr<StubFileStream> stream2 = new StubFileStream(true);
+  scoped_refptr<StubFileStream> stream3 = new StubFileStream(true);
+  size_t expected_count1 = 0;  // for |stream1|
+  size_t expected_count2 = 0;  // for |stream2|
+  size_t expected_count3 = 0;  // for |stream3|
+
+  // Test modifications to |stream1|.
+  RESET();
+
+  // Modify [0]. This should be no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[0], kSize, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Modify [0]-[1]. no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[0], kSize * 2, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Modify [0]-[2]. The first block of |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[0], kSize * 3, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [0]-[4]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[0], kSize * 5, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [1]-[2]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[1], kSize * 2, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [2]-[3]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[2], kSize * 2, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [3]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[3], kSize, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[3], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [2]-[4]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[2], kSize * 3, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [3]-[4]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[3], kSize * 2, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[3], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [4]-[6]. |stream1| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[4], kSize * 3, PROT_READ));
+  ++expected_count1;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+
+  // Modify [5]-[6]. no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[5], kSize * 2, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Test modifications to |stream2|.
+
+  // Modify [6]. This should be no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[6], kSize, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Modify [6]-[7]. no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[6], kSize * 2, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Modify [6]-[8]. The first block of |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[6], kSize * 3, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [6]-[A]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[6], kSize * 5, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [7]-[8]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[7], kSize * 2, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [8]-[9]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[8], kSize * 2, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [9]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[9], kSize, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[9], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [8]-[A]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[8], kSize * 3, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [9]-[A]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[9], kSize * 2, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[9], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [A]-[C]. |stream2| should be modified.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[0xA], kSize * 3, PROT_READ));
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[0xA], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [B]-[C]. no-op.
+  EXPECT_FALSE(ChangeProtectionModeByAddr(
+      addresses.region[0xB], kSize * 2, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+
+  // Modify |stream1| and |stream2| at the same time.
+
+  // Modify [1]-[B].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[1], kSize * 11, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [2]-[A].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[2], kSize * 9, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [3]-[9].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[3], kSize * 7, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[3], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [4]-[8].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[4], kSize * 5, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[8], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify |stream1| and |stream2| at the same time.
+  RESET2();
+
+  // Modify [1]-[8].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[1], kSize * 8, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[5], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [2]-[7].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[2], kSize * 6, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[2], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[5], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 3, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [3]-[6].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[3], kSize * 4, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[3], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[5], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize * 2, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Modify [4]-[5].
+  EXPECT_TRUE(ChangeProtectionModeByAddr(
+      addresses.region[4], kSize * 2, PROT_READ));
+  ++expected_count1;
+  ++expected_count2;
+  CHECK_MPROTECT_COUNT();
+  EXPECT_EQ(addresses.region[4], stream1->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream1->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream1->last_mprotect_prot);
+  EXPECT_EQ(addresses.region[5], stream2->last_mprotect_addr);
+  EXPECT_EQ(kSize, stream2->last_mprotect_length);
+  EXPECT_EQ(PROT_READ, stream2->last_mprotect_prot);
+
+  // Test zero-length modify. It should always succeed.
+  RESET();
+  EXPECT_TRUE(ChangeProtectionModeByAddr(addresses.region[4], 0, PROT_READ));
+  CHECK_MPROTECT_COUNT();
+}
+
+TEST_F(MemoryRegionTest, TestGetMemoryMapAsString) {
+  // The debug function should return something non-empty.
+  EXPECT_NE(std::string(), GetMemoryMapAsString());
+}
+
+// Tests IsWriteMapped and IsCurrentlyMapped.
+TEST_F(MemoryRegionTest, TestIsMappedFunctions) {
+  static const size_t kSize = 8;
+  struct {
+    char addr1[kSize];
+    char addr2[kSize];
+    char addr3[kSize];
+    char addr4[kSize];
+  } m ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream1 =
+      new StubFileStream(std::string("/path/1"));
+  scoped_refptr<StubFileStream> stream2 =
+      new StubFileStream(std::string("/path/2"));
+  scoped_refptr<StubFileStream> stream3 =
+      new StubFileStream(std::string("/path/3"));
+  scoped_refptr<StubFileStream> stream4 =
+      new StubFileStream(std::string("/path/4"));
+
+  // Initially IsWriteMapped and IsCurrentlyMapped should both return false.
+  EXPECT_FALSE(IsWriteMapped(stream1->inode()));
+  EXPECT_FALSE(IsWriteMapped(stream2->inode()));
+  EXPECT_FALSE(IsWriteMapped(stream3->inode()));
+  EXPECT_FALSE(IsWriteMapped(stream4->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream3->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream4->inode()));
+
+  // Map files.
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr1, kSize, PROT_READ, stream1));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr2, kSize, PROT_WRITE, stream2));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr2, kSize));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr2, kSize, PROT_WRITE, stream2));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(
+      m.addr3, kSize, PROT_READ | PROT_WRITE, stream3));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr4, kSize, PROT_NONE, stream4));
+
+  // Test the functions again.
+  EXPECT_FALSE(IsWriteMapped(stream1->inode()));
+  EXPECT_TRUE(IsWriteMapped(stream2->inode()));
+  EXPECT_TRUE(IsWriteMapped(stream3->inode()));
+  EXPECT_FALSE(IsWriteMapped(stream4->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream3->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream4->inode()));
+
+  // Change from PROT_READ to PROT_WRITE.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(m.addr1, kSize, PROT_WRITE));
+  // Change from PROT_WRITE to PROT_READ.
+  EXPECT_TRUE(ChangeProtectionModeByAddr(m.addr1, kSize, PROT_WRITE));
+
+  // Test the functions again.
+  EXPECT_TRUE(IsWriteMapped(stream1->inode()));
+  EXPECT_TRUE(IsWriteMapped(stream2->inode()));  // still return true
+  EXPECT_TRUE(IsWriteMapped(stream3->inode()));
+  EXPECT_FALSE(IsWriteMapped(stream4->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream3->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream4->inode()));
+
+  // Partially unmap |m.addr1| and |m.addr2|, then confirm IsXXXMapped still
+  // returns true.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr1, kSize / 2));
+  EXPECT_TRUE(IsWriteMapped(stream1->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(
+      m.addr2 + 2, kSize / 2));  // split the region into two
+  EXPECT_TRUE(IsWriteMapped(stream2->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+
+  // Unmap all memory regions, then test the functions again. IsWriteMapped
+  // should still return true for |stream1|, |stream2|, and |stream3|.
+  // Note: Removing the same address twice or more is safe.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr1, kSize));
+  EXPECT_TRUE(IsWriteMapped(stream1->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream1->inode()));
+
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr2, kSize));
+  EXPECT_TRUE(IsWriteMapped(stream2->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream2->inode()));
+
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr3, kSize));
+  EXPECT_TRUE(IsWriteMapped(stream3->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream3->inode()));
+
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr4, kSize));
+  EXPECT_FALSE(IsWriteMapped(stream4->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream4->inode()));
+
+  // Final sanity checks.
+  EXPECT_TRUE(IsWriteMapped(stream1->inode()));
+  EXPECT_TRUE(IsWriteMapped(stream2->inode()));
+  EXPECT_TRUE(IsWriteMapped(stream3->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream3->inode()));
+}
+
+// Tests IsCurrentlyMapped more.
+TEST_F(MemoryRegionTest, TestIsCurrentlyMapped) {
+  static const size_t kSize = 2;
+  struct {
+    char addr1[kSize];
+    char addr2[kSize];
+    char addr3[kSize];
+  } m ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream1 =
+      new StubFileStream(std::string("/path/1"));
+  scoped_refptr<StubFileStream> stream2 =
+      new StubFileStream(std::string("/path/2"));
+  scoped_refptr<StubFileStream> stream3 =
+      new StubFileStream(std::string("/path/3"));
+
+  EXPECT_FALSE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream3->inode()));
+
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr1, kSize, PROT_READ, stream1));
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr2, kSize, PROT_WRITE, stream2));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr2, kSize));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr2, kSize, PROT_WRITE, stream2));
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(AddFileStreamByAddrWithProt(m.addr3, kSize, PROT_NONE, stream3));
+
+  EXPECT_TRUE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream3->inode()));
+
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr1, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr2, kSize));
+  EXPECT_FALSE(IsCurrentlyMapped(stream1->inode()));
+  EXPECT_FALSE(IsCurrentlyMapped(stream2->inode()));
+  EXPECT_TRUE(IsCurrentlyMapped(stream3->inode()));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr3, kSize));
+  EXPECT_FALSE(RemoveFileStreamsByAddr(m.addr3, kSize));
+  EXPECT_FALSE(IsCurrentlyMapped(stream3->inode()));
+}
+
+TEST_F(MemoryRegionTest, TestIsPageEndAddress) {
+  uintptr_t ptr = 0x0;
+  EXPECT_FALSE(IsPageEndAddress(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_TRUE(IsPageEndAddress(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_FALSE(IsPageEndAddress(reinterpret_cast<void*>(ptr)));
+  ++ptr;
+  EXPECT_TRUE(IsPageEndAddress(reinterpret_cast<void*>(ptr)));
+}
+
+TEST_F(MemoryRegionTest, TestIsMemoryRangeAvailable) {
+  static const size_t kPageSize = 4096;
+  static const size_t kLength = kPageSize * 3;
+  struct {
+    char addr_before[kPageSize];
+    char addr[kLength];
+    char addr_after[kPageSize];
+  } m ALIGN_(2);
+
+  // Initially, the function should always return true.
+  EXPECT_TRUE(IsMemoryRangeAvailable(m.addr, kLength));
+
+  scoped_refptr<StubFileStream> stream = new StubFileStream(true);
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr, kLength, stream));
+  EXPECT_FALSE(IsMemoryRangeAvailable(m.addr, kLength));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr, kLength));
+  EXPECT_EQ(m.addr, stream->last_munmap_addr);
+  EXPECT_EQ(kLength, stream->last_munmap_length);
+  EXPECT_TRUE(IsMemoryRangeAvailable(m.addr, kLength));
+  for (int i = 0; i < 3; ++i) {
+    EXPECT_TRUE(AddFileStreamByAddr(m.addr + kPageSize * i, kPageSize, stream));
+    EXPECT_FALSE(IsMemoryRangeAvailable(m.addr, kLength));
+    EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr + kPageSize * i, kPageSize));
+    EXPECT_EQ(m.addr + kPageSize * i, stream->last_munmap_addr);
+    EXPECT_EQ(kPageSize, stream->last_munmap_length);
+    EXPECT_TRUE(IsMemoryRangeAvailable(m.addr, kLength));
+  }
+  // Out of range.
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr_before, kPageSize, stream));
+  EXPECT_TRUE(IsMemoryRangeAvailable(m.addr, kLength));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr_before, kPageSize));
+  EXPECT_EQ(m.addr_before, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  // Out of range.
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr_after, kPageSize, stream));
+  EXPECT_TRUE(IsMemoryRangeAvailable(m.addr, kLength));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr_after, kPageSize));
+  EXPECT_EQ(m.addr_after, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+}
+
+// Tests common Add/RemoveStreamByAddr usage.
+TEST_F(MemoryRegionTest, TestAddRemoveStreamByAddr) {
+  static const size_t kPageSize = 4096;
+  static const size_t length = kPageSize * 5;
+  char addr1[kPageSize * 5] ALIGN_(2);
+
+  // Initially the tree is empty.
+  EXPECT_EQ(0U, GetAddrMapSize());
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addr1, kPageSize));
+
+  scoped_refptr<StubFileStream> stream = new StubFileStream(true);
+  EXPECT_TRUE(AddFileStreamByAddr(addr1, length, stream));
+
+  // Remove the first page.
+  // Still possible to use the last 4 pages.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1, kPageSize));
+  EXPECT_EQ(addr1, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addr1 + kPageSize, kPageSize * 4));
+
+  // Remove the last page.
+  // Still possible to use the 3 pages in the middle.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + kPageSize * 4, kPageSize));
+  EXPECT_EQ(addr1 + kPageSize * 4, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  EXPECT_TRUE(HasMemoryRegion(addr1 + kPageSize, kPageSize * 3));
+  // Remove the third page.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + kPageSize * 2, kPageSize));
+  EXPECT_EQ(addr1 + kPageSize * 2, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  // Still possible to use the 2nd and 4th pages.
+  EXPECT_TRUE(HasMemoryRegion(addr1 + kPageSize, kPageSize));
+  EXPECT_TRUE(HasMemoryRegion(addr1 + kPageSize * 3, kPageSize));
+  // It's possible to reuse the removed pages.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + kPageSize, kPageSize));
+  EXPECT_EQ(addr1 + kPageSize, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  EXPECT_TRUE(AddFileStreamByAddr(addr1 + kPageSize / 2, kPageSize, stream));
+  EXPECT_TRUE(AddFileStreamByAddr(addr1 + kPageSize * 4, kPageSize, stream));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + kPageSize * 4, kPageSize));
+  EXPECT_EQ(addr1 + kPageSize * 4, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + kPageSize / 2, kPageSize));
+  EXPECT_EQ(addr1 + kPageSize / 2, stream->last_munmap_addr);
+  EXPECT_EQ(kPageSize, stream->last_munmap_length);
+
+  // Remove the 4th page.
+  for (size_t i = kPageSize * 3; i < kPageSize * 4; ++i) {
+    // Delete 0-1, 4-5, 8-9, .. elements.
+    if (i % 4 == 0) {
+      EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + i, 2)) << i;
+      EXPECT_EQ(addr1 + i, stream->last_munmap_addr);
+      EXPECT_EQ(2U, stream->last_munmap_length);
+    }
+  }
+  for (size_t i = kPageSize * 4 - 1; i >= kPageSize * 3; --i) {
+    // Delete 2, 3, 6, 7, .. elements.
+    if (i % 4 == 2) {
+      EXPECT_TRUE(RemoveFileStreamsByAddr(addr1 + i, 2)) << i;
+      EXPECT_EQ(addr1 + i, stream->last_munmap_addr);
+      EXPECT_EQ(2U, stream->last_munmap_length);
+    }
+  }
+
+  // Confirm all elements are now gone.
+  EXPECT_EQ(0U, GetAddrMapSize());
+}
+
+TEST_F(MemoryRegionTest, TestAddRemoveStreamByAddrDupRegion) {
+  static const size_t kSize = 16;
+  struct TestAddresses {
+    char region0[kSize];
+    char region1[kSize];
+  } addresses ALIGN_(2);
+
+  // Initially the tree is empty.
+  EXPECT_EQ(0U, GetAddrMapSize());
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  // The second/third AddFileStreamByAddr calls should succeed.
+  scoped_refptr<StubFileStream> stream = new StubFileStream(true);
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_TRUE(AddFileStreamByAddr(addresses.region1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_EQ(2U, GetAddrMapSize());  // not 6. it's ref-counted.
+  // Try to remove regions which overlap |region1| in many ways. They should all
+  // fail except the "exactly the same" case.
+
+  // Exactly the same. Ref count decreases to 2.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  // Left aligned.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1, kSize - 2));
+  // Right aligned.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + kSize - 2, 2));
+  // Overlaps left, right aligned.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + (kSize - 2),
+                                       kSize + 2));
+  // Overlaps right, left aligned.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1, kSize + 2));
+  // Overlaps both.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + (kSize - 2),
+                                       kSize + 4));
+  // Overlaps left.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + (kSize - 2), kSize));
+  // Overlaps right.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + 2, kSize));
+  // Contained.
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1 + 2, kSize - 4));
+  // Ref count should still be 2.
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_EQ(2U, GetAddrMapSize());
+
+  // Remove twice. Ref count goes down to 0.
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  EXPECT_EQ(addresses.region1, stream->last_munmap_addr);
+  EXPECT_EQ(kSize, stream->last_munmap_length);
+  EXPECT_FALSE(HasMemoryRegion(addresses.region1, kSize));
+  EXPECT_EQ(0U, GetAddrMapSize());
+}
+
+// Resets the map, then adds |stream| to the map in the following way:
+//
+// * region[0] to [3] and [8] to [11] are unused.
+// * region[4] to [7] are allocated at once and backed by |stream|.
+//
+//  0 1 2 3 4 5 6 7 8 9 A B
+// |E|E|E|E|S|S|S|S|E|E|E|E|
+#define RESET4()                                                             \
+  do {                                                                       \
+    ClearAddrMap();                                                          \
+    EXPECT_TRUE(AddFileStreamByAddr(addresses.region4, kBlockSize, stream)); \
+    EXPECT_TRUE(HasMemoryRegion(addresses.region4, kBlockSize));             \
+    EXPECT_EQ(2U, GetAddrMapSize());                                         \
+  } while (false)
+
+// Tests Add/RemoveStreamByAddr usage with FileStream that does not return
+// the same address for multiple mmap() requests.
+TEST_F(MemoryRegionTest, TestAddRemovePosixCompliantFileStream) {
+  static const size_t kSize = 2;
+  static const size_t kBlockSize = kSize * 4;
+  struct TestAddresses {
+    char region0[kSize];
+    char region1[kSize];
+    char region2[kSize];
+    char region3[kSize];
+    char region4[kSize];
+    char region5[kSize];
+    char region6[kSize];
+    char region7[kSize];
+    char region8[kSize];
+    char region9[kSize];
+    char region10[kSize];
+    char region11[kSize];
+  } addresses ALIGN_(2);
+
+  // Initially the tree is empty.
+  EXPECT_EQ(0U, GetAddrMapSize());
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region1, kSize));
+  scoped_refptr<StubFileStream> stream = new StubFileStream(false);
+  // Try to remove regions which overlap |region1| in many ways. They should all
+  // pass.
+
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region4, kBlockSize));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  // Left aligned.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region4, kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  // Right aligned.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region5, kSize * 3));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  // Overlaps left, right aligned.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region3, kSize * 5));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  // Overlaps right, left aligned.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region4, kSize * 5));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  // Overlaps both.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region2, kSize * 7));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  // Overlaps left.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region1, kSize * 4));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  // Overlaps right.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region6, kSize * 4));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  // Contained.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region5, kSize * 2));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region4, kSize));
+  EXPECT_TRUE(HasMemoryRegion(addresses.region7, kSize));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // Remove twice. The second call should fail, but break nothing.
+  RESET4();
+  EXPECT_TRUE(RemoveFileStreamsByAddr(addresses.region4, kBlockSize));
+  EXPECT_FALSE(HasMemoryRegion(addresses.region4, kBlockSize));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  EXPECT_FALSE(RemoveFileStreamsByAddr(addresses.region4, kBlockSize));
+  EXPECT_FALSE(HasMemoryRegion(addresses.region4, kBlockSize));
+  EXPECT_EQ(0U, GetAddrMapSize());
+}
+
+// Tests RemoveStreamByAddr usage with |call_munmap| to check if underlying
+// munmap() is called or not called.
+TEST_F(MemoryRegionTest, TestRemoveStreamWithoutMunmap) {
+  static const size_t kSize = 8;
+  struct {
+    char addr1[kSize];
+    char addr2[kSize];
+    char addr3[kSize];
+  } m ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream = new StubFileStream(true);
+
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(m.addr1, kSize));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr2, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(m.addr2, kSize));
+  EXPECT_EQ(4U, GetAddrMapSize());
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr3, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(m.addr3, kSize));
+  EXPECT_EQ(6U, GetAddrMapSize());
+  EXPECT_EQ(0U, stream->munmap_count);
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr1, kSize));
+  EXPECT_EQ(4U, GetAddrMapSize());
+  EXPECT_EQ(1U, stream->munmap_count);
+  EXPECT_EQ(m.addr1, stream->last_munmap_addr);
+  EXPECT_EQ(kSize, stream->last_munmap_length);
+
+  // RemoveFileStreamByAddrWithoutMunmap() should not call underlying munmap()
+  // implementation.
+  EXPECT_TRUE(RemoveFileStreamsByAddrWithoutMunmap(m.addr2, kSize));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  EXPECT_EQ(1U, stream->munmap_count);
+
+  // And RemoveFileStreamByAddr() should call the munmap().
+  EXPECT_TRUE(RemoveFileStreamsByAddr(m.addr3, kSize));
+  EXPECT_EQ(0U, GetAddrMapSize());
+  EXPECT_EQ(2U, stream->munmap_count);
+  EXPECT_EQ(m.addr3, stream->last_munmap_addr);
+  EXPECT_EQ(kSize, stream->last_munmap_length);
+}
+
+TEST_F(MemoryRegionTest, TestSetAdviceByAddr) {
+  static const size_t kSize = 8;
+  struct {
+    char addr1[kSize];
+    char addr2[kSize];
+    char addr3[kSize];
+  } m ALIGN_(2);
+
+  scoped_refptr<StubFileStream> stream = new StubFileStream(false);
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr1, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(m.addr1, kSize));
+  EXPECT_EQ(2U, GetAddrMapSize());
+  EXPECT_TRUE(AddFileStreamByAddr(m.addr2, kSize, stream));
+  EXPECT_TRUE(HasMemoryRegion(m.addr1, kSize));
+  EXPECT_EQ(4U, GetAddrMapSize());
+
+  // It always pass on zero length.
+  EXPECT_TRUE(SetAdviceByAddr(NULL, 0, MADV_NORMAL));
+  EXPECT_TRUE(SetAdviceByAddr(m.addr3, 0, MADV_NORMAL));
+
+  // It passes on registered space.
+  EXPECT_TRUE(SetAdviceByAddr(m.addr1, 2, MADV_NORMAL));
+  EXPECT_TRUE(SetAdviceByAddr(m.addr1, kSize, MADV_NORMAL));
+  EXPECT_TRUE(SetAdviceByAddr(m.addr2, kSize, MADV_NORMAL));
+  EXPECT_TRUE(SetAdviceByAddr(m.addr1, kSize + 2, MADV_NORMAL));
+  EXPECT_TRUE(SetAdviceByAddr(m.addr1, kSize * 2, MADV_NORMAL));
+
+  // It fails on unmanaged space.
+  // TODO(crbug.com/362862): This is not Linux compatible. Once MemoryRegion
+  // can manage all regions, it should succeed even on unknown spaces.
+  EXPECT_FALSE(SetAdviceByAddr(m.addr3, kSize, MADV_NORMAL));
+  EXPECT_EQ(ENOSYS, errno);
+
+  // MADV_REMOVE is not supported.
+  EXPECT_FALSE(SetAdviceByAddr(m.addr1, kSize, MADV_REMOVE));
+  EXPECT_EQ(ENOSYS, errno);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/misc_wrap.cc b/src/posix_translation/misc_wrap.cc
new file mode 100644
index 0000000..2c16a6c
--- /dev/null
+++ b/src/posix_translation/misc_wrap.cc
@@ -0,0 +1,361 @@
+/* Copyright 2014 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.
+ *
+ * Simple wrapper for functions not related to file/socket such as
+ * madvise.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <map>
+
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "common/arc_strace.h"
+#include "common/backtrace.h"
+#include "common/danger.h"
+#include "common/export.h"
+#include "common/plugin_handle.h"
+#include "common/process_emulator.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+extern "C" {
+ARC_EXPORT void __wrap_abort();
+ARC_EXPORT void __wrap_exit(int status);
+ARC_EXPORT int __wrap_fork();
+ARC_EXPORT int __wrap_getpriority(int which, int who);
+ARC_EXPORT int __wrap_getrlimit(int resource, struct rlimit *rlim);
+ARC_EXPORT int __wrap_kill(pid_t pid, int sig);
+ARC_EXPORT int __wrap_madvise(void* addr, size_t length, int advice);
+ARC_EXPORT int __wrap_pthread_kill(pthread_t thread, int sig);
+ARC_EXPORT int __wrap_setpriority(int which, int who, int prio);
+ARC_EXPORT int __wrap_setrlimit(int resource, const struct rlimit *rlim);
+ARC_EXPORT int __wrap_sigaction(int signum, const struct sigaction *act,
+                     struct sigaction *oldact);
+ARC_EXPORT int __wrap_tgkill(int tgid, int tid, int sig);
+ARC_EXPORT int __wrap_tkill(int tid, int sig);
+ARC_EXPORT int __wrap_uname(struct utsname* buf);
+ARC_EXPORT int __wrap_vfork();
+ARC_EXPORT pid_t __wrap_wait(int *status);
+ARC_EXPORT pid_t __wrap_waitpid(pid_t pid, int *status, int options);
+ARC_EXPORT int __wrap_waitid(
+    idtype_t idtype, id_t id, siginfo_t *infop, int options);
+ARC_EXPORT pid_t __wrap_wait3(int *status, int options, struct rusage *rusage);
+ARC_EXPORT pid_t __wrap_wait4(
+    pid_t pid, int *status, int options, struct rusage *rusage);
+
+ARC_EXPORT pid_t __wrap_getpid();
+ARC_EXPORT uid_t __wrap_getuid();
+ARC_EXPORT int __wrap_pthread_create(
+    pthread_t* thread_out,
+    pthread_attr_t const* attr,
+    void* (*start_routine)(void*),  // NOLINT(readability/casting)
+    void* arg);
+}  // extern "C"
+
+namespace {
+
+// Highest possible priority, see frameworks/native/include/utils/ThreadDefs.h.
+const int ANDROID_PRIORITY_HIGHEST = -20;
+
+// Initial value is set to a value that is usually not used. This will
+// happen if atexit handler is called without __wrap_exit being
+// called. For example, when user returns from main().
+const int DEFAULT_EXIT_STATUS = 111;
+
+// Store status code in __wrap_exit(), then read it from a function
+// which is registered to atexit().
+int g_exit_status = DEFAULT_EXIT_STATUS;
+
+// get/setpriority is not currently supported. It is not yet clear how we
+// should deal with thread priorities in ARC. Remember and return
+// them for now.
+class PriorityMap {
+ public:
+  static PriorityMap* GetInstance() {
+    return Singleton<PriorityMap, LeakySingletonTraits<PriorityMap> >::get();
+  }
+
+  int GetPriority(int pid);
+  void SetPriority(int pid, int priority);
+
+ private:
+  friend struct DefaultSingletonTraits<PriorityMap>;
+
+  PriorityMap() {}
+  ~PriorityMap() {}
+
+  base::Lock mu_;
+  // This actually maps 'tid' to priority, but because get/setpriority
+  // specifies that we use process identifiers we name the map as pid-based.
+  std::map<int, int> pid_to_priority_;
+
+  DISALLOW_COPY_AND_ASSIGN(PriorityMap);
+};
+
+int PriorityMap::GetPriority(int pid) {
+  base::AutoLock lock(mu_);
+  return pid_to_priority_[pid];
+}
+
+void PriorityMap::SetPriority(int pid, int priority) {
+  base::AutoLock lock(mu_);
+  pid_to_priority_[pid] = priority;
+}
+
+}  // namespace
+
+namespace arc {
+
+ARC_EXPORT int GetExitStatus() {
+  return g_exit_status;
+}
+
+}  // namespace arc
+
+//
+// Function wrappers, sorted by function name.
+//
+
+/* Attempt to show the backtrace in abort(). */
+void __wrap_abort() {
+  arc::PluginHandle handle;
+  // Do not show a backtrace on the main thread because it depends on the
+  // virtual filesystem lock, which cannot be acquired on the main thread.
+  if (handle.GetPluginUtil() && !handle.GetPluginUtil()->IsMainThread())
+    arc::BacktraceInterface::Print();
+  abort();
+}
+
+// TODO(crbug.com/323815): __wrap_exit does not work against loader exit(),
+// and _exit().
+void __wrap_exit(int status) {
+  ARC_STRACE_ENTER("exit", "%d", status);
+  // We do not use mutex lock here since stored |g_exit_status| is read from
+  // the same thread inside exit() through atexit() functions chain.
+  g_exit_status = status;
+  exit(status);
+}
+
+/* fork/vfork is currently not supported in NaCl mode. It also causes several
+ * other issues in trusted mode (crbug.com/268645).
+ */
+int __wrap_fork() {
+  ARC_STRACE_ENTER("fork", "%s", "");
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_getpriority(int which, int who) {
+  ARC_STRACE_ENTER("getpriority", "%d, %d", which, who);
+  if (which == PRIO_PROCESS) {
+    int result = PriorityMap::GetInstance()->GetPriority(who);
+    ARC_STRACE_RETURN(result);
+  }
+  errno = ESRCH;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_getrlimit(int resource, struct rlimit *rlim) {
+  // TODO(crbug.com/241955): Stringify |resource| and |rlim|.
+  ARC_STRACE_ENTER("getrlimit", "%d, %p", resource, rlim);
+  int result = -1;
+  static const uint32_t kArcRLimInfinity = -1;
+  switch (resource) {
+    case RLIMIT_AS:
+    case RLIMIT_DATA:
+      rlim->rlim_cur = kArcRLimInfinity;
+      rlim->rlim_max = kArcRLimInfinity;
+      result = 0;
+      break;
+    case RLIMIT_CORE:
+    case RLIMIT_MEMLOCK:
+    case RLIMIT_MSGQUEUE:
+    case RLIMIT_RTPRIO:
+    case RLIMIT_RTTIME:
+      rlim->rlim_cur = 0;
+      rlim->rlim_max = 0;
+      result = 0;
+      break;
+    case RLIMIT_CPU:
+    case RLIMIT_FSIZE:
+    case RLIMIT_LOCKS:
+    case RLIMIT_NICE:
+    case RLIMIT_NPROC:
+    case RLIMIT_RSS:
+    case RLIMIT_SIGPENDING:
+    case RLIMIT_STACK:
+      rlim->rlim_cur = kArcRLimInfinity;
+      rlim->rlim_max = kArcRLimInfinity;
+      result = 0;
+      break;
+    case RLIMIT_NOFILE:
+      // The same as in posix_translation/fd_to_file_stream_map.h
+      rlim->rlim_cur = FD_SETSIZE;
+      rlim->rlim_max = FD_SETSIZE;
+      result = 0;
+      break;
+    default:
+      ALOGE("Unknown getrlimit request. resource=%d", resource);
+      errno = EINVAL;
+      result = -1;
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_kill(pid_t pid, int sig) {
+  // Although POSIX does not require it, Bionic's strsignal implementation
+  // uses a buffer returned by pthread_getspecific, and hence thread-safe
+  // even when |sig| is out-of-range.
+  ARC_STRACE_ENTER("kill", "%d, \"%s\"",
+                   static_cast<int>(pid), strsignal(sig));
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_pthread_kill(pthread_t thread, int sig) {
+  ARC_STRACE_ENTER("pthread_kill", "\"%s\"", strsignal(sig));
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_setpriority(int which, int who, int prio) {
+  ARC_STRACE_ENTER("setpriority", "%d, %d, %d", which, who, prio);
+  if (which == PRIO_PROCESS) {
+    if (prio < 0) {
+      // Warn when Android or apps attempt to use higher thread priorities.
+      DANGERF("Called for tid %d prio %d", who, prio);
+    }
+    if (who == -1) {
+      // For CtsOsTestCases's ProcessTest.testMiscMethods().
+      errno = ESRCH;
+      ARC_STRACE_RETURN(-1);
+    }
+    if (prio < ANDROID_PRIORITY_HIGHEST)
+      prio = ANDROID_PRIORITY_HIGHEST;  // CTS tests expect successful result.
+    PriorityMap::GetInstance()->SetPriority(who, prio);
+    ARC_STRACE_RETURN(0);
+  }
+  ALOGW("Only PRIO_PROCESS is supported in setpriority()");
+  errno = EPERM;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_setrlimit(int resource, const struct rlimit *rlim) {
+  // TODO(crbug.com/241955): Stringify |resource| and |rlim|.
+  ARC_STRACE_ENTER("setrlimit", "%d, %p", resource, rlim);
+  errno = EPERM;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_sigaction(int signum, const struct sigaction *act,
+                     struct sigaction *oldact) {
+  ARC_STRACE_ENTER("sigaction", "\"%s\", %p, %p",
+                   strsignal(signum), act, oldact);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_tgkill(int tgid, int tid, int sig) {
+  ARC_STRACE_ENTER("tgkill", "%d, %d, \"%s\"", tgid, tid, strsignal(sig));
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_tkill(int tid, int sig) {
+  ARC_STRACE_ENTER("tkill", "%d, \"%s\"", tid, strsignal(sig));
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_uname(struct utsname* buf) {
+  ARC_STRACE_ENTER("uname", "%p", buf);
+  // Dalvik VM calls this.
+  strcpy(buf->sysname, "nacl");  // NOLINT(runtime/printf)
+  strcpy(buf->nodename, "localhost");  // NOLINT(runtime/printf)
+  strcpy(buf->release, "31");  // NOLINT(runtime/printf)
+  strcpy(buf->version, "31");  // NOLINT(runtime/printf)
+  strcpy(buf->machine, "nacl");  // NOLINT(runtime/printf)
+#ifdef _GNU_SOURCE
+  strcpy(buf->domainname, "chrome");  // NOLINT(runtime/printf)
+#endif
+  ARC_STRACE_RETURN(0);
+}
+
+int __wrap_vfork() {
+  ARC_STRACE_ENTER("vfork", "%s", "");
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+pid_t __wrap_wait(int *status) {
+  ARC_STRACE_ENTER("wait", "%p", status);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+pid_t __wrap_waitpid(pid_t pid, int *status, int options) {
+  ARC_STRACE_ENTER("waitpid", "%d, %p, %d",
+                   static_cast<int>(pid), status, options);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+int __wrap_waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options) {
+  ARC_STRACE_ENTER("waitid", "%d, %d, %p, %d",
+                   static_cast<int>(idtype), static_cast<int>(id),
+                   infop, options);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+pid_t __wrap_wait3(int *status, int options, struct rusage *rusage) {
+  ARC_STRACE_ENTER("wait3", "%p, %d, %p", status, options, rusage);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+pid_t __wrap_wait4(pid_t pid, int *status, int options, struct rusage *rusage) {
+  ARC_STRACE_ENTER("wait4", "%d, %p, %d, %p",
+                   static_cast<int>(pid), status, options, rusage);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+pid_t __wrap_getpid() {
+  ARC_STRACE_ENTER("getpid", "%s", "");
+  const pid_t result = arc::ProcessEmulator::GetPid();
+  ARC_STRACE_RETURN(result);
+}
+
+uid_t __wrap_getuid() {
+  ARC_STRACE_ENTER("getuid", "%s", "");
+  const uid_t result = arc::ProcessEmulator::GetUid();
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_pthread_create(
+    pthread_t* thread_out,
+    const pthread_attr_t* attr,
+    void* (*start_routine)(void*),  // NOLINT(readability/casting)
+    void* arg) {
+  // TODO(crbug.com/241955): Stringify |attr|?
+  ARC_STRACE_ENTER("pthread_create", "%p, %p, %p, %p",
+                   thread_out, attr, start_routine, arg);
+
+  arc::ProcessEmulator::FilterPthreadCreate(&start_routine, &arg);
+
+  int result = pthread_create(thread_out, attr, start_routine, arg);
+
+  ARC_STRACE_RETURN(result);
+}
diff --git a/src/posix_translation/mount_point_manager.cc b/src/posix_translation/mount_point_manager.cc
new file mode 100644
index 0000000..9efc748
--- /dev/null
+++ b/src/posix_translation/mount_point_manager.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 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 "posix_translation/mount_point_manager.h"
+
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "common/process_emulator.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/path_util.h"
+
+namespace posix_translation {
+
+MountPointManager::MountPointManager() {
+}
+
+MountPointManager::~MountPointManager() {
+}
+
+void MountPointManager::Add(const std::string& path,
+                            FileSystemHandler* handler) {
+  ALOG_ASSERT(!path.empty());
+  ALOG_ASSERT(handler, "NULL FileSystemHandler is not allowed: %s",
+              path.c_str());
+  if (!mount_point_map_.insert(make_pair(
+          path, MountPoint(handler, arc::kRootUid))).second) {
+    LOG_ALWAYS_FATAL("%s: mount point already exists", path.c_str());
+  }
+  handler->OnMounted(path);
+  ARC_STRACE_REPORT("MountPointManager::Add: path=%s handler=%s",
+                      path.c_str(), handler->name().c_str());
+}
+
+void MountPointManager::Remove(const std::string& path) {
+  MountPointMap::iterator i = mount_point_map_.find(path);
+  if (i != mount_point_map_.end()) {
+    FileSystemHandler* handler = i->second.handler;
+    ALOG_ASSERT(handler);
+    ARC_STRACE_REPORT("MountPointManager::Remove: path=%s handler=%s",
+                        path.c_str(), handler->name().c_str());
+    mount_point_map_.erase(i);
+    handler->OnUnmounted(path);
+  } else {
+    ARC_STRACE_REPORT("MountPointManager::Remove: path=%s is NOT registered",
+                        path.c_str());
+  }
+}
+
+void MountPointManager::ChangeOwner(const std::string& path, uid_t owner_uid) {
+  ALOG_ASSERT(!path.empty());
+  MountPointMap::iterator found = mount_point_map_.find(path);
+  // If the mount point does not exist yet, create it. This is for e.g.
+  // /data/data/<app-id>. This mount point does not exist before chown
+  // is called.
+  if (found == mount_point_map_.end()) {
+    uid_t dummy_uid;
+    FileSystemHandler* handler = GetFileSystemHandler(path, &dummy_uid);
+    ALOG_ASSERT(handler, "Could not find a FileSystemHandler for %s",
+                path.c_str());
+    Add(path, handler);
+    found = mount_point_map_.find(path);
+    ALOG_ASSERT(found != mount_point_map_.end());
+  }
+  found->second.owner_uid = owner_uid;
+  ARC_STRACE_REPORT("MountPointManager::ChangeOwner: path=%s uid=%d",
+                      path.c_str(), owner_uid);
+}
+
+FileSystemHandler* MountPointManager::GetFileSystemHandler(
+    const std::string& path,
+    uid_t* owner_uid) const {
+  ALOG_ASSERT(owner_uid);
+  if (path.empty())
+    return NULL;
+
+  *owner_uid = arc::kRootUid;
+  // MountPointManager may have some mount points for non-directory
+  // files (e.g., /dev/null). Check it first.
+  MountPointMap::const_iterator found = mount_point_map_.find(path);
+  if (found != mount_point_map_.end()) {
+    *owner_uid = found->second.owner_uid;
+    return found->second.handler;
+  }
+
+  // We will find the deepest mount point for |path|. For example, for
+  // /system/lib/libdl.so, we should find /system/lib, not /system. To
+  // do this, we strip the basename one by one in the following loop.
+  std::string dir(path);
+  do {
+    util::EnsurePathEndsWithSlash(&dir);
+    // Check if |dir| is a mount point.
+    found = mount_point_map_.find(dir);
+    if (found != mount_point_map_.end()) {
+      *owner_uid = found->second.owner_uid;
+      return found->second.handler;
+    }
+
+    // Strip a basename.
+    util::GetDirNameInPlace(&dir);
+  } while (dir.length() > 1);
+  // TODO(satorux): Clean up the logic here. |dir| is either "/" or "." here.
+  // Normalizing the path before calling this function would make the logic
+  // cleaner. See also crbug.com/178515.
+  if (dir == "/") {
+    found = mount_point_map_.find(dir);
+    if (found != mount_point_map_.end()) {
+      *owner_uid = found->second.owner_uid;
+      return found->second.handler;
+    }
+  }
+
+  return NULL;
+}
+
+void MountPointManager::GetAllFileSystemHandlers(
+    std::vector<FileSystemHandler*>* out_handlers) {
+  for (MountPointMap::const_iterator it = mount_point_map_.begin();
+       it != mount_point_map_.end(); ++it) {
+    FileSystemHandler* handler = it->second.handler;
+    out_handlers->push_back(handler);
+  }
+}
+
+void MountPointManager::Clear() {
+  mount_point_map_.clear();
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/mount_point_manager.h b/src/posix_translation/mount_point_manager.h
new file mode 100644
index 0000000..8245360
--- /dev/null
+++ b/src/posix_translation/mount_point_manager.h
@@ -0,0 +1,86 @@
+// Copyright 2014 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 POSIX_TRANSLATION_MOUNT_POINT_MANAGER_H_
+#define POSIX_TRANSLATION_MOUNT_POINT_MANAGER_H_
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace posix_translation {
+
+class Dir;
+class DirectoryManager;
+class FileSystemHandler;
+
+// A class which decides which handler should be used for which path.
+// This class also manages static symbolic links.
+// TODO(crbug.com/324950): This really should be part of the VirtualFileSystem
+// interface.  As it is now, it's several unrelated things: mount points
+// manager, ephemeral symlink implementation, and ephemeral file metadata
+// (uids) manager.  I could see the metadata and symlink pieces being
+// part of DirectoryManager.
+class MountPointManager {
+ public:
+  MountPointManager();
+  ~MountPointManager();
+
+  // Registers |handler| to |path|. If |path| ends with '/', this is
+  // considered as a directory and files under |path| will be handled
+  // by |handler|. This function does not take the ownership of
+  // |handler|. The UID of the mount point added is kRootUid.
+  void Add(const std::string& path, FileSystemHandler* handler);
+
+  // Unregisters the handler associated with |path| if exists. Do nothing if no
+  // handler is associated with |path|.
+  void Remove(const std::string& path);
+
+  // Changes the owner of |path| to |owner_uid|. If |path| is not
+  // registered yet, this function will add a mount point using the
+  // FileSystemHandler for |path|. When |path| is a directory, it must
+  // end with '/'.
+  void ChangeOwner(const std::string& path, uid_t owner_uid);
+
+  // Gets the path handler using mount points registered by
+  // AddMountPoint. Returns NULL if |path| is a relative path or no
+  // mount point is found. The UID of the owner will be returned using
+  // |owner_uid|. |owner_uid| must not be a NULL pointer.
+  FileSystemHandler* GetFileSystemHandler(const std::string& path,
+                                          uid_t* owner_uid) const;
+
+  // Returns all file system handlers that have been added.
+  void GetAllFileSystemHandlers(std::vector<FileSystemHandler*>* out_handlers);
+
+  // Returns a Dir object created based on mount points in |name|
+  // registered by AddMountPoint().  Does not affect errno.
+  Dir* GetVirtualEntriesInDirectory(const std::string& name) const;
+
+  // Removes all mount points. For testing only.
+  void Clear();
+
+ private:
+  struct MountPoint {
+    MountPoint(FileSystemHandler* h, uid_t u) : handler(h), owner_uid(u) {}
+
+    FileSystemHandler* handler;
+    uid_t owner_uid;
+  };
+  typedef base::hash_map<std::string, MountPoint> MountPointMap;  // NOLINT
+
+  // A map from mount point paths to metadata of them.
+  MountPointMap mount_point_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(MountPointManager);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_MOUNT_POINT_MANAGER_H_
diff --git a/src/posix_translation/mount_point_manager_test.cc b/src/posix_translation/mount_point_manager_test.cc
new file mode 100644
index 0000000..a392fdc
--- /dev/null
+++ b/src/posix_translation/mount_point_manager_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 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 "base/compiler_specific.h"
+#include "gtest/gtest.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/mount_point_manager.h"
+
+namespace posix_translation {
+
+namespace {
+
+class StubFileSystemHandler : public FileSystemHandler {
+ public:
+  StubFileSystemHandler() : FileSystemHandler("StubFileSystemHandler") {}
+
+  virtual scoped_refptr<FileStream> open(int, const std::string&, int,
+                                         mode_t) OVERRIDE {
+    return NULL;
+  }
+  virtual Dir* OnDirectoryContentsNeeded(const std::string&) OVERRIDE {
+    return NULL;
+  }
+  virtual int stat(const std::string&, struct stat*) OVERRIDE { return -1; }
+  virtual int statfs(const std::string&, struct statfs*) OVERRIDE { return -1; }
+};
+
+}  // namespace
+
+TEST(MountPointManagerTest, MountUnmountTest) {
+  MountPointManager mount_points;
+  StubFileSystemHandler handler;
+  uid_t uid;
+
+  mount_points.Add("/path/to/file", &handler);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/file", &uid));
+  mount_points.Remove("/path/to/file");
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/file", &uid));
+
+  mount_points.Add("/path/to/dir/", &handler);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/dir/", &uid));
+  mount_points.Remove("/path/to/dir/");
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/dir/", &uid));
+}
+
+TEST(MountPointManagerTest, TestGetFileSystemHandler_mount_file) {
+  MountPointManager mount_points;
+  StubFileSystemHandler handler;
+  mount_points.Add("/path/to/file", &handler);
+  mount_points.ChangeOwner("/path/to/file", 1000);
+  uid_t uid;
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/file", &uid));
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/file_", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/file2", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/file/", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/file/foo", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/to/fil", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("path/to/fil", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("path/to/file", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("file", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("file1", &uid));
+  EXPECT_EQ(0U, uid);
+}
+
+TEST(MountPointManagerTest, TestGetFileSystemHandler_mount_dir) {
+  MountPointManager mount_points;
+  StubFileSystemHandler handler;
+  mount_points.Add("/path/to/dir/", &handler);
+  mount_points.ChangeOwner("/path/to/dir/", 1000);
+  uid_t uid;
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/dir", &uid));
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/dir/", &uid));
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/dir/1",
+                                                        &uid));
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/path/to/dir/1/2",
+                                                        &uid));
+  EXPECT_EQ(1000U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path/", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/path", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("/", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler(".", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("path/to/dir", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("path/to/dir1", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("dir", &uid));
+  EXPECT_EQ(0U, uid);
+  EXPECT_EQ(NULL, mount_points.GetFileSystemHandler("dir1", &uid));
+  EXPECT_EQ(0U, uid);
+
+  mount_points.Add("/", &handler);
+  mount_points.ChangeOwner("/", 2000);
+  EXPECT_EQ(&handler, mount_points.GetFileSystemHandler("/", &uid));
+  EXPECT_EQ(2000U, uid);
+}
+
+TEST(MountPointManagerTest, TestGetFileSystemHandler_empty) {
+  MountPointManager mount_points;
+  StubFileSystemHandler handler;
+  mount_points.Add("/path/to/dir/", &handler);
+  mount_points.ChangeOwner("/path/to/dir/", 1000);
+  uid_t uid;
+  EXPECT_TRUE(NULL == mount_points.GetFileSystemHandler("", &uid));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/nacl_manifest_file.cc b/src/posix_translation/nacl_manifest_file.cc
new file mode 100644
index 0000000..d66fe1d
--- /dev/null
+++ b/src/posix_translation/nacl_manifest_file.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Handles files listed in the NaCl manifest file.
+
+#include "posix_translation/nacl_manifest_file.h"
+
+#include <string.h>
+
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "common/arc_strace.h"
+#include "common/file_util.h"
+#include "common/trace_event.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/file_stream.h"  // DirectoryFileStream
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+NaClManifestFileHandler::NaClManifestFileHandler(const NaClManifestEntry* files,
+                                                 size_t num_files)
+    : FileSystemHandler("NaClManifestFileHandler") {
+  if (!nacl_interface_query(NACL_IRT_RESOURCE_OPEN_v0_1,
+                            &resource_open_, sizeof(resource_open_))) {
+    ALOG_ASSERT(false, "Query for NACL_IRT_RESOURCE_OPEN_v0_1 has failed");
+  }
+  InitializeDirectoryManager(files, num_files);
+}
+
+NaClManifestFileHandler::~NaClManifestFileHandler() {
+}
+
+void NaClManifestFileHandler::AddToFdCacheLocked(
+    const std::string& pathname, int fd) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  ALOG_ASSERT(!pathname.empty());
+  ALOG_ASSERT(fd >= 0);
+  fd_cache_.insert(std::make_pair(pathname, fd));
+}
+
+bool NaClManifestFileHandler::ExistsLocked(const std::string& pathname) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (!directory_manager_.StatFile(pathname) &&
+      !directory_manager_.StatDirectory(pathname)) {
+    ARC_STRACE_REPORT("%s is not found", pathname.c_str());
+    return false;
+  }
+  return true;
+}
+
+int NaClManifestFileHandler::OpenLocked(const std::string& pathname) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "NaClManifestFileHandler::OpenLocked",
+               "pathname", pathname);
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  ALOG_ASSERT(ExistsLocked(pathname));
+
+  int fd = -1;
+  std::string real_path(pathname);
+  const char* key = arc::GetBaseName(pathname.c_str());
+  if (key[0] == '\0')
+    return -1;
+
+  // open_resource() is kind of a special IRT call which asks the main thread
+  // to talk to the renderer process with SRPC and the thread which called
+  // open_resource() itself is suspended waiting for the operation on the main
+  // thread to be done. For that reason, the |mutex_| should be unlocked before
+  // calling open_resource() to avoid dead lock. See crbug.com/274233 and
+  // native_client/src/untrusted/irt/irt_manifest.c for more details.
+  // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+  ARC_STRACE_REPORT("Slow path - Calling open_resource(\"%s\")", key);
+  base::AutoUnlock unlock(sys->mutex());
+  if (resource_open_.open_resource(key, &fd))
+    return -1;
+  return fd;
+}
+
+void NaClManifestFileHandler::InitializeDirectoryManager(
+    const NaClManifestEntry* files, size_t num_files) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  for (size_t i = 0; i < num_files; ++i) {
+    struct stat st = {};
+    st.st_mode = files[i].mode;
+    st.st_size = files[i].size;
+    st.st_mtime = files[i].mtime;
+    {
+      base::AutoLock lock(sys->mutex());
+      // Note: This fails if |files[i].name| is not a normalized path name.
+      st.st_ino = sys->GetInodeLocked(files[i].name);
+    }
+    ALOG_ASSERT(st.st_mode & S_IFREG);
+    ALOG_ASSERT(st.st_size > 0);
+    ALOG_ASSERT(st.st_mtime > 0);
+    ALOG_ASSERT(st.st_ino > 0);
+    const bool insert_result =
+        stat_cache_.insert(std::make_pair(files[i].name, st)).second;
+    ALOG_ASSERT(insert_result);
+
+    std::string name = files[i].name;
+    ARC_STRACE_REPORT("Found %s", name.c_str());
+    directory_manager_.AddFile(name);
+  }
+}
+
+scoped_refptr<FileStream> NaClManifestFileHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  if (oflag & (O_WRONLY | O_RDWR)) {
+    errno = EACCES;
+    return NULL;
+  }
+
+  // Check if |pathname| is a directory.
+  if (directory_manager_.StatDirectory(pathname))
+    return new DirectoryFileStream("nmf", pathname, this);
+
+  if (oflag & O_DIRECTORY) {
+    errno = ENOTDIR;
+    return NULL;
+  }
+
+  struct stat st;
+  if (this->stat(pathname, &st))
+    return NULL;  // the file does not exist.
+
+  // First, search for the FD cache.
+  int native_handle;
+  std::multimap<std::string, int>::iterator it = fd_cache_.find(pathname);
+  if (it != fd_cache_.end()) {
+    native_handle = it->second;
+    fd_cache_.erase(it);
+    ARC_STRACE_REPORT("Reusing a cached NaCl descriptor %d for %s",
+                      native_handle, pathname.c_str());
+  } else {
+    // Then, try to open the file with open_resource.
+    native_handle = OpenLocked(pathname);
+    if (native_handle < 0) {
+      errno = ENOENT;
+      return NULL;
+    }
+  }
+  return new NaClManifestFile(native_handle, pathname, oflag, st, this);
+}
+
+Dir* NaClManifestFileHandler::OnDirectoryContentsNeeded(
+    const std::string& name) {
+  return directory_manager_.OpenDirectory(name);
+}
+
+int NaClManifestFileHandler::stat(const std::string& pathname,
+                                  struct stat* out) {
+  if (directory_manager_.StatDirectory(pathname)) {
+    DirectoryFileStream::FillStatData(pathname, out);
+    return 0;
+  }
+
+  base::hash_map<std::string, struct stat>::iterator it =  // NOLINT
+    stat_cache_.find(pathname);
+  if (it != stat_cache_.end()) {
+    *out = it->second;
+    return 0;
+  }
+  errno = ENOENT;
+  return -1;
+}
+
+int NaClManifestFileHandler::statfs(const std::string& pathname,
+                                    struct statfs* out) {
+  // TODO(crbug.com/269075): Implement this.
+  if (ExistsLocked(pathname))
+    return DoStatFsForSystem(out);
+  errno = ENOENT;
+  return -1;
+}
+
+int NaClManifestFileHandler::mkdir(const std::string& pathname, mode_t mode) {
+  if (ExistsLocked(pathname)) {
+    errno = EEXIST;
+    return -1;
+  }
+  errno = EACCES;
+  return -1;
+}
+
+int NaClManifestFileHandler::rename(const std::string& oldpath,
+                                    const std::string& newpath) {
+  if (!ExistsLocked(oldpath) || newpath.empty()) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (oldpath == newpath)
+    return 0;
+  errno = EACCES;
+  return -1;
+}
+
+int NaClManifestFileHandler::truncate(const std::string& pathname,
+                                      off64_t length) {
+  if (!ExistsLocked(pathname))
+    errno = ENOENT;
+  else
+    errno = EACCES;
+  return -1;
+}
+
+int NaClManifestFileHandler::unlink(const std::string& pathname) {
+  if (!ExistsLocked(pathname))
+    errno = ENOENT;
+  else
+    errno = EACCES;
+  return -1;
+}
+
+int NaClManifestFileHandler::utimes(const std::string& pathname,
+                                    const struct timeval times[2]) {
+  if (!ExistsLocked(pathname))
+    errno = ENOENT;
+  else
+    errno = EACCES;
+  return -1;
+}
+
+NaClManifestFile::NaClManifestFile(int native_handle,
+                                   const std::string& pathname, int oflag,
+                                   const struct stat& st,
+                                   NaClManifestFileHandler* handler)
+    : PassthroughStream(native_handle, pathname, oflag,
+                        false),  // The |native_handle| will NEVER be closed on
+                                 // destruction.
+      st_(st),
+      handler_(handler) {
+  ALOG_ASSERT(native_handle >= 0);
+  ALOG_ASSERT(!pathname.empty());
+  ALOG_ASSERT(st_.st_ino == inode());
+  ALOG_ASSERT(handler);
+}
+
+NaClManifestFile::~NaClManifestFile() {
+  this->lseek(0, SEEK_SET);
+  ARC_STRACE_REPORT("Adding NaCl descriptor %d for %s to the cache",
+                    native_fd(), pathname().c_str());
+  handler_->AddToFdCacheLocked(pathname(), native_fd());
+}
+
+int NaClManifestFile::fstat(struct stat* out) {
+  *out = st_;
+  return 0;
+}
+
+ssize_t NaClManifestFile::write(const void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+const char* NaClManifestFile::GetStreamType() const {
+  return "nmf";  // should be <=8 characters.
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/nacl_manifest_file.h b/src/posix_translation/nacl_manifest_file.h
new file mode 100644
index 0000000..47f1e41
--- /dev/null
+++ b/src/posix_translation/nacl_manifest_file.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_NACL_MANIFEST_FILE_H_
+#define POSIX_TRANSLATION_NACL_MANIFEST_FILE_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "common/export.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/passthrough.h"
+
+// For nacl_irt_resource_open.
+#include "irt.h"  // NOLINT(build/include)
+
+namespace posix_translation {
+
+struct NaClManifestEntry {
+  const char* name;
+  mode_t mode;
+  off_t size;
+  time_t mtime;
+};
+
+// Simulates a file system based on file keys from NaCl manifest.
+class ARC_EXPORT NaClManifestFileHandler : public FileSystemHandler {
+ public:
+  NaClManifestFileHandler(const NaClManifestEntry* files, size_t num_files);
+  virtual ~NaClManifestFileHandler();
+
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual int truncate(const std::string& pathname, off64_t length) OVERRIDE;
+  virtual int unlink(const std::string& pathname) OVERRIDE;
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]) OVERRIDE;
+
+  void AddToFdCacheLocked(const std::string& pathname, int fd);
+
+ private:
+  // The function unlocks VirtualFileSystem::mutex_.
+  bool ExistsLocked(const std::string& pathname);
+  int OpenLocked(const std::string& pathname);
+
+  // Initializes |directory_manager_|.
+  void InitializeDirectoryManager(const NaClManifestEntry* files,
+                                  size_t num_files);
+
+  struct nacl_irt_resource_open resource_open_;
+
+  // A object which knows a list of all files
+  // (e.g. "/system/lib/egl/libEGL_emulation.so") in the nmf file.
+  DirectoryManager directory_manager_;
+
+  // A cahce for getting a stat() result without NaCl IPC.
+  base::hash_map<std::string, struct stat> stat_cache_;  // NOLINT
+
+  // A cache for getting a file descriptor for the file without calling
+  // into the slow open_resource IRT.
+  std::multimap<std::string, int> fd_cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(NaClManifestFileHandler);
+};
+
+class NaClManifestFile : public PassthroughStream {
+ public:
+  NaClManifestFile(int fd, const std::string& pathname, int oflag,
+                   const struct stat& st, NaClManifestFileHandler* handler);
+
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~NaClManifestFile();
+
+ private:
+  const struct stat st_;
+  NaClManifestFileHandler* handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(NaClManifestFile);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_NACL_MANIFEST_FILE_H_
diff --git a/src/posix_translation/passthrough.cc b/src/posix_translation/passthrough.cc
new file mode 100644
index 0000000..6839064
--- /dev/null
+++ b/src/posix_translation/passthrough.cc
@@ -0,0 +1,198 @@
+// Copyright 2014 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 "posix_translation/passthrough.h"
+
+#include <string>
+
+#include "common/alog.h"
+
+namespace posix_translation {
+
+namespace {
+
+const ino_t kNativeInodeNumberMask = 0x80000000;
+const blksize_t kBlockSize = 4096;
+const int kInvalidFd = -1;
+
+}  // namespace
+
+PassthroughHandler::PassthroughHandler()
+    : FileSystemHandler("PassthroughHandler") {
+}
+
+PassthroughHandler::~PassthroughHandler() {
+}
+
+scoped_refptr<FileStream> PassthroughHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  int native_fd;
+  if (!pathname.empty()) {
+    native_fd = ::open(pathname.c_str(), oflag, cmode);
+  } else {
+    ALOG_ASSERT(fd >= 0);
+    native_fd = fd;
+  }
+  return (native_fd < 0) ? NULL :
+      new PassthroughStream(native_fd, pathname, oflag, !pathname.empty());
+}
+
+int PassthroughHandler::stat(const std::string& pathname, struct stat* out) {
+  scoped_refptr<FileStream> stream = this->open(-1, pathname, O_RDONLY, 0);
+  if (!stream) {
+    errno = ENOENT;
+    return -1;
+  }
+  return stream->fstat(out);
+}
+
+int PassthroughHandler::statfs(const std::string& pathname,
+                               struct statfs* out) {
+  errno = ENOSYS;
+  return -1;
+}
+
+Dir* PassthroughHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  return NULL;
+}
+
+// Note: You can call libc functions with |native_fd| if the function is
+// listed in libc_functions.h. Otherwise you can not. For example, it is
+// okay to call ::close() with |native_fd| since ::close() is overridden in
+// libc_functions_prod.cc and it immediately calls into (the original) IRT.
+// However, calling ::pread() with the fd is NOT okay since it is not in
+// libc_functions.h and Bionic version of ::pread() may call into a hooked
+// IRT which may in turn call back posix_translation. The same restriction
+// applies to other streams like PepperFile, NaClManifestFile, and MemoryFile
+// that handle native descriptors.
+
+PassthroughStream::PassthroughStream(int native_fd,
+                                     const std::string& pathname,
+                                     int oflag,
+                                     bool close_on_destruction)
+    : FileStream(oflag, pathname),
+      native_fd_(native_fd),
+      close_on_destruction_(close_on_destruction) {
+  ALOG_ASSERT(native_fd_ >= 0);
+}
+
+PassthroughStream::PassthroughStream()
+    : FileStream(0, std::string()),
+      native_fd_(kInvalidFd),
+      close_on_destruction_(false) {
+}
+
+PassthroughStream::~PassthroughStream() {
+  if (close_on_destruction_)
+    ::close(native_fd_);
+}
+
+int PassthroughStream::fstat(struct stat* out) {
+  ALOG_ASSERT(native_fd_ >= 0);
+  const int result = ::fstat(native_fd_, out);
+  if (!result) {
+    // Add a large number so that st_ino does not conflict with the one
+    // generated in our VFS.
+    out->st_ino |= kNativeInodeNumberMask;
+    // Overwrite the real dev/rdev numbers with zero. See PepperFile::fstat.
+    out->st_dev = out->st_rdev = 0;
+    // Overwrite atime/ctime too.
+    out->st_atime = out->st_ctime = 0;
+    out->st_blksize = kBlockSize;
+  }
+  return result;
+}
+
+off64_t PassthroughStream::lseek(off64_t offset, int whence) {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return ::lseek64(native_fd_, offset, whence);
+}
+
+// Note: [addr, addr+length) should be valid even if a part of original mmaped
+// region is released partially by munmap(). MemoryRegion manages the memory
+// layout, and calls each madvise implementation so that [addr, addr+length)
+// is always valid for each FileStream instance.
+int PassthroughStream::madvise(void* addr, size_t length, int advice) {
+  if (advice != MADV_DONTNEED)
+    return FileStream::madvise(addr, length, advice);
+
+  if (native_fd_ != kInvalidFd) {
+    ALOGW("madvise with MADV_DONTNEED for native fd backed stream is not "
+          "supported.");
+    errno = EBADF;
+    return -1;
+  }
+
+  // TODO(crbug.com/427417): Since MemoryRegion handles memory layout
+  // information by FileStream unit basis, we do not have page by page prot
+  // information that can be updated by subsequent mmap and mprotect.
+  // Use the relaxed protection mode (R/W) here.
+  void* result = ::mmap(addr, length, PROT_READ | PROT_WRITE,
+                        MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, native_fd_, 0);
+  if (result == addr)
+    return 0;
+  ALOGE("An internal mmap call for PassthroughStream::madvise returns an "
+        "unexpected address %p for expected address %p", result, addr);
+  // Return 1 for an unrecoverable error to go LOG_ALWAYS_FATAL.
+  return 1;
+}
+
+void* PassthroughStream::mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) {
+  if ((flags & MAP_ANONYMOUS) && (flags & MAP_SHARED))
+    ALOGW("mmap with MAP_ANONYMOUS | MAP_SHARED is not fully supported");
+  return ::mmap(addr, length, prot, flags, native_fd_, offset);
+}
+
+int PassthroughStream::munmap(void* addr, size_t length) {
+  return ::munmap(addr, length);
+}
+
+ssize_t PassthroughStream::read(void* buf, size_t count) {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return ::read(native_fd_, buf, count);
+}
+
+ssize_t PassthroughStream::write(const void* buf, size_t count) {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return ::write(native_fd_, buf, count);
+}
+
+bool PassthroughStream::IsSelectReadReady() const {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return false;
+}
+bool PassthroughStream::IsSelectWriteReady() const {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return false;
+}
+bool PassthroughStream::IsSelectExceptionReady() const {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return false;
+}
+
+int16_t PassthroughStream::GetPollEvents() const {
+  ALOG_ASSERT(native_fd_ >= 0);
+  return 0;
+}
+
+size_t PassthroughStream::GetSize() const {
+  // MemoryRegion calls GetSize() even for an instance of an
+  // anonymous memory region in order to show a memory mapping when
+  // --logging=posix-translation-debug is enabled. Returning 0 is enough.
+  struct stat st;
+  if (pathname().empty() || const_cast<PassthroughStream*>(this)->fstat(&st))
+    return 0;  // unknown size
+  return st.st_size;
+}
+
+bool PassthroughStream::IsAllowedOnMainThread() const {
+  return true;
+}
+
+const char* PassthroughStream::GetStreamType() const {
+  return "passthru";
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/passthrough.h b/src/posix_translation/passthrough.h
new file mode 100644
index 0000000..6ee0f29
--- /dev/null
+++ b/src/posix_translation/passthrough.h
@@ -0,0 +1,82 @@
+// Copyright 2014 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 POSIX_TRANSLATION_PASSTHROUGH_H_
+#define POSIX_TRANSLATION_PASSTHROUGH_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/export.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+// A handler which implements all FileSystemHandler interfaces with libc
+// functions.
+class ARC_EXPORT PassthroughHandler : public FileSystemHandler {
+ public:
+  PassthroughHandler();
+  virtual ~PassthroughHandler();
+
+  // FileSystemHandler overrides:
+  // When |pathname| is not empty, open() tries to open the file with IRT
+  // and creates a stream with the native_fd returned from the IRT. The opened
+  // native_fd will be closed on destruction. When it is empty, open() just
+  // passes the |fd| as-is to the stream, which is useful for creating a stream
+  // for pre-existing FDs like STDERR_FILENO. The passed |fd| will not be closed
+  // on destruction.
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PassthroughHandler);
+};
+
+// A stream which implements all FileStream interfaces with libc calls. This
+// is useful for handling STDERR_FILENO in posix_translation/, for example.
+class PassthroughStream : public FileStream {
+ public:
+  PassthroughStream(int native_fd, const std::string& pathname,
+                    int oflag, bool close_on_destruction);
+  PassthroughStream();  // for anonymous mmap
+
+  // FileStream overrides:
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual int madvise(void* addr, size_t length, int advice) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual bool IsSelectReadReady() const OVERRIDE;
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+  virtual bool IsSelectExceptionReady() const OVERRIDE;
+  virtual int16_t GetPollEvents() const OVERRIDE;
+
+  virtual bool IsAllowedOnMainThread() const OVERRIDE;
+  virtual const char* GetStreamType() const OVERRIDE;
+  virtual size_t GetSize() const OVERRIDE;
+
+ protected:
+  virtual ~PassthroughStream();
+  int native_fd() const { return native_fd_; }
+
+ private:
+  const int native_fd_;
+  const bool close_on_destruction_;
+
+  DISALLOW_COPY_AND_ASSIGN(PassthroughStream);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_PASSTHROUGH_H_
diff --git a/src/posix_translation/passthrough_test.cc b/src/posix_translation/passthrough_test.cc
new file mode 100644
index 0000000..1fded13
--- /dev/null
+++ b/src/posix_translation/passthrough_test.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 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 <elf.h>  // For ELFMAG
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/memory/ref_counted.h"
+#include "gtest/gtest.h"
+#include "posix_translation/passthrough.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+namespace {
+
+int DoFcntl(scoped_refptr<FileStream> stream, int cmd, ...) {
+  va_list ap;
+  va_start(ap, cmd);
+  const int result = stream->fcntl(cmd, ap);
+  va_end(ap);
+  return result;
+}
+
+class PassthroughTest : public FileSystemTestCommon {
+  // Use FileSystemTestCommon to set up VirtualFileSystem instance for
+  // TestHandlerOpenWithPath. Initializing FileStream with a non-empty
+  // pathname requires VFS.
+};
+
+}  // namespace
+
+TEST_F(PassthroughTest, TestHandlerOpenWithPath) {
+  PassthroughHandler handler;
+  // Read a file which always exists on Linux. Note that user-mode
+  // qemu does not allow guest to read some device files under /dev.
+  static const char kElfFileName[] = "/proc/self/exe";
+  scoped_refptr<FileStream> stream =
+      handler.open(123, kElfFileName, O_RDONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+  char magic[] = "????";
+  EXPECT_EQ(4, stream->read(magic, 4));
+  EXPECT_STREQ(ELFMAG, magic);
+  errno = 0;
+  EXPECT_EQ(-1, stream->write(magic, 4));
+  EXPECT_EQ(EBADF, errno);  // not opened for writing.
+}
+
+TEST_F(PassthroughTest, TestHandlerOpenWithEmptyPath) {
+  PassthroughHandler handler;
+  int fd = dup(STDOUT_FILENO);
+  scoped_refptr<FileStream> stream =
+      handler.open(fd, "", O_WRONLY, 0);
+  ASSERT_TRUE(stream != NULL);
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+}
+
+TEST_F(PassthroughTest, TestConstructAndCloseOnDestruct) {
+  int fd = dup(STDIN_FILENO);
+  ASSERT_NE(-1, fd);
+  {
+    scoped_refptr<FileStream> stream = new PassthroughStream(fd, "", O_WRONLY,
+                                                             true);
+  }  // |fd| should be automatically closed here.
+  errno = 0;
+  EXPECT_EQ(-1, close(fd));  // should fail.
+  EXPECT_EQ(EBADF, errno);
+}
+
+TEST_F(PassthroughTest, TestConstructAndNotClosedOnDestruct) {
+  int fd = dup(STDIN_FILENO);
+  ASSERT_NE(-1, fd);
+  {
+    scoped_refptr<FileStream> stream = new PassthroughStream(fd, "", O_WRONLY,
+                                                             false);
+    // |fd| should NOT be automatically closed here since passing
+    // close_on_destruction = false.
+  }
+  EXPECT_EQ(0, close(fd));
+}
+
+TEST_F(PassthroughTest, TestConstructAndDestructForAnonymousMmap) {
+  scoped_refptr<FileStream> stream = new PassthroughStream();
+  size_t length = sysconf(_SC_PAGESIZE);
+  void* new_addr = stream->mmap(NULL, length, PROT_READ | PROT_WRITE,
+                                MAP_PRIVATE | MAP_ANONYMOUS, 0);
+  EXPECT_NE(MAP_FAILED, new_addr);
+  EXPECT_EQ(0, stream->munmap(new_addr, length));
+}
+
+TEST_F(PassthroughTest, TestFcntl) {
+  scoped_refptr<FileStream> stream =
+      new PassthroughStream(dup(STDERR_FILENO), "", O_WRONLY, true);
+  EXPECT_EQ(O_WRONLY, DoFcntl(stream, F_GETFL));
+  const long new_flag = O_WRONLY | O_NONBLOCK;  // NOLINT(runtime/int)
+  EXPECT_EQ(0, DoFcntl(stream, F_SETFL, new_flag));
+  EXPECT_EQ(new_flag, DoFcntl(stream, F_GETFL));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/path_util.cc b/src/posix_translation/path_util.cc
new file mode 100644
index 0000000..9ab6aab
--- /dev/null
+++ b/src/posix_translation/path_util.cc
@@ -0,0 +1,102 @@
+// Copyright 2014 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 "posix_translation/path_util.h"
+
+#include <vector>
+
+#include "base/strings/string_split.h"
+#include "common/alog.h"
+
+namespace posix_translation {
+namespace util {
+
+std::string GetDirName(const base::StringPiece& path) {
+  std::string dirname(path.data(), path.size());
+  GetDirNameInPlace(&dirname);
+  return dirname;
+}
+
+void GetDirNameInPlace(std::string* in_out_path) {
+  ALOG_ASSERT(in_out_path);
+
+  const size_t length = in_out_path->length();
+  size_t search_start = std::string::npos;
+  if ((length >= 2) && EndsWithSlash(*in_out_path))
+    search_start = length - 2;
+
+  const size_t pos = in_out_path->rfind('/', search_start);
+  if (!pos)
+    *in_out_path = "/";
+  else if (pos == std::string::npos)
+    *in_out_path = kCurrentDirectory;
+  else
+    in_out_path->erase(pos);
+}
+
+std::string JoinPath(const std::string& dirname,
+                     const std::string& basename) {
+  if (EndsWithSlash(dirname))
+    return dirname + basename;
+  else
+    return dirname + "/" + basename;
+}
+
+void EnsurePathEndsWithSlash(std::string* in_out_path) {
+  ALOG_ASSERT(in_out_path);
+  if (!EndsWithSlash(*in_out_path))
+    in_out_path->append("/");
+}
+
+void RemoveSingleDotsAndRedundantSlashes(std::string* in_out_path) {
+  ALOG_ASSERT(in_out_path);
+
+  if (in_out_path->find('.') == std::string::npos &&
+      in_out_path->find("//") == std::string::npos) {
+    // Fast path.
+    if (util::EndsWithSlash(*in_out_path) && in_out_path->length() > 2U)
+      in_out_path->erase(in_out_path->length() - 1);
+    // Check the post condition of the function.
+    ALOG_ASSERT(*in_out_path == "/" || !util::EndsWithSlash(*in_out_path));
+    return;
+  }
+  ALOG_ASSERT(!in_out_path->empty());
+
+  const bool is_absolute = ((*in_out_path)[0] == '/');
+  std::vector<std::string> directories;
+  base::SplitString(*in_out_path, '/', &directories);
+  in_out_path->clear();
+  if (is_absolute)
+    in_out_path->assign("/");
+  for (size_t i = 0; i < directories.size(); ++i) {
+    const std::string& directory = directories[i];
+    if (directory == "." || directory.empty())
+      continue;  // Skip empty (i.e. //) or .
+    in_out_path->append(directory + "/");
+  }
+
+  // When path consists of only './', we will end up with empty
+  // string. Make it be ".".
+  if (in_out_path->empty()) {
+    in_out_path->assign(".");
+    return;
+  }
+
+  // Remove the trailing "/".
+  ALOG_ASSERT(util::EndsWithSlash(*in_out_path));
+  if (in_out_path->length() > 2U)
+    in_out_path->erase(in_out_path->length() - 1);
+
+  // Check the post condition of the function.
+  ALOG_ASSERT(*in_out_path == "/" || !util::EndsWithSlash(*in_out_path));
+}
+
+void RemoveTrailingSlashes(std::string* in_out_path) {
+  while (in_out_path->length() > 1U && EndsWithSlash(*in_out_path))
+    in_out_path->erase(in_out_path->length() - 1);
+  ALOG_ASSERT(*in_out_path == "/" || !EndsWithSlash(*in_out_path));
+}
+
+}  // namespace util
+}  // namespace posix_translation
diff --git a/src/posix_translation/path_util.h b/src/posix_translation/path_util.h
new file mode 100644
index 0000000..cdaad3a
--- /dev/null
+++ b/src/posix_translation/path_util.h
@@ -0,0 +1,62 @@
+// Copyright 2014 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 POSIX_TRANSLATION_PATH_UTIL_H_
+#define POSIX_TRANSLATION_PATH_UTIL_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace posix_translation {
+namespace util {
+
+// A special path component meaning "this directory."
+const char kCurrentDirectory[] = ".";
+
+// Returns a string corresponding to the directory containing the given
+// path. If the string only contains one component, returns a string
+// identifying kCurrentDirectory. If the string already refers to the root
+// directory, returns a string identifying the root directory.  If the path
+// ends with a slash, the slash is handled as if it does not exist
+// (i.e. GetDirName("/foo/bar") == GetDirName("/foo/bar/") == "/foo").
+std::string GetDirName(const base::StringPiece& path);
+
+// Similar to GetDirName() but this function modifies the input parameter
+// in-place.
+void GetDirNameInPlace(std::string* in_out_path);
+
+// Joins |dirname| and |basename|. Note that this function takes std::string
+// rather than StringPiece as std::strings are needed internally (otherwise,
+// need to copy strings).
+std::string JoinPath(const std::string& dirname,
+                     const std::string& basename);
+
+// Appends a trailing separator to the string if it does not exist. If the
+// input string is empty, "/" will be returned.
+void EnsurePathEndsWithSlash(std::string* in_out_path);
+
+// Returns true if |path| starts with '/'.
+inline bool IsAbsolutePath(const base::StringPiece& path) {
+  return path.starts_with("/");
+}
+
+// Returns true if |path| ends with '/'.
+inline bool EndsWithSlash(const base::StringPiece& path) {
+  return path.ends_with("/");
+}
+
+// Removes all single '.'s and replaces '//+' with '/' in |in_out_path|. The
+// resulting string does not end with a slash unless it is "/", the root
+// directory. The resulting string is "." if the path is equivalent of "."
+// (ex. "./" and "./././").
+void RemoveSingleDotsAndRedundantSlashes(std::string* in_out_path);
+
+// Removes trailing slashes in the given path, but "/" will remain as "/".
+void RemoveTrailingSlashes(std::string* in_out_path);
+
+}  // namespace util
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_PATH_UTIL_H_
diff --git a/src/posix_translation/path_util_test.cc b/src/posix_translation/path_util_test.cc
new file mode 100644
index 0000000..15c29cc
--- /dev/null
+++ b/src/posix_translation/path_util_test.cc
@@ -0,0 +1,180 @@
+// Copyright 2014 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 "base/basictypes.h"  // arraysize
+#include "gtest/gtest.h"
+#include "posix_translation/path_util.h"
+
+namespace posix_translation {
+namespace util {
+
+namespace {
+
+// Copied from base/files/file_path_unittest.cc.
+struct UnaryTestData {
+  const char* input;
+  const char* expected;
+};
+
+// A wrapper around RemoveSingleDotsAndRedundantSlashes() to make the
+// function easier to test.
+std::string DoRemoveSingleDotsAndRedundantSlashes(const std::string& path) {
+  std::string output = path;
+  RemoveSingleDotsAndRedundantSlashes(&output);
+  return output;
+}
+
+// A wrapper around RemoveTrailingSlashes() to make the function easier to
+// test.
+std::string DoRemoveTrailingSlashes(const std::string& path) {
+  std::string output = path;
+  RemoveTrailingSlashes(&output);
+  return output;
+}
+
+}  // namespace
+
+TEST(PathUtilTest, GetDirName) {
+  // Copied from base/files/file_path_unittest.cc. Removed test cases that
+  // checks double-slash ('//') paths since we do not support such paths.
+  const struct UnaryTestData cases[] = {
+    { "",              "." },
+    { "aa",            "." },
+    { "/a",            "/" },
+    { "a/",            "." },
+    { "/aa/bb",        "/aa" },
+    { "/aa/bb/",       "/aa" },
+    { "/aa/bb/ccc",    "/aa/bb" },
+    { "/aa",           "/" },
+    { "/aa/",          "/" },
+    { "/",             "/" },
+    { "aa/",           "." },
+    { "aa/bb",         "aa" },
+    { "aa/bb/",        "aa" },
+    { "0:",            "." },
+    { "@:",            "." },
+    { "[:",            "." },
+    { "`:",            "." },
+    { "{:",            "." },
+    { "\xB3:",         "." },
+    { "\xC5:",         "." },
+  };
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    std::string expected = cases[i].expected;
+    EXPECT_EQ(expected, GetDirName(cases[i].input)) <<
+        "i: " << i << ", input: " << cases[i].input;
+
+    std::string observed = cases[i].input;
+    GetDirNameInPlace(&observed);
+    EXPECT_EQ(expected, observed) <<
+        "i: " << i << ", input: " << cases[i].input;
+  }
+}
+
+TEST(PathUtilTest, JoinPath) {
+  EXPECT_EQ("/foo.txt", JoinPath("/", "foo.txt"));
+  EXPECT_EQ("/foo/bar.txt", JoinPath("/foo", "bar.txt"));
+  EXPECT_EQ("/foo/bar.txt", JoinPath("/foo/", "bar.txt"));
+  // Do not normalize redundant slashes. This behavior is consistent with
+  // Python's os.path.join().
+  EXPECT_EQ("/foo//bar.txt", JoinPath("/foo//", "bar.txt"));
+}
+
+TEST(PathUtilTest, EnsurePathEndsWithSlash) {
+  // Copied from base/files/file_path_unittest.cc.
+  const UnaryTestData cases[] = {
+    { "", "/" },
+    { "/", "/" },
+    { "foo", "foo/" },
+    { "foo/", "foo/" }
+  };
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    std::string observed = cases[i].input;
+    EnsurePathEndsWithSlash(&observed);
+    std::string expected = cases[i].expected;
+    EXPECT_EQ(expected, observed);
+  }
+}
+
+TEST(PathUtilTest, IsAbsolutePath) {
+  EXPECT_FALSE(IsAbsolutePath(""));
+  EXPECT_TRUE(IsAbsolutePath("/"));
+  EXPECT_FALSE(IsAbsolutePath("a"));
+  EXPECT_TRUE(IsAbsolutePath("/a"));
+  EXPECT_FALSE(IsAbsolutePath("a/"));
+  EXPECT_TRUE(IsAbsolutePath("/a/b.txt"));
+  EXPECT_FALSE(IsAbsolutePath("a/b.txt"));
+}
+
+TEST(PathUtilTest, EndsWithSlash) {
+  EXPECT_FALSE(EndsWithSlash(""));
+  EXPECT_TRUE(EndsWithSlash("/"));
+  EXPECT_FALSE(EndsWithSlash("a"));
+  EXPECT_TRUE(EndsWithSlash("a/"));
+  EXPECT_TRUE(EndsWithSlash("/a/"));
+  EXPECT_FALSE(EndsWithSlash("a/b"));
+  EXPECT_TRUE(EndsWithSlash("a/b/"));
+  EXPECT_TRUE(EndsWithSlash("/a/b/"));
+}
+
+TEST(PathUtilTest, RemoveSingleDotsAndRedundantSlashes) {
+  EXPECT_EQ("/", DoRemoveSingleDotsAndRedundantSlashes("/"));
+  EXPECT_EQ("/", DoRemoveSingleDotsAndRedundantSlashes("//"));
+  EXPECT_EQ("/", DoRemoveSingleDotsAndRedundantSlashes("///"));
+  EXPECT_EQ("/foo", DoRemoveSingleDotsAndRedundantSlashes("/foo/"));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/./foo"));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/././foo"));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/./././foo"));
+  EXPECT_EQ("path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("./path/to/./foo"));
+  EXPECT_EQ("path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("././path/to/./foo"));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/foo/."));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/foo/./."));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/foo/././."));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("//././path/to/./foo/./."));
+  EXPECT_EQ("/path/to/foo",
+            DoRemoveSingleDotsAndRedundantSlashes("/././path/to/./foo/./."));
+  EXPECT_EQ("/.dot_file",
+            DoRemoveSingleDotsAndRedundantSlashes("/.dot_file"));
+  EXPECT_EQ("/path/to/.dot_file",
+            DoRemoveSingleDotsAndRedundantSlashes("/path/to/.dot_file"));
+  EXPECT_EQ("/ends_with_dot.",
+            DoRemoveSingleDotsAndRedundantSlashes("/ends_with_dot."));
+  EXPECT_EQ("/ends_with_dot.",
+            DoRemoveSingleDotsAndRedundantSlashes("/ends_with_dot./"));
+  EXPECT_EQ("/ends_with_dot./a",
+            DoRemoveSingleDotsAndRedundantSlashes("/ends_with_dot./a"));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes("."));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes("./"));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes(".//"));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes("./."));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes("././"));
+  EXPECT_EQ(".", DoRemoveSingleDotsAndRedundantSlashes("././/"));
+  EXPECT_EQ("", DoRemoveSingleDotsAndRedundantSlashes(""));
+  EXPECT_EQ("..", DoRemoveSingleDotsAndRedundantSlashes("../"));
+  EXPECT_EQ("foo/..", DoRemoveSingleDotsAndRedundantSlashes("foo/../"));
+  EXPECT_EQ("foo/../bar", DoRemoveSingleDotsAndRedundantSlashes("foo/../bar"));
+}
+
+TEST(PathUtilTest, RemoveTrailingSlashes) {
+  EXPECT_EQ("/", DoRemoveTrailingSlashes("/"));
+  EXPECT_EQ("/", DoRemoveTrailingSlashes("//"));
+  EXPECT_EQ("/", DoRemoveTrailingSlashes("///"));
+  EXPECT_EQ("/foo/bar", DoRemoveTrailingSlashes("/foo/bar"));
+  EXPECT_EQ("/foo/bar", DoRemoveTrailingSlashes("/foo/bar/"));
+  EXPECT_EQ("/foo/bar", DoRemoveTrailingSlashes("/foo/bar//"));
+  // Only trailing slashes should be removed.
+  EXPECT_EQ("//foo//bar", DoRemoveTrailingSlashes("//foo//bar//"));
+}
+
+}  // namespace util
+}  // namespace posix_translation
diff --git a/src/posix_translation/pepper_file.cc b/src/posix_translation/pepper_file.cc
new file mode 100644
index 0000000..af55985
--- /dev/null
+++ b/src/posix_translation/pepper_file.cc
@@ -0,0 +1,1133 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/pepper_file.h"
+
+#include <string.h>  // memset
+#include <sys/ioctl.h>
+
+#include "base/containers/mru_cache.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/safe_strerror_posix.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "common/arc_strace.h"
+#include "common/danger.h"
+#include "common/trace_event.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/ppb_file_io.h"
+#include "ppapi/cpp/directory_entry.h"
+#include "ppapi/cpp/file_ref.h"
+#include "ppapi/cpp/private/file_io_private.h"
+
+namespace posix_translation {
+
+namespace {
+
+const size_t kMaxFSCacheEntries = 1024;
+const blksize_t kBlockSize = 4096;
+
+#if !defined(NDEBUG)
+// TODO(crbug.com/358440): Fix the issue and remove the very ARC specific
+// hack from posix_translation/.
+bool IsWhitelistedFile(const std::string& name) {
+  // dexZipGetEntryInfo in dalvik/libdex/ZipArchive.cpp reads mmaped
+  // (with PROT_WRITE) region so we need to allow all .jar files.
+  if (EndsWith(name, ".jar", true))
+    return true;
+
+  // This allows the App's APK to be read/mmap'd as well as APKs passed to
+  // aapt and during testing.
+  if (EndsWith(name, ".apk", true)) {
+    return true;
+  }
+
+  if (EndsWith(name, ".dex", true)) {
+    return StartsWithASCII(name, "/data/dalvik-cache/", true) ||
+      StartsWithASCII(name, "/data/data/", true);
+  }
+
+  // Secondary dex files are loaded by the same code as .jar from mmaped region.
+  if (EndsWith(name, ".zip", true)) {
+    return StartsWithASCII(name, "/data/data/", true);
+  }
+
+  return false;
+}
+#endif
+
+// Returns true if it is allowed to read/write |pathname| with |inode|. This
+// function may return false if the file associated with the |inode| was/is
+// mmapped. Note that "mmap(PROT_READ), munmap, then read/write" is allowed,
+// but other ways of mixing mmap and read are not allowed. For production
+// (when NDEBUG is defined), this function does nothing and always returns
+// true.
+bool IsReadWriteAllowed(const std::string& pathname, ino_t inode,
+                        const std::string& operation_str) {
+#if !defined(NDEBUG)
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+
+  const bool is_write_mapped = sys->IsWriteMapped(inode);
+  const bool is_currently_mapped =
+    // Do not call IsCurrentlyMapped() when |is_write_mapped| is true
+    // for (slightly) better performance.
+    is_write_mapped ? false : sys->IsCurrentlyMapped(inode);
+  if (!is_write_mapped && !is_currently_mapped)
+    return true;
+
+  static const char kWarnWriteMapped[] = "was/is mmapped with PROT_WRITE";
+  static const char kWarnMapped[] = "is currently mmapped";
+  const std::string log_str = base::StringPrintf(
+      "%s(\"%s\") might not be safe on non-Linux environment since the file %s",
+      operation_str.c_str(), pathname.c_str(),
+      (is_write_mapped ? kWarnWriteMapped : kWarnMapped));
+  ALOGI("%s", log_str.c_str());
+
+  // TODO(crbug.com/358440): Stop calling IsWhitelistedFile().
+  if (IsWhitelistedFile(pathname))
+    return true;
+
+  sys->mutex().AssertAcquired();  // touching |s_show_mmap_warning| is safe.
+  static bool s_show_mmap_warning = true;
+  if (s_show_mmap_warning) {
+    // Show a big warning with ALOGE only once to notify developers that the
+    // current APK is not 100% compatible with non-Linux environment.
+    s_show_mmap_warning = false;
+    ALOGE("********* MMAP COMPATIBILITY ERROR (crbug.com/357780) *********");
+    ALOGE("********* %s *********", log_str.c_str());
+  }
+  return false;
+#else
+  // For production, do not check anything for performance (crbug.com/373645).
+  return true;
+#endif
+}
+
+}  // namespace
+
+#if defined(DEBUG_POSIX_TRANSLATION)
+namespace ipc_stats {
+
+// |VirtualFileSystem::mutex_| must be held before updating these variables.
+size_t g_delete;
+size_t g_fdatasync;
+size_t g_fsync;
+size_t g_make_directory;
+size_t g_open;
+size_t g_query;
+size_t g_read_directory_entries;
+size_t g_rename;
+size_t g_set_length;
+size_t g_touch;
+uint64_t g_write_bytes;
+uint64_t g_read_bytes;
+
+std::string GetIPCStatsAsStringLocked() {
+  VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+  const size_t total = g_delete + g_make_directory + g_open + g_query +
+      g_read_directory_entries + g_rename + g_set_length + g_touch;
+  return base::StringPrintf("PepperFile: Delete:%zu MakeDirectory:%zu Open:%zu "
+                            "Query:%zu ReadDirectoryEntries:%zu Rename:%zu "
+                            "SetLength:%zu Touch:%zu TOTAL:%zu, "
+                            "FSync:%zu FDataSync: %zu, "
+                            "BytesWritten: %llu BytesRead: %llu",
+                            g_delete, g_make_directory, g_open, g_query,
+                            g_read_directory_entries, g_rename, g_set_length,
+                            g_touch, total, g_fsync, g_fdatasync,
+                            g_write_bytes, g_read_bytes);
+}
+
+}  // namespace ipc_stats
+#endif
+
+// A MRU cache to avoid doing extra calls to access/stat.
+// Access is currently implemented in terms of the same function that stat is
+// using. Several applications open files by calling access, followed by stat
+// and open. This causes one extra superfluous call to Pepper, that can be
+// avoided.
+class PepperFileCache {
+ public:
+  explicit PepperFileCache(size_t size) : size_(size), cache_(size) {}
+
+  bool Get(const std::string& path, PP_FileInfo* file_info, bool* exists) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return false;
+    std::string key(path);
+    RemoveTrailingSlash(&key);
+    const MRUCache::iterator it = cache_.Get(key);
+    if (it == cache_.end()) {
+      ARC_STRACE_REPORT("PepperFileCache: Cache miss for %s", path.c_str());
+      return false;
+    }
+    ARC_STRACE_REPORT("PepperFileCache: Cache hit for %s", path.c_str());
+    if (file_info)
+      *file_info = it->second.file_info;
+    if (exists)
+      *exists = it->second.exists;
+    return true;
+  }
+
+  // Returns true when the |path| is definitely non-existent. When it exists or
+  // when it is unknown, returns false.
+  bool IsNonExistent(const std::string& path) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return false;
+    bool exists = false;
+    if (!Get(path, NULL, &exists))
+      return false;  // unknown
+    return !exists;
+  }
+
+  void Set(const std::string& path, const PP_FileInfo& file_info, bool exists) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return;
+    ARC_STRACE_REPORT("PepperFileCache: Adding to cache %s, exists: %s",
+                      path.c_str(), exists ? "true" : "false");
+    const CacheEntry entry = { exists, file_info };
+    std::string key(path);
+    RemoveTrailingSlash(&key);
+    cache_.Put(key, entry);
+  }
+
+  void SetNotExistent(const std::string& path) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return;
+    PP_FileInfo dummy = {};
+    Set(path, dummy, false);
+  }
+
+  void SetNotExistentDirectory(const std::string& path) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return;
+    std::string key(path);
+    if (!util::EndsWithSlash(key))
+      key.append("/");
+
+    PP_FileInfo dummy = {};
+    const CacheEntry entry = { false, dummy };
+    for (MRUCache::iterator i = cache_.begin(); i != cache_.end(); ++i) {
+      if (StartsWithASCII(i->first, key, true))
+        i->second = entry;
+    }
+  }
+
+  void Invalidate(const std::string& path) {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return;
+    ARC_STRACE_REPORT("PepperFileCache: Cache invalidation for %s",
+                      path.c_str());
+    std::string key(path);
+    RemoveTrailingSlash(&key);
+    const MRUCache::iterator it = cache_.Get(key);
+    if (it != cache_.end())
+      cache_.Erase(it);
+  }
+
+  void Clear() {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (!IsCacheEnabled())
+      return;
+    ARC_STRACE_REPORT("PepperFileCache: Invalidate all cache entries");
+    cache_.Clear();
+  }
+
+  void DisableForTesting() {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    Clear();
+    size_ = 0;
+  }
+
+ private:
+  bool IsCacheEnabled() const {
+    return size_ > 0;
+  }
+
+  static void RemoveTrailingSlash(std::string* in_out_path) {
+    ALOG_ASSERT(in_out_path);
+    const size_t len = in_out_path->length();
+    if (len < 2 || !util::EndsWithSlash(*in_out_path))
+      return;
+    in_out_path->erase(len - 1);
+  }
+
+  struct CacheEntry {
+    bool exists;
+    PP_FileInfo file_info;
+  };
+  typedef base::MRUCache<std::string, CacheEntry> MRUCache;
+
+  size_t size_;
+  MRUCache cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(PepperFileCache);
+};
+
+class FileIOWrapper {
+ public:
+  FileIOWrapper(pp::FileIO* file_io, PP_FileHandle native_handle,
+                const std::string& filename)
+      : file_io_(file_io), native_handle_(native_handle) {
+    ALOG_ASSERT(native_handle_ >= 0);
+  }
+  ~FileIOWrapper() {
+    if (native_handle_ != PP_kInvalidFileHandle) {
+      if (::close(native_handle_)) {
+        ALOGW("libc_close failed: native_handle_=%d: %s",
+              native_handle_, safe_strerror(errno).c_str());
+      }
+    }
+  }
+
+  pp::FileIO* file_io() { return file_io_.get(); }
+  PP_FileHandle native_handle() { return native_handle_; }
+
+ private:
+  scoped_ptr<pp::FileIO> file_io_;
+  PP_FileHandle native_handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileIOWrapper);
+};
+
+PepperFileHandler::PepperFileHandler()
+    : FileSystemHandler("PepperFileHandler"),
+      factory_(this),
+      cache_(new PepperFileCache(kMaxFSCacheEntries)) {
+}
+
+PepperFileHandler::PepperFileHandler(const char* name, size_t max_cache_size)
+    : FileSystemHandler(name),
+      factory_(this),
+      cache_(new PepperFileCache(max_cache_size)) {
+}
+
+PepperFileHandler::~PepperFileHandler() {
+}
+
+void PepperFileHandler::OpenPepperFileSystem(pp::Instance* instance) {
+  // Since Chrome ignores |kExpectedUsage|, the actual value is not important.
+  static const uint64_t kExpectedUsage = 16ULL * 1024 * 1024 * 1024;
+  ALOG_ASSERT(pp::Module::Get()->core()->IsMainThread());
+  pp::FileSystem* file_system =
+      new pp::FileSystem(instance, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
+  TRACE_EVENT_ASYNC_BEGIN1(ARC_TRACE_CATEGORY,
+                           "PepperFileHandler::OpenPepperFileSystem",
+                           this, "type", PP_FILESYSTEMTYPE_LOCALPERSISTENT);
+  const int32_t result = file_system->Open(
+      kExpectedUsage,
+      factory_.NewCallback(&PepperFileHandler::OnFileSystemOpen, file_system));
+  ALOG_ASSERT(result == PP_OK_COMPLETIONPENDING,
+              "Failed to create pp::FileSystem, error: %d", result);
+}
+
+void PepperFileHandler::DisableCacheForTesting() {
+  cache_->DisableForTesting();
+}
+
+void PepperFileHandler::OnFileSystemOpen(int32_t result,
+                                         pp::FileSystem* file_system) {
+  TRACE_EVENT_ASYNC_END1(ARC_TRACE_CATEGORY,
+                         "PepperFileHandler::OpenPepperFileSystem",
+                         this, "result", result);
+  if (result != PP_OK)
+    LOG_FATAL("Failed to open pp::FileSystem, error: %d", result);
+  SetPepperFileSystem(file_system, "/", "/");
+}
+
+bool PepperFileHandler::IsInitialized() const {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  return file_system_.get() && sys->IsBrowserReadyLocked();
+}
+
+void PepperFileHandler::Initialize() {
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::Initialize");
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  ALOG_ASSERT(!IsInitialized());
+  while (!IsInitialized())
+    sys->Wait();
+}
+
+std::string PepperFileHandler::SetPepperFileSystem(
+    const pp::FileSystem* pepper_file_system,
+    const std::string& mount_source_in_pepper_file_system,
+    const std::string& mount_dest_in_vfs) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  ALOG_ASSERT(pepper_file_system);
+  ALOG_ASSERT(!file_system_);
+  file_system_.reset(pepper_file_system);
+  ARC_STRACE_REPORT("Mounting %s in pp::FileSystem %p to %s in VFS",
+                    mount_source_in_pepper_file_system.c_str(),
+                    pepper_file_system,
+                    mount_dest_in_vfs.c_str());
+  sys->Broadcast();
+  return mount_dest_in_vfs;
+}
+
+bool PepperFileHandler::IsWorldWritable(const std::string& pathname) {
+  // Calling this->stat() every time when VFS::GetFileSystemHandlerLocked() is
+  // invoked is too expensive for this handler (and this handler's stat() does
+  // not fill the permission part of st_mode anyway). Just returning false
+  // would be just fine.
+  return false;
+}
+
+scoped_refptr<FileStream> PepperFileHandler::open(
+    int unused_fd, const std::string& pathname, int oflag, mode_t cmode) {
+  // TODO(crbug.com/242355): Use |cmode|.
+  TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::open",
+               "pathname", pathname, "oflag", oflag);
+  // First, check the cache if O_CREAT is not in |oflag|.
+  if (pathname.empty() ||
+      (!(oflag & O_CREAT) && cache_->IsNonExistent(pathname))) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::open - Pepper");
+  const int access_mode = (oflag & O_ACCMODE);
+
+  // When needed, invalidate the |cache_| before calling "new PepperFile" which
+  // might unlock the |mutex_|. Note that 'O_RDONLY|O_CREAT' is allowed at least
+  // on Linux and it may actually create the file. Just in case, do the same for
+  // 'O_RDONLY|O_TRUNC' which may also truncate the file at least on Linux (even
+  // though pp::FileIO seems to refuse the latter).
+  if ((access_mode != O_RDONLY) || (oflag & (O_CREAT | O_TRUNC)))
+    cache_->Invalidate(pathname);
+
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFile::open",
+               "pathname", pathname);
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+
+  const int open_flags = ConvertNativeOpenFlagsToPepper(oflag);
+  int32_t result;
+  scoped_ptr<pp::FileIO_Private> file_io;
+  PP_FileHandle file_handle;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef file_ref(*file_system_, pathname.c_str());
+    file_io.reset(new pp::FileIO_Private(sys->instance()));
+    result = file_io->Open(file_ref, open_flags, pp::BlockUntilComplete());
+    if (result == PP_OK) {
+      pp::CompletionCallbackWithOutput<pp::PassFileHandle> cb(&file_handle);
+      result = file_io->RequestOSFileHandle(cb);
+      if (result != PP_OK) {
+        ALOGE("PPB_FileIO_Private::RequestOSFileHandle failed! This usually "
+              "means that your app does not have unlimitedStorage permission.");
+      }
+    }
+  }
+
+  ARC_STRACE_REPORT_PP_ERROR(result);
+
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_open;
+#endif
+  scoped_refptr<FileStream> stream = NULL;
+  if (result == PP_OK) {
+    if (oflag & O_DIRECTORY) {
+      errno = ENOTDIR;
+      return NULL;
+    }
+    stream = new PepperFile(
+        oflag, cache_.get(), pathname,
+        new FileIOWrapper(file_io.release(), file_handle, pathname));
+  } else if (result == PP_ERROR_NOTAFILE) {
+    // A directory is opened.
+    if (access_mode != O_RDONLY) {
+      errno = EISDIR;
+      return NULL;
+    }
+    stream = new DirectoryFileStream("pepper", pathname, this);
+  } else {
+    errno = ConvertPepperErrorToErrno(result);
+  }
+
+  return stream;
+}
+
+Dir* PepperFileHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY,
+               "PepperFileHandler::OnDirectoryContentsNeeded", "name", name);
+
+  // First, check the cache.
+  if (name.empty() || cache_->IsNonExistent(name)) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY,
+               "PepperFileHandler::OnDirectoryContentsNeeded - Pepper");
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  int32_t result;
+
+  pp::internal::DirectoryEntryArrayOutputAdapterWithStorage adapter;
+  pp::CompletionCallbackWithOutput<std::vector<pp::DirectoryEntry> > cb(
+      &adapter);
+
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef file_ref(*file_system_, name.c_str());
+    result = file_ref.ReadDirectoryEntries(cb);
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_read_directory_entries;
+#endif
+
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result != PP_OK) {
+    errno = ConvertPepperErrorToErrno(result);
+    // getdents should not return these values.
+    if (errno == EEXIST || errno == EISDIR ||
+        errno == ENOSPC || errno == EPERM) {
+      ALOG_ASSERT(false, "errno=%d", errno);
+      errno = ENOENT;
+    }
+    return NULL;
+  }
+
+  const std::vector<pp::DirectoryEntry>& directories = adapter.output();
+  const base::FilePath base_path(name);
+  DirectoryManager directory_manager;
+  // We have already confirmed the directory exists. Make sure
+  // OpenDirectory will succeed for empty directories by adding the
+  // directory we are checking.
+  directory_manager.MakeDirectories(name);
+  for (size_t i = 0; i < directories.size(); ++i) {
+    const pp::DirectoryEntry& entry = directories[i];
+    const pp::FileRef& ref = entry.file_ref();
+    std::string filename = base_path.Append(ref.GetName().AsString()).value();
+    if (entry.file_type() == PP_FILETYPE_DIRECTORY) {
+      directory_manager.MakeDirectories(filename);
+    } else {
+      bool result = directory_manager.AddFile(filename);
+      ALOG_ASSERT(result);
+    }
+  }
+
+  return directory_manager.OpenDirectory(name);
+}
+
+int PepperFileHandler::stat(const std::string& pathname, struct stat* out) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::stat",
+               "pathname", pathname);
+
+  PP_FileInfo file_info = {};
+  bool exists = false;
+  if (!cache_->Get(pathname, &file_info, &exists)) {
+    TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::stat - Pepper");
+    int32_t result = QueryRefLocked(pathname, &file_info);
+    ARC_STRACE_REPORT_PP_ERROR(result);
+    exists = result == PP_OK;
+    cache_->Set(pathname, file_info, exists);
+  }
+
+  if (!exists) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  if (file_info.type == PP_FILETYPE_DIRECTORY) {
+    DirectoryFileStream::FillStatData(pathname, out);
+    // Do not fill st_mtime for a directory to be consistent with
+    // DirectoryFileStream::fstat.
+  } else {
+    memset(out, 0, sizeof(struct stat));
+    // Always assigning 0 (or another constant) to |st_ino| does not always
+    // work. For example, since SQLite3 manages the current file lock status
+    // per inode (see unixLock() in sqlite/dist/sqlite3.c), always using
+    // 0 for |st_ino| may cause deadlock.
+    out->st_ino =
+        VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
+    out->st_mode = S_IFREG;
+    out->st_nlink = 1;
+    out->st_size = file_info.size;
+    out->st_blksize = kBlockSize;
+    // We do not support atime and ctime. See PepperFile::fstat().
+    out->st_mtime = static_cast<time_t>(file_info.last_modified_time);
+  }
+
+  return 0;
+}
+
+int PepperFileHandler::statfs(const std::string& pathname, struct statfs* out) {
+  // TODO(crbug.com/242832): Return real values by apps v2 API.
+  // http://developer.chrome.com/extensions/experimental.systemInfo.storage.html
+  struct stat st;
+  if (this->stat(pathname, &st) == 0)
+    return DoStatFsForData(out);
+  errno = ENOENT;
+  return -1;
+}
+
+int PepperFileHandler::mkdir(const std::string& pathname, mode_t mode) {
+  TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::mkdir",
+               "pathname", pathname, "mode", mode);
+
+  // First, check the cache.
+  PP_FileInfo file_info = {};
+  bool exists = false;
+  if (cache_->Get(pathname, &file_info, &exists) && exists) {
+    // |pathname| already exists (either file or directory).
+    errno = EEXIST;
+    return -1;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::mkdir - Pepper");
+  cache_->Invalidate(pathname);  // call this before unlocking the |mutex_|.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  int32_t result;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef file_ref(*file_system_, pathname.c_str());
+    result = file_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_EXCLUSIVE,
+                                    pp::BlockUntilComplete());
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_make_directory;
+#endif
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result == PP_OK)
+    return 0;
+  errno = ConvertPepperErrorToErrno(result);
+  // mkdir should not return EISDIR.
+  if (errno == EISDIR) {
+    ALOG_ASSERT(false, "errno=%d", errno);
+    errno = ENOENT;
+  }
+  return -1;
+}
+
+int PepperFileHandler::remove(const std::string& pathname) {
+  // Remove an empty directory or a file specified by |pathname|.
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::remove",
+               "pathname", pathname);
+
+  // First, check the cache.
+  if (cache_->IsNonExistent(pathname)) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::remove - Pepper");
+  cache_->Invalidate(pathname);  // call this before unlocking the |mutex_|.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  int32_t result;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef file_ref(*file_system_, pathname.c_str());
+    result = file_ref.Delete(pp::BlockUntilComplete());
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_delete;
+#endif
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result == PP_ERROR_FILENOTFOUND) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (result != PP_OK) {
+    // TODO(crbug.com/180985): ARC running on Windows might return PP_ERROR
+    // to Remove. We might have to add a "delete later" logic here for Windows.
+    // Use ConvertPepperErrorToErrno once this issue is resolved.
+    errno = EISDIR;
+    return -1;
+  }
+  sys->RemoveInodeLocked(pathname);
+  // No need to call SetNotExistentDirectory since remove() can remove only
+  // empty directory.
+  cache_->SetNotExistent(pathname);
+  return 0;
+}
+
+int PepperFileHandler::rename(const std::string& oldpath,
+                              const std::string& newpath) {
+  TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::rename",
+               "oldpath", oldpath, "newpath", newpath);
+
+  // First, check the cache.
+  if (cache_->IsNonExistent(oldpath)) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::rename - Pepper");
+  PP_FileInfo old_file_info = {};
+  bool old_file_has_metadata = cache_->Get(oldpath, &old_file_info, NULL);
+  cache_->Invalidate(oldpath);  // call this before unlocking the |mutex_|.
+  cache_->Invalidate(newpath);  // call this before unlocking the |mutex_|.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  int32_t result;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef old_file_ref(*file_system_, oldpath.c_str());
+    pp::FileRef new_file_ref(*file_system_, newpath.c_str());
+    result = old_file_ref.Rename(
+        new_file_ref, pp::BlockUntilComplete());
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_rename;
+#endif
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result != PP_OK) {
+    errno = ConvertPepperErrorToErrno(result);
+    return -1;
+  }
+  if (oldpath != newpath)
+    cache_->SetNotExistentDirectory(oldpath);
+  if (old_file_has_metadata)
+    cache_->Set(newpath, old_file_info, true);  // rename preserves metadata.
+  sys->ReassignInodeLocked(oldpath, newpath);
+  // rename() should not change inode.
+  return 0;
+}
+
+int PepperFileHandler::rmdir(const std::string& pathname) {
+  // TODO(crbug.com/190550): Implement this properly. Note that we should
+  // return ENOTDIR if |pathname| is a file, but right now we do not have a
+  // good way to perform the check without unlocking the |mutex|. For now,
+  // just call remove() since some apps require this API and a file name is
+  // usually not passed to rmdir(). To fix this issue properly, we likely
+  // have to add an API to pp::FileRef.
+  ALOGW("PepperFileHandler::rmdir is not fully POSIX compatible and may"
+        " delete a file: %s", pathname.c_str());
+  return this->remove(pathname);
+}
+
+int PepperFileHandler::truncate(const std::string& pathname,
+                                off64_t length) {
+  TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::truncate",
+               "pathname", pathname, "length", length);
+
+  // First, check the cache.
+  if (cache_->IsNonExistent(pathname)) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::truncate - Pepper");
+  scoped_refptr<FileStream> stream = this->open(-1, pathname, O_WRONLY, 0);
+  if (stream == NULL) {
+    // truncate should not return these errno values.
+    if (errno == EEXIST || errno == ENOMEM || errno == ENOSPC) {
+      ALOG_ASSERT(false, "errno=%d", errno);
+      errno = ENOENT;
+    }
+    return -1;
+  }
+  return stream->ftruncate(length);
+}
+
+int PepperFileHandler::unlink(const std::string& pathname) {
+  // TODO(crbug.com/190550): Return EISDIR if |pathname| is a directory. Right
+  // now, we do not have a good way to perform the check without unlocking the
+  // |mutex|.
+  return this->remove(pathname);
+}
+
+int PepperFileHandler::utimes(const std::string& pathname,
+                              const struct timeval times[2]) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::utimes",
+               "pathname", pathname);
+
+  // First, check the cache.
+  if (cache_->IsNonExistent(pathname)) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::utimes - Pepper");
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  if (!times) {
+    errno = EACCES;
+    return -1;
+  }
+  cache_->Invalidate(pathname);  // call this before unlocking the |mutex_|.
+  int32_t result;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileRef file_ref(*file_system_, pathname.c_str());
+    result = file_ref.Touch(
+        times[0].tv_sec, times[1].tv_sec, pp::BlockUntilComplete());
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_touch;
+#endif
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result != PP_OK) {
+    errno = ConvertPepperErrorToErrno(result);
+    // utimes should not return these errno values.
+    if (errno == EEXIST || errno == EISDIR ||
+        errno == ENOMEM || errno == ENOSPC) {
+      ALOG_ASSERT(false, "errno=%d", errno);
+      errno = ENOENT;
+    }
+    return -1;
+  }
+  return 0;
+}
+
+void PepperFileHandler::InvalidateCache() {
+  cache_->Clear();
+}
+
+void PepperFileHandler::AddToCache(const std::string& path,
+                                   const PP_FileInfo& file_info,
+                                   bool exists) {
+  cache_->Set(path, file_info, exists);
+}
+
+void PepperFileHandler::OnMounted(const std::string& path) {
+  // Check if |path| being mounted exists. If this function is called on the
+  // main thread, do not check the existence. There are two cases when this
+  // function is called on the main thread: during handler initialization, the
+  // library user mounts a static set of paths that are known to be validn.
+  // The other case is that the external file handler mounts an existing
+  // external file.
+  // Note: It is better to move this check to MountPointManager::Add, but doing
+  // so breaks many unit tests outside this library.
+  PP_FileInfo info;
+  ALOG_ASSERT(pp::Module::Get()->core()->IsMainThread() ||
+              (QueryRefLocked(path, &info) == PP_OK),
+              "Unknown path '%s' is mounted", path.c_str());
+
+  // Update the cache when possible.
+  if (!util::EndsWithSlash(path)) {
+    // Ignore OnMounted calls against files since it is difficult to fill the
+    // cache for files. Note that chown("/path/to/pepper/file", ..) may end up
+    // taking this path.
+    return;
+  }
+  PP_FileInfo file_info = {};
+  file_info.size = 4096;
+  file_info.type = PP_FILETYPE_DIRECTORY;
+  // For directories, we do not have to fill mtime. See DirectoryFileStream.cc.
+  cache_->Set(path, file_info, true);
+}
+
+void PepperFileHandler::OnUnmounted(const std::string& path) {
+  cache_->Invalidate(path);
+}
+
+int32_t PepperFileHandler::QueryRefLocked(const std::string& pathname,
+                                          PP_FileInfo* out_file_info) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_query;
+#endif
+  // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+  base::AutoUnlock unlock(sys->mutex());
+  pp::FileRef file_ref(*file_system_, pathname.c_str());
+  pp::CompletionCallbackWithOutput<PP_FileInfo> cb(out_file_info);
+  return file_ref.Query(cb);
+}
+
+// static
+int PepperFileHandler::ConvertPepperErrorToErrno(int pp_error) {
+  switch (pp_error) {
+    case PP_ERROR_FILENOTFOUND:
+      return ENOENT;
+    case PP_ERROR_FILEEXISTS:
+      return EEXIST;
+    case PP_ERROR_NOACCESS:
+      // This error code is returned when the system tries to write
+      // something to CRX file system. As the CRX file system is
+      // read-only, EPERM is more appropriate than EACCES.
+      return EPERM;
+    case PP_ERROR_NOMEMORY:
+      return ENOMEM;
+    case PP_ERROR_NOQUOTA:
+    case PP_ERROR_NOSPACE:
+      return ENOSPC;
+    case PP_ERROR_NOTAFILE:
+      return EISDIR;
+    case PP_ERROR_BADRESOURCE:
+      return EBADF;
+    default:
+      // TODO(crbug.com/293953): Some of PP_ERROR_FAILED should be ENOTDIR.
+      DANGERF("Unknown Pepper error code: %d", pp_error);
+      return ENOENT;
+  }
+}
+
+// static
+int PepperFileHandler::ConvertNativeOpenFlagsToPepper(int native_flags) {
+  int pepper_flags = 0;
+
+  if ((native_flags & O_ACCMODE) == O_WRONLY) {
+    pepper_flags = PP_FILEOPENFLAG_WRITE;
+  } else if ((native_flags & O_ACCMODE) == O_RDONLY) {
+    pepper_flags = PP_FILEOPENFLAG_READ;
+  } else if ((native_flags & O_ACCMODE) == O_RDWR) {
+    pepper_flags = PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE;
+  } else {
+    ALOGW("Unknown open flags %o, falling back to O_RDONLY", native_flags);
+    pepper_flags = PP_FILEOPENFLAG_READ;
+  }
+
+  if (native_flags & O_CREAT)
+    pepper_flags |= PP_FILEOPENFLAG_CREATE;
+  if (native_flags & O_EXCL)
+    pepper_flags |= PP_FILEOPENFLAG_EXCLUSIVE;
+  if (native_flags & O_TRUNC)
+    pepper_flags |= PP_FILEOPENFLAG_TRUNCATE;
+
+  if (native_flags & O_NOCTTY)
+    ALOGW("O_NOCTTY is not supported");
+  if (native_flags & O_NONBLOCK)
+    ALOGW("O_NONBLOCK is not supported");
+  if (native_flags & O_SYNC)
+    ALOGW("O_SYNC is not supported");
+  if (native_flags & O_ASYNC)
+    ALOGW("O_ASYNC is not supported");
+  if (native_flags & O_NOFOLLOW)
+    ALOGW("O_NOFOLLOW is not supported");
+  if (native_flags & O_CLOEXEC)
+    ALOGW("O_CLOEXEC is not supported");
+  if (native_flags & O_NOATIME)
+    ALOGW("O_NOATIME is not supported");
+
+  if (native_flags & O_APPEND) {
+    if (pepper_flags & PP_FILEOPENFLAG_TRUNCATE) {
+      // TODO(crbug.com/308809): Support O_APPEND | O_TRUNC file open.
+      ALOGW("O_TRUNC with O_APPEND is not supported.");
+    }
+    if (pepper_flags & PP_FILEOPENFLAG_WRITE) {
+      // _WRITE and _APPEND flags are exclusive in Pepper.
+      pepper_flags |= PP_FILEOPENFLAG_APPEND;
+      pepper_flags &= ~PP_FILEOPENFLAG_WRITE;
+    } else {
+      ALOGW("O_APPEND is specified with O_RDONLY. Ignored.");
+    }
+  }
+
+  return pepper_flags;
+}
+
+//------------------------------------------------------------------------------
+
+PepperFile::PepperFile(int oflag,
+                       PepperFileCache* cache,
+                       const std::string& pathname,
+                       FileIOWrapper* file_wrapper)
+    : FileStream(oflag, pathname),
+      factory_(this),
+      cache_(cache),
+      file_(file_wrapper) {
+  ALOG_ASSERT(cache);
+  ALOG_ASSERT(file_wrapper);
+}
+
+PepperFile::~PepperFile() {}
+
+void* PepperFile::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  void* result =
+      ::mmap(addr, length, prot, flags, file_->native_handle(), offset);
+  if (prot & PROT_WRITE)
+    cache_->Invalidate(pathname());
+  return result;
+}
+
+int PepperFile::munmap(void* addr, size_t length) {
+  int result = ::munmap(addr, length);
+  if ((oflag() & O_ACCMODE) != O_RDONLY)
+    cache_->Invalidate(pathname());
+  return result;
+}
+
+ssize_t PepperFile::read(void* buf, size_t count) {
+  // Detect non-portable read attempts like mmap(W)-munmap-read and
+  // mmap(W)-read. For more details, see crbug.com/357780.
+  if (!IsReadWriteAllowed(pathname(), inode(), "read")) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  const ssize_t result = ::read(file_->native_handle(), buf, count);
+#if defined(DEBUG_POSIX_TRANSLATION)
+  if (result > 0)
+    ipc_stats::g_read_bytes += result;
+#endif
+  return result;
+}
+
+// Note for atomicity of the write/pread/pwrite operations below:
+//
+// PepperFile::write(), PepperFile::pread(), and PepperFile::pwrite() calls
+// lseek() to emulate Linux kernel's behavior. The
+// "lseek-lseek-read/write-lseek" (for emulating pread and pwrite) sequence
+// is safe for the following reasons.
+//
+// * Only the PPAPI (or NaCl) process for the app and HTML5 FS code in browser
+//   process access files for the app in the FS.
+// * For each app, only one PPAPI (or NaCl) process is started.
+// * All POSIX compatible functions in this file are synchronized. For example,
+//   VirtualFileSystem::write locks the |mutex_| before calling into
+//   PepperFile::write.
+// * All operations that might change the file offset of a file descriptor,
+//   PepperFile::lseek, PepperFile::read, PepperFile::write, PepperFile::pread,
+//   and PepperFile::pwrite, are done within this process. They never issues an
+//   IPC.
+// * Other asynchronous operations, such as PepperFileHandler::unlink,
+//   PepperFileHandler::truncate, and PepperFile::ftruncate could be done in the
+//   browser process in parallel to the lseek, read, write, pread, and pwrite
+//   operations above, but the operations in the browser never change the offset
+//   of a descriptor.
+
+ssize_t PepperFile::write(const void* buf, size_t count) {
+  // Detect non-portable write attempts like mmap(W)-write and
+  // mmap(W)-munmap-write. For more details, see crbug.com/357780.
+  if (!IsReadWriteAllowed(pathname(), inode(), "write")) {
+    errno = EFAULT;
+    return -1;
+  }
+
+  cache_->Invalidate(pathname());
+  const ssize_t result = ::write(file_->native_handle(), buf, count);
+#if defined(DEBUG_POSIX_TRANSLATION)
+  if (result > 0)
+    ipc_stats::g_write_bytes += result;
+#endif
+  return result;
+}
+
+off64_t PepperFile::lseek(off64_t offset, int whence) {
+  return ::lseek64(file_->native_handle(), offset, whence);
+}
+
+int PepperFile::fdatasync() {
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFile::fdatasync");
+  // TODO(crbug.com/242349): Call NaCl IRT or pp::FileIO::Flush().
+  ARC_STRACE_REPORT("not implemented yet");
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_fdatasync;
+#endif
+  return 0;
+}
+
+int PepperFile::fstat(struct stat* out) {
+  int result = ::fstat(file_->native_handle(), out);
+  if (!result) {
+    // If we expose the values got from host filesystem, the result
+    // will be inconsistent with stat and lstat. Let VirtualFileSystem set
+    // permission bits.
+    out->st_mode &= ~0777;
+    out->st_ino = inode();
+    // Overwrite the real dev/rdev numbers with zero. This is necessary for e.g.
+    // dexopt to work. dvmOpenCachedDexFile() in DexPrepare.cpp checks if st_dev
+    // numbers returned from ::stat(path) and ::fstat(fd_for_the_path) are the
+    // same, and retries until they return the same st_dev numbers.
+    out->st_dev = out->st_rdev = 0;
+    // We do not support atime and ctime. Note that java.io.File does not
+    // provide a way to access them.
+    out->st_atime = out->st_ctime = 0;
+    // TODO(crbug.com/242337): Fill this value?
+    out->st_blocks = 0;
+    out->st_blksize = kBlockSize;
+  }
+  return result;
+}
+
+int PepperFile::fsync() {
+  TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFile::fsync");
+  // TODO(crbug.com/242349): Call NaCl IRT or pp::FileIO::Flush().
+  ARC_STRACE_REPORT("not implemented yet");
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_fsync;
+#endif
+  return 0;
+}
+
+int PepperFile::ftruncate(off64_t length) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFile::ftruncate", "length", length);
+
+  if ((oflag() & O_ACCMODE) == O_RDONLY) {
+    errno = EBADF;
+    return -1;
+  }
+
+  cache_->Invalidate(pathname());
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  int32_t result;
+  {
+    // TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
+    base::AutoUnlock unlock(sys->mutex());
+    pp::FileIO* file_io = file_->file_io();
+    result = file_io->SetLength(length, pp::BlockUntilComplete());
+  }
+#if defined(DEBUG_POSIX_TRANSLATION)
+  ++ipc_stats::g_set_length;
+#endif
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  if (result != PP_OK) {
+    DANGERF("ftruncate failed with Pepper error code: %d", result);
+    errno = EACCES;
+    return -1;
+  }
+  return 0;
+}
+
+int PepperFile::ioctl(int request, va_list ap) {
+  if (request == FIONREAD) {
+    // According to "man ioctl_list", FIONREAD stores its value as an int*.
+    int* argp = va_arg(ap, int*);
+    *argp = 0;
+    off64_t pos = this->lseek(0, SEEK_CUR);
+    if (pos == -1) {
+      ALOGE("lseek(cur) returned error %d", errno);
+      errno = EINVAL;
+      return -1;
+    }
+    struct stat st;
+    if (this->fstat(&st)) {
+      ALOGE("fstat() returned error %d", errno);
+      errno = EINVAL;
+      return -1;
+    }
+    if (pos < st.st_size)
+      *argp = (st.st_size - pos);
+    return 0;
+  }
+  ALOGE("ioctl command %d not supported\n", request);
+  errno = EINVAL;
+  return -1;
+}
+
+const char* PepperFile::GetStreamType() const {
+  return "pepper";
+}
+
+size_t PepperFile::GetSize() const {
+  struct stat st;
+  if (const_cast<PepperFile*>(this)->fstat(&st))
+    return 0;  // unknown size
+  return st.st_size;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/pepper_file.h b/src/posix_translation/pepper_file.h
new file mode 100644
index 0000000..8986767
--- /dev/null
+++ b/src/posix_translation/pepper_file.h
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_PEPPER_FILE_H_
+#define POSIX_TRANSLATION_PEPPER_FILE_H_
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/export.h"
+#include "posix_translation/file_system_handler.h"
+#include "ppapi/cpp/directory_entry.h"
+#include "ppapi/cpp/file_system.h"
+#include "ppapi/cpp/private/file_io_private.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace posix_translation {
+
+class FileIOWrapper;
+class PepperFileCache;
+
+// A handler which handles files in the LOCALPERSISTENT Pepper (aka HTML5)
+// filesystem. Note that files in the filesystem are not read-only.
+class ARC_EXPORT PepperFileHandler : public FileSystemHandler {
+ public:
+  PepperFileHandler();
+  PepperFileHandler(const char* name, size_t max_cache_size);
+  virtual ~PepperFileHandler();
+
+  virtual void OpenPepperFileSystem(pp::Instance* instance);
+
+  virtual bool IsInitialized() const OVERRIDE;
+  virtual void Initialize() OVERRIDE;
+  virtual bool IsWorldWritable(const std::string& pathname) OVERRIDE;
+  virtual std::string SetPepperFileSystem(
+      const pp::FileSystem* file_system,
+      const std::string& path_in_pepperfs,
+      const std::string& path_in_vfs) OVERRIDE;
+
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual int remove(const std::string& pathname) OVERRIDE;
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE;
+  virtual int rmdir(const std::string& pathname) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual int truncate(const std::string& pathname, off64_t length) OVERRIDE;
+  virtual int unlink(const std::string& pathname) OVERRIDE;
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]) OVERRIDE;
+
+  virtual void InvalidateCache() OVERRIDE;
+  virtual void AddToCache(const std::string& path,
+                          const PP_FileInfo& file_info,
+                          bool exists) OVERRIDE;
+  virtual void OnMounted(const std::string& path) OVERRIDE;
+  virtual void OnUnmounted(const std::string& path) OVERRIDE;
+
+  static int ConvertPepperErrorToErrno(int pp_error);
+  static int ConvertNativeOpenFlagsToPepper(int native_flags);
+
+ private:
+  friend class PepperFileTest;
+  void DisableCacheForTesting();
+
+  void OnFileSystemOpen(int32_t result, pp::FileSystem* file_system);
+  int32_t QueryRefLocked(const std::string& pathname,
+                         PP_FileInfo* out_file_info);
+
+  scoped_ptr<const pp::FileSystem> file_system_;
+  pp::CompletionCallbackFactory<PepperFileHandler> factory_;
+  scoped_ptr<PepperFileCache> cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(PepperFileHandler);
+};
+
+class PepperFile : public FileStream {
+ public:
+  PepperFile(int oflag,
+             PepperFileCache* cache,
+             const std::string& pathname,
+             FileIOWrapper* file_wrapper);
+
+  int32_t open(const std::string& pathname);
+
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual int fdatasync() OVERRIDE;
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual int fsync() OVERRIDE;
+  virtual int ftruncate(off64_t length) OVERRIDE;
+
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+  virtual size_t GetSize() const OVERRIDE;
+
+ protected:
+  virtual ~PepperFile();
+
+ private:
+  friend class PepperFileCache;
+  friend class PepperFileTest;
+
+  pp::CompletionCallbackFactory<PepperFile> factory_;
+  PepperFileCache* cache_;
+  scoped_ptr<FileIOWrapper> file_;
+
+  DISALLOW_COPY_AND_ASSIGN(PepperFile);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_PEPPER_FILE_H_
diff --git a/src/posix_translation/pepper_file_test.cc b/src/posix_translation/pepper_file_test.cc
new file mode 100644
index 0000000..dcf03a3
--- /dev/null
+++ b/src/posix_translation/pepper_file_test.cc
@@ -0,0 +1,887 @@
+// Copyright (c) 2013 The Chromium OS 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 "posix_translation/pepper_file.h"
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/process_emulator.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi_mocks/background_test.h"
+#include "ppapi_mocks/background_thread.h"
+#include "ppapi_mocks/ppapi_test.h"
+#include "ppapi_mocks/ppb_file_io.h"
+#include "ppapi_mocks/ppb_file_io_private.h"
+#include "ppapi_mocks/ppb_file_ref.h"
+
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::WithArgs;
+
+namespace posix_translation {
+
+class PepperFileTest : public FileSystemBackgroundTestCommon<PepperFileTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(TestAccess);
+  DECLARE_BACKGROUND_TEST(TestAccessDirectory);
+  DECLARE_BACKGROUND_TEST(TestAccessFail);
+  DECLARE_BACKGROUND_TEST(TestFstat);
+  DECLARE_BACKGROUND_TEST(TestFtruncateReadonly);
+  DECLARE_BACKGROUND_TEST(TestMkdir);
+  DECLARE_BACKGROUND_TEST(TestMkdirFail);
+  DECLARE_BACKGROUND_TEST(TestMkdirNoPermission);
+  DECLARE_BACKGROUND_TEST(TestOpenAppend);
+  DECLARE_BACKGROUND_TEST(TestOpenCreat);
+  DECLARE_BACKGROUND_TEST(TestOpenCreatExclusive);
+  DECLARE_BACKGROUND_TEST(TestOpenCreatTruncate);
+  DECLARE_BACKGROUND_TEST(TestOpenCreatWriteOnly);
+  DECLARE_BACKGROUND_TEST(TestOpenDirectory);
+  DECLARE_BACKGROUND_TEST(TestOpenDirectoryWithWriteAccess);
+  DECLARE_BACKGROUND_TEST(TestOpenClose);
+  DECLARE_BACKGROUND_TEST(TestOpenExclusiveFail);
+  DECLARE_BACKGROUND_TEST(TestOpenNoentFail);
+  DECLARE_BACKGROUND_TEST(TestOpenPermFail);
+  DECLARE_BACKGROUND_TEST(TestOpenRead);
+  DECLARE_BACKGROUND_TEST(TestOpenWithOpenDirectoryFlag);
+  DECLARE_BACKGROUND_TEST(TestPacketCalls);
+  DECLARE_BACKGROUND_TEST(TestRename);
+  DECLARE_BACKGROUND_TEST(TestRenameInode);
+  DECLARE_BACKGROUND_TEST(TestRenameInode2);
+  DECLARE_BACKGROUND_TEST(TestRenameEnoentFail);
+  DECLARE_BACKGROUND_TEST(TestRenameEisdirFail);
+  DECLARE_BACKGROUND_TEST(TestRequestHandleFail);
+  DECLARE_BACKGROUND_TEST(TestStat);
+  DECLARE_BACKGROUND_TEST(TestStatCache);
+  DECLARE_BACKGROUND_TEST(TestStatCacheDisabled);
+  DECLARE_BACKGROUND_TEST(TestStatCacheInvalidation);
+  DECLARE_BACKGROUND_TEST(TestStatCacheWithTrailingSlash);
+  DECLARE_BACKGROUND_TEST(TestStatDirectory);
+  DECLARE_BACKGROUND_TEST(TestStatWithENOENT);
+  DECLARE_BACKGROUND_TEST(TestTruncate);
+  DECLARE_BACKGROUND_TEST(TestTruncateFail);
+  DECLARE_BACKGROUND_TEST(TestUTime);
+  DECLARE_BACKGROUND_TEST(TestUTimeFail);
+  DECLARE_BACKGROUND_TEST(TestUnlink);
+  DECLARE_BACKGROUND_TEST(TestUnlinkFail);
+
+ protected:
+  static const PP_Resource kFileRefResource = 74;
+  static const PP_Resource kFileRefResource2 = 75;
+  static const PP_Resource kFileIOResource = 76;
+
+  PepperFileTest()
+    : default_executor_(&bg_, PP_OK),
+      ppb_file_io_(NULL),
+      ppb_file_io_private_(NULL),
+      ppb_file_ref_(NULL) {
+  }
+  virtual void SetUp() OVERRIDE;
+
+  void SetUpOpenExpectations(
+      const char* path,
+      int native_flags,
+      CompletionCallbackExecutor* open_callback_executor,
+      CompletionCallbackExecutor* request_handle__callback_executor,
+      CompletionCallbackExecutor* query_callback_executor,
+      const PP_FileInfo& file_info);
+  void SetUpStatExpectations(
+      CompletionCallbackExecutor* callback_executor,
+      const PP_FileInfo& file_info);
+  void SetUpFtruncateExpectations(
+      CompletionCallbackExecutor* callback_executor,
+      off64_t length);
+  void SetUpMkdirExpectations(const char* path,
+                              CompletionCallbackExecutor* callback_executor);
+  void SetUpRenameExpectations(const char* oldpath,
+                               const char* newpath,
+                               CompletionCallbackExecutor* callback_executor);
+  void SetUpUnlinkExpectations(const char* path,
+                               CompletionCallbackExecutor* callback_executor);
+  void SetUpUTimeExpectations(const char* path,
+                              time_t time,
+                              CompletionCallbackExecutor* callback_executor);
+  scoped_refptr<FileStream> OpenFile(int oflag);
+  scoped_refptr<FileStream> OpenFileWithExpectations(int flags);
+  bool IsDirectory(scoped_refptr<FileStream> file);
+  void CheckStatStructure(const struct stat& st,
+                          mode_t mode,
+                          nlink_t link,
+                          off64_t size,
+                          ino_t inode,
+                          time_t ctime,
+                          time_t atime,
+                          time_t mtime);
+  void DisableCache();
+
+  static int ConvertNativeOpenFlagsToPepper(int native_flags) {
+    return PepperFileHandler::ConvertNativeOpenFlagsToPepper(native_flags);
+  }
+
+  static int ConvertPepperErrorToErrno(int pp_error) {
+    return PepperFileHandler::ConvertPepperErrorToErrno(pp_error);
+  }
+
+  static const char kPepperPath[];
+  static const char kAnotherPepperPath[];
+  static const time_t kTime;
+  CompletionCallbackExecutor default_executor_;
+  NiceMock<PPB_FileIO_Mock>* ppb_file_io_;
+  NiceMock<PPB_FileIO_Private_Mock>* ppb_file_io_private_;
+  NiceMock<PPB_FileRef_Mock>* ppb_file_ref_;
+  scoped_ptr<PepperFileHandler> handler_;
+};
+
+#define EXPECT_ERROR(result, expected_error)     \
+  EXPECT_EQ(-1, result);                         \
+  EXPECT_EQ(expected_error, errno);              \
+  errno = 0;
+
+const char PepperFileTest::kPepperPath[] = "/pepperfs.file";
+const char PepperFileTest::kAnotherPepperPath[] = "/another.pepperfs.file";
+const time_t PepperFileTest::kTime = 1355707320;
+
+void PepperFileTest::SetUp() {
+  FileSystemBackgroundTestCommon<PepperFileTest>::SetUp();
+  factory_.GetMock(&ppb_file_io_);
+  factory_.GetMock(&ppb_file_io_private_);
+  factory_.GetMock(&ppb_file_ref_);
+  SetUpPepperFileSystemConstructExpectations(kInstanceNumber);
+  handler_.reset(new PepperFileHandler);
+  handler_->OpenPepperFileSystem(instance_.get());
+  RunCompletionCallbacks();
+}
+
+void PepperFileTest::SetUpOpenExpectations(
+    const char* path,
+    int native_flags,
+    CompletionCallbackExecutor* open_callback_executor,
+    CompletionCallbackExecutor* request_handle_callback_executor,
+    CompletionCallbackExecutor* query_callback_executor,
+    const PP_FileInfo& file_info) {
+  const int pepper_flags = ConvertNativeOpenFlagsToPepper(native_flags);
+
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(path))).
+    WillOnce(Return(kFileRefResource));
+
+  EXPECT_CALL(*ppb_file_io_, Create(kInstanceNumber)).
+    WillOnce(Return(kFileIOResource));
+  // Note that kFileIOResource is not released until close() is called.
+  EXPECT_CALL(*ppb_file_io_, Open(kFileIOResource,
+                                  kFileRefResource,
+                                  pepper_flags,
+                                  _)).
+    WillOnce(WithArgs<3>(
+        Invoke(open_callback_executor,
+               &CompletionCallbackExecutor::ExecuteOnMainThread)));
+  if (open_callback_executor->final_result() == PP_OK) {
+    static const PP_FileHandle kDummyNativeHandle = 100;
+    EXPECT_CALL(*ppb_file_io_private_,
+                RequestOSFileHandle(kFileIOResource, _, _)).
+        WillOnce(DoAll(
+          SetArgPointee<1>(kDummyNativeHandle),
+          WithArgs<2>(Invoke(
+              request_handle_callback_executor,
+              &CompletionCallbackExecutor::ExecuteOnMainThread)))).
+        RetiresOnSaturation();
+  }
+}
+
+void PepperFileTest::SetUpStatExpectations(
+    CompletionCallbackExecutor* callback_executor,
+    const PP_FileInfo& file_info) {
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, _)).
+    WillOnce(Return(kFileRefResource));
+  EXPECT_CALL(*ppb_file_ref_, Query(kFileRefResource,
+                                    _,
+                                    _)).
+      WillOnce(DoAll(
+          SetArgPointee<1>(file_info),
+          WithArgs<2>(Invoke(
+              callback_executor,
+              &CompletionCallbackExecutor::ExecuteOnMainThread)))).
+      RetiresOnSaturation();
+}
+
+void PepperFileTest::SetUpFtruncateExpectations(
+    CompletionCallbackExecutor* callback_executor,
+    off64_t length) {
+  EXPECT_CALL(*ppb_file_io_, SetLength(kFileIOResource,
+                                       length,
+                                       _)).
+    WillOnce(WithArgs<2>(Invoke(
+        callback_executor,
+        &CompletionCallbackExecutor::ExecuteOnMainThread))).
+    RetiresOnSaturation();
+}
+
+void PepperFileTest::SetUpMkdirExpectations(
+    const char* path, CompletionCallbackExecutor* callback_executor) {
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(path))).
+    WillOnce(Return(kFileRefResource));
+  EXPECT_CALL(*ppb_file_ref_,
+              MakeDirectory(kFileRefResource,
+                            PP_MAKEDIRECTORYFLAG_EXCLUSIVE,
+                            _)).
+    WillOnce(WithArgs<2>(
+        Invoke(callback_executor,
+               &CompletionCallbackExecutor::ExecuteOnMainThread))).
+    RetiresOnSaturation();
+}
+
+void PepperFileTest::SetUpRenameExpectations(
+    const char* oldpath,
+    const char* newpath,
+    CompletionCallbackExecutor* callback_executor) {
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(oldpath))).
+    WillOnce(Return(kFileRefResource));
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(newpath))).
+    WillOnce(Return(kFileRefResource2));
+  EXPECT_CALL(*ppb_file_ref_,
+              Rename(kFileRefResource, kFileRefResource2, _)).
+    WillOnce(WithArgs<2>(
+        Invoke(callback_executor,
+               &CompletionCallbackExecutor::ExecuteOnMainThread))).
+    RetiresOnSaturation();
+}
+
+void PepperFileTest::SetUpUnlinkExpectations(
+    const char* path, CompletionCallbackExecutor* callback_executor) {
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(path))).
+    WillOnce(Return(kFileRefResource));
+  EXPECT_CALL(*ppb_file_ref_, Delete(kFileRefResource, _)).
+    WillOnce(WithArgs<1>(
+        Invoke(callback_executor,
+               &CompletionCallbackExecutor::ExecuteOnMainThread))).
+    RetiresOnSaturation();
+}
+
+void PepperFileTest::SetUpUTimeExpectations(
+    const char* path,
+    time_t time,
+    CompletionCallbackExecutor* callback_executor) {
+  EXPECT_CALL(*ppb_file_ref_, Create(kFileSystemResource, StrEq(path))).
+    WillOnce(Return(kFileRefResource));
+  EXPECT_CALL(*ppb_file_ref_, Touch(kFileRefResource, time, time, _)).
+    WillOnce(WithArgs<3>(
+        Invoke(callback_executor,
+               &CompletionCallbackExecutor::ExecuteOnMainThread))).
+    RetiresOnSaturation();
+}
+
+scoped_refptr<FileStream> PepperFileTest::OpenFile(int oflag) {
+  int fd = file_system_->fd_to_stream_->GetFirstUnusedDescriptor();
+  return handler_->open(fd, kPepperPath, oflag, 0);
+}
+
+scoped_refptr<FileStream> PepperFileTest::OpenFileWithExpectations(
+    int open_flags) {
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, open_flags,
+                        &default_executor_, &default_executor_,
+                        &default_executor_, file_info);
+  return OpenFile(open_flags);
+}
+
+bool PepperFileTest::IsDirectory(scoped_refptr<FileStream> file) {
+  if (!file) {
+    ADD_FAILURE() << "No file stream";
+    return false;
+  }
+  return strcmp(file->GetStreamType(), "pepper") != 0;
+}
+
+void PepperFileTest::CheckStatStructure(const struct stat& st,
+                                        mode_t mode,
+                                        nlink_t link,
+                                        off64_t size,
+                                        ino_t inode,
+                                        time_t ctime,
+                                        time_t atime,
+                                        time_t mtime) {
+  EXPECT_EQ(static_cast<dev_t>(0), st.st_dev);
+  EXPECT_EQ(inode, st.st_ino);
+  // PepperFile does not set permission bits, relying VirtualFileSystem.
+  EXPECT_EQ(mode, st.st_mode);
+  EXPECT_EQ(link, st.st_nlink);
+  // UID and GID must not be set in FileSystemHandler.
+  EXPECT_EQ(arc::kRootUid, st.st_uid);
+  EXPECT_EQ(arc::kRootGid, st.st_gid);
+  EXPECT_EQ(static_cast<dev_t>(0), st.st_rdev);
+  EXPECT_EQ(size, st.st_size);
+  EXPECT_EQ(static_cast<blksize_t>(4096), st.st_blksize);
+  EXPECT_EQ(static_cast<blkcnt_t>(0), st.st_blocks);
+  // We casts st_[cam]time for bionic. In bionic, their type is
+  // unsigned long and time_t is long.
+  EXPECT_EQ(ctime, static_cast<time_t>(st.st_ctime));
+  EXPECT_EQ(atime, static_cast<time_t>(st.st_atime));
+  EXPECT_EQ(mtime, static_cast<time_t>(st.st_mtime));
+}
+
+void PepperFileTest::DisableCache() {
+  handler_->DisableCacheForTesting();
+}
+
+TEST_F(PepperFileTest, ConstructPendingDestruct) {
+  // Just tests that the initialization that runs in SetUp() itself
+  // succeeds.
+}
+
+TEST_F(PepperFileTest, TestConvertNativeOpenFlagsToPepper) {
+  EXPECT_EQ(PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_WRONLY));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ,
+            ConvertNativeOpenFlagsToPepper(O_RDONLY));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR));
+  // Unknown flag should be treated as O_RDONLY.
+  EXPECT_EQ(PP_FILEOPENFLAG_READ,
+            ConvertNativeOpenFlagsToPepper(O_ACCMODE));
+  // _WRITE and _APPEND flags are exclusive in Pepper.
+  EXPECT_EQ(PP_FILEOPENFLAG_APPEND,
+            ConvertNativeOpenFlagsToPepper(O_WRONLY | O_APPEND));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_APPEND,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_APPEND));
+  // O_RDONLY | O_APPEND is an error. O_APPEND should be ignored.
+  EXPECT_EQ(PP_FILEOPENFLAG_READ,
+            ConvertNativeOpenFlagsToPepper(O_RDONLY | O_APPEND));
+  // Test misc flags.
+  EXPECT_EQ(PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE,
+            ConvertNativeOpenFlagsToPepper(O_WRONLY | O_CREAT));
+  EXPECT_EQ(PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE |
+            PP_FILEOPENFLAG_EXCLUSIVE,
+            ConvertNativeOpenFlagsToPepper(O_WRONLY | O_CREAT | O_EXCL));
+  EXPECT_EQ(PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_TRUNCATE,
+            ConvertNativeOpenFlagsToPepper(O_WRONLY | O_TRUNC));
+  // Test unsupported flags.
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_NOCTTY));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_NONBLOCK));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_SYNC));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_ASYNC));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_NOFOLLOW));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_CLOEXEC));
+  EXPECT_EQ(PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE,
+            ConvertNativeOpenFlagsToPepper(O_RDWR | O_NOATIME));
+}
+
+TEST_F(PepperFileTest, TestConvertPepperErrorToErrno) {
+  EXPECT_EQ(ENOENT, ConvertPepperErrorToErrno(PP_ERROR_FILENOTFOUND));
+  EXPECT_EQ(EEXIST, ConvertPepperErrorToErrno(PP_ERROR_FILEEXISTS));
+  EXPECT_EQ(EPERM, ConvertPepperErrorToErrno(PP_ERROR_NOACCESS));
+  EXPECT_EQ(ENOMEM, ConvertPepperErrorToErrno(PP_ERROR_NOMEMORY));
+  EXPECT_EQ(ENOSPC, ConvertPepperErrorToErrno(PP_ERROR_NOQUOTA));
+  EXPECT_EQ(ENOSPC, ConvertPepperErrorToErrno(PP_ERROR_NOSPACE));
+  EXPECT_EQ(EISDIR, ConvertPepperErrorToErrno(PP_ERROR_NOTAFILE));
+  // We use ENOENT for all other error code.
+  EXPECT_EQ(ENOENT, ConvertPepperErrorToErrno(PP_ERROR_FAILED));
+  EXPECT_EQ(ENOENT, ConvertPepperErrorToErrno(PP_ERROR_USERCANCEL));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenRead) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDONLY));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenCreat) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDWR | O_CREAT));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenCreatExclusive) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(
+      OpenFileWithExpectations(O_RDWR | O_CREAT | O_EXCL));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenCreatTruncate) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(
+      OpenFileWithExpectations(O_RDWR | O_CREAT | O_TRUNC));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenCreatWriteOnly) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(
+      OpenFileWithExpectations(O_WRONLY | O_CREAT));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenAppend) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(
+      OpenFileWithExpectations(O_RDWR | O_APPEND));
+  EXPECT_TRUE(file.get());
+  EXPECT_FALSE(IsDirectory(file));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenWithOpenDirectoryFlag) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  const int flags = O_RDONLY | O_DIRECTORY;
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &default_executor_, &default_executor_,
+                        &default_executor_, file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_FALSE(file.get());
+  EXPECT_EQ(ENOTDIR, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenDirectory) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_NOTAFILE);
+  PP_FileInfo file_info = {};
+  int flags = O_RDONLY;
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_TRUE(file.get());
+  EXPECT_TRUE(IsDirectory(file));
+
+  flags = O_RDONLY | O_DIRECTORY;
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file2(OpenFile(flags));
+  EXPECT_TRUE(file2.get());
+  EXPECT_TRUE(IsDirectory(file2));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenDirectoryWithWriteAccess) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_NOTAFILE);
+  PP_FileInfo file_info = {};
+  int flags = O_RDWR;
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_FALSE(file.get());
+  EXPECT_EQ(EISDIR, errno);
+
+  flags = O_WRONLY;
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file2(OpenFile(flags));
+  EXPECT_FALSE(file2.get());
+  EXPECT_EQ(EISDIR, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenNoentFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);
+  int flags = O_RDONLY;
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_FALSE(file.get());
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenPermFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_NOACCESS);
+  int flags = O_RDWR | O_CREAT;
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file2(OpenFile(flags));
+  EXPECT_FALSE(file2.get());
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenExclusiveFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILEEXISTS);
+  int flags = O_WRONLY | O_CREAT | O_EXCL;
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_FALSE(file.get());
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRequestHandleFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor request_handle_executor(&bg_, PP_ERROR_NOACCESS);
+  int flags = O_WRONLY | O_CREAT;
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, flags,
+                        &default_executor_, &request_handle_executor,
+                        &default_executor_, file_info);
+  scoped_refptr<FileStream> file(OpenFile(flags));
+  EXPECT_FALSE(file.get());
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestOpenClose) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDWR | O_CREAT));
+  EXPECT_TRUE(file.get());
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestFstat) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDONLY));
+  EXPECT_TRUE(file.get());
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  // Call fstat just to make sure it does not crash.
+  // Since fstat() is implemented by __read_fstat,
+  // it returns -1.
+  EXPECT_EQ(-1, file->fstat(&st));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStat) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  const off64_t kSize = 0xdeadbeef;
+  file_info.size = kSize;
+  file_info.type = PP_FILETYPE_REGULAR;
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  CheckStatStructure(st, S_IFREG, 1, kSize, GetInode(kPepperPath), 0, 0, 0);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatDirectory) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  file_info.type = PP_FILETYPE_DIRECTORY;
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  CheckStatStructure(st, S_IFDIR, 32, 4096, GetInode(kPepperPath), 0, 0, 0);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestMkdir) {
+  base::AutoLock lock(file_system_->mutex());
+  const mode_t mode = 0777;
+  SetUpMkdirExpectations(kPepperPath, &default_executor_);
+  EXPECT_EQ(0, handler_->mkdir(kPepperPath, mode));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestMkdirFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FAILED);
+  const mode_t mode = 0777;
+  SetUpMkdirExpectations(kPepperPath, &executor);
+  EXPECT_EQ(-1, handler_->mkdir(kPepperPath, mode));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestMkdirNoPermission) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_NOACCESS);
+  const mode_t mode = 0777;
+  SetUpMkdirExpectations(kPepperPath, &executor);
+  EXPECT_EQ(-1, handler_->mkdir(kPepperPath, mode));
+  EXPECT_EQ(EPERM, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRename) {
+  base::AutoLock lock(file_system_->mutex());
+  SetUpRenameExpectations(kPepperPath, kAnotherPepperPath, &default_executor_);
+  EXPECT_EQ(0, handler_->rename(kPepperPath, kAnotherPepperPath));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRenameInode) {
+  base::AutoLock lock(file_system_->mutex());
+  static const ino_t zero_ino = 0;
+  PP_FileInfo file_info = {};
+  file_info.size = 0xdeadbeef;
+  file_info.type = PP_FILETYPE_REGULAR;
+
+  // Assign inode for |kPepperPath| by calling stat().
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  const ino_t orig_ino = st.st_ino;
+  EXPECT_NE(zero_ino, orig_ino);
+  // Call rename().
+  SetUpRenameExpectations(kPepperPath, kAnotherPepperPath, &default_executor_);
+  EXPECT_EQ(0, handler_->rename(kPepperPath, kAnotherPepperPath));
+  // Call stat() against |kAnotherPepperPath| to confirm st_ino is the same.
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kAnotherPepperPath, &st));
+  EXPECT_EQ(orig_ino, st.st_ino);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRenameInode2) {
+  base::AutoLock lock(file_system_->mutex());
+  static const ino_t zero_ino = 0;
+  PP_FileInfo file_info = {};
+  file_info.size = 0xdeadbeef;
+  file_info.type = PP_FILETYPE_REGULAR;
+
+  // Assign inode for |kAnotherPepperPath| by calling stat().
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kAnotherPepperPath, &st));
+  const ino_t orig_ino = st.st_ino;
+  EXPECT_NE(zero_ino, orig_ino);
+  // Call rename().
+  SetUpRenameExpectations(kPepperPath, kAnotherPepperPath, &default_executor_);
+  EXPECT_EQ(0, handler_->rename(kPepperPath, kAnotherPepperPath));
+  // Call stat() against |kAnotherPepperPath| again to confirm st_ino is NOT
+  // the same.
+  SetUpStatExpectations(&default_executor_, file_info);
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, handler_->stat(kAnotherPepperPath, &st));
+  EXPECT_NE(orig_ino, st.st_ino);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRenameEnoentFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);
+  SetUpRenameExpectations(kPepperPath, kAnotherPepperPath, &executor);
+  EXPECT_EQ(-1, handler_->rename(kPepperPath, kAnotherPepperPath));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestRenameEisdirFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_NOTAFILE);
+  SetUpRenameExpectations(kPepperPath, kAnotherPepperPath, &executor);
+  EXPECT_EQ(-1, handler_->rename(kPepperPath, kAnotherPepperPath));
+  EXPECT_EQ(EISDIR, errno);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestUnlink) {
+  base::AutoLock lock(file_system_->mutex());
+  ino_t inode = GetInode(kPepperPath);
+  SetUpUnlinkExpectations(kPepperPath, &default_executor_);
+  EXPECT_EQ(0, handler_->unlink(kPepperPath));
+  EXPECT_NE(inode, GetInode(kPepperPath));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestUnlinkFail) {
+  base::AutoLock lock(file_system_->mutex());
+  {
+    CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);
+    SetUpUnlinkExpectations(kPepperPath, &executor);
+    EXPECT_ERROR(handler_->unlink(kPepperPath), ENOENT);
+  }
+  {
+    // If you try to delete a non-empty directory, the API returns with
+    // PP_ERROR_FAILED.
+    CompletionCallbackExecutor executor(&bg_, PP_ERROR_FAILED);
+    SetUpUnlinkExpectations(kPepperPath, &executor);
+    EXPECT_ERROR(handler_->unlink(kPepperPath), EISDIR);
+  }
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestUTime) {
+  base::AutoLock lock(file_system_->mutex());
+  SetUpUTimeExpectations(kPepperPath, kTime, &default_executor_);
+  {
+    struct timeval times[2];
+    times[0].tv_sec = kTime;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = kTime;
+    times[1].tv_usec = 0;
+    EXPECT_EQ(0, handler_->utimes(kPepperPath, times));
+  }
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestUTimeFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);
+  SetUpUTimeExpectations(kPepperPath, kTime, &executor);
+  {
+    struct timeval times[2];
+    times[0].tv_sec = kTime;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = kTime;
+    times[1].tv_usec = 0;
+    EXPECT_ERROR(handler_->utimes(kPepperPath, times), ENOENT);
+  }
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatCache) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  SetUpStatExpectations(&default_executor_, file_info);
+  // StatExpectations expect the underlying calls that stat call to be
+  // called exactly once.
+  struct stat st;
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatCacheDisabled) {
+  base::AutoLock lock(file_system_->mutex());
+  DisableCache();
+  PP_FileInfo file_info = {};
+  struct stat st;
+  // Confirm Pepper's stat() is called twice when the cache is disabled.
+  SetUpStatExpectations(&default_executor_, file_info);
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  SetUpStatExpectations(&default_executor_, file_info);
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatCacheWithTrailingSlash) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  EXPECT_EQ(0, handler_->stat("/dir", &st));
+  // Check if pepper_file.cc automatically remove the trailing / when
+  // accessing the cache.
+  EXPECT_EQ(0, handler_->stat("/dir/", &st));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatCacheInvalidation) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  SetUpStatExpectations(&default_executor_, file_info);
+  struct stat st;
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  // Now call utimes to invalidate the cache.
+  SetUpUTimeExpectations(kPepperPath, kTime, &default_executor_);
+  {
+    struct timeval times[2];
+    times[0].tv_sec = kTime;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = kTime;
+    times[1].tv_usec = 0;
+    EXPECT_EQ(0, handler_->utimes(kPepperPath, times));
+  }
+  SetUpStatExpectations(&default_executor_, file_info);
+  // StatExpectations expect the underlying calls that stat call to be
+  // called exactly once.
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+  EXPECT_EQ(0, handler_->stat(kPepperPath, &st));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestStatWithENOENT) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);  // ENOENT
+  PP_FileInfo file_info = {};
+  SetUpStatExpectations(&executor, file_info);
+  struct stat st;
+  EXPECT_EQ(-1, handler_->stat(kPepperPath, &st));
+  EXPECT_EQ(ENOENT, errno);
+
+  // The following stat, open, rename, truncate, unlink, and utimes
+  // calls should not call into Pepper since the initial stat call above
+  // returned ENOENT.
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->stat(kPepperPath, &st));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(NULL, handler_->open(-1, kPepperPath, O_RDONLY, 0).get());
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(NULL, handler_->open(-1, kPepperPath, O_WRONLY, 0).get());
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(NULL, handler_->open(-1, kPepperPath, O_RDWR, 0).get());
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->rename(kPepperPath, "/abc"));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->truncate(kPepperPath, 0));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->unlink(kPepperPath));
+  EXPECT_EQ(ENOENT, errno);
+  {
+    struct timeval times[2];
+    times[0].tv_sec = kTime;
+    times[0].tv_usec = 0;
+    times[1].tv_sec = kTime;
+    times[1].tv_usec = 0;
+    EXPECT_EQ(-1, handler_->utimes(kPepperPath, times));
+    EXPECT_EQ(ENOENT, errno);
+  }
+
+  // However, open() with O_CREAT should ignore the cache.
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_WRONLY | O_CREAT));
+  EXPECT_TRUE(file.get());
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestTruncate) {
+  base::AutoLock lock(file_system_->mutex());
+  PP_FileInfo file_info = {};
+  // Truncate  is just an open, ftruncate, and close.
+  SetUpOpenExpectations(kPepperPath, O_WRONLY,
+                        &default_executor_, &default_executor_,
+                        &default_executor_, file_info);
+  off64_t length = 0;
+  SetUpFtruncateExpectations(&default_executor_, length);
+  EXPECT_EQ(0, handler_->truncate(kPepperPath, length));
+
+  // Do the same with non-zero |length|.
+  SetUpOpenExpectations(kPepperPath, O_WRONLY,
+                        &default_executor_, &default_executor_,
+                        &default_executor_, file_info);
+  length = 12345;
+  SetUpFtruncateExpectations(&default_executor_, length);
+  EXPECT_EQ(0, handler_->truncate(kPepperPath, length));
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestTruncateFail) {
+  base::AutoLock lock(file_system_->mutex());
+  CompletionCallbackExecutor executor(&bg_, PP_ERROR_FILENOTFOUND);
+  PP_FileInfo file_info = {};
+  SetUpOpenExpectations(kPepperPath, O_WRONLY,
+                        &executor, &default_executor_, &default_executor_,
+                        file_info);
+  EXPECT_ERROR(handler_->truncate(kPepperPath, 0), ENOENT);
+
+  CompletionCallbackExecutor executor2(&bg_, PP_ERROR_NOTAFILE);
+  SetUpOpenExpectations(kPepperPath, O_WRONLY,
+                        &executor2, &default_executor_, &default_executor_,
+                        file_info);
+  EXPECT_ERROR(handler_->truncate(kPepperPath, 0), EISDIR);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestFtruncateReadonly) {
+  base::AutoLock lock(file_system_->mutex());
+  // Can not call truncate() against a read-only fd.
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDONLY));
+  EXPECT_TRUE(file.get());
+  EXPECT_ERROR(file->ftruncate(0), EBADF);
+}
+
+TEST_BACKGROUND_F(PepperFileTest, TestPacketCalls) {
+  base::AutoLock lock(file_system_->mutex());
+  scoped_refptr<FileStream> file(OpenFileWithExpectations(O_RDWR | O_CREAT));
+
+  char buf[1];
+  EXPECT_ERROR(file->recv(buf, 1, 0), ENOTSOCK);
+  EXPECT_ERROR(file->recvfrom(buf, 1, 0, NULL, NULL), ENOTSOCK);
+  EXPECT_ERROR(file->send(buf, 1, 0), ENOTSOCK);
+  EXPECT_ERROR(file->sendto(buf, 1, 0, NULL, 0), ENOTSOCK);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/permission_info.cc b/src/posix_translation/permission_info.cc
new file mode 100644
index 0000000..fbd9da6
--- /dev/null
+++ b/src/posix_translation/permission_info.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 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 "posix_translation/permission_info.h"
+
+namespace posix_translation {
+
+const uid_t PermissionInfo::kInvalidUid = static_cast<uid_t>(-1);
+
+PermissionInfo::PermissionInfo()
+    : file_uid_(kInvalidUid),
+      is_writable_(false) {
+}
+
+PermissionInfo::PermissionInfo(uid_t file_uid, bool is_writable)
+    : file_uid_(file_uid),
+      is_writable_(is_writable) {
+}
+
+PermissionInfo::~PermissionInfo() {
+}
+
+bool PermissionInfo::IsValid() const {
+  return file_uid_ != kInvalidUid;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/permission_info.h b/src/posix_translation/permission_info.h
new file mode 100644
index 0000000..e95e2ca
--- /dev/null
+++ b/src/posix_translation/permission_info.h
@@ -0,0 +1,39 @@
+// Copyright 2014 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 POSIX_TRANSLATION_PERMISSION_INFO_H_
+#define POSIX_TRANSLATION_PERMISSION_INFO_H_
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common/export.h"
+
+namespace posix_translation {
+
+// Maintains info necessary to implement permissions. As our
+// permission implementation is limited because we do not run multiple
+// applications at once, this class does not have much information.
+class ARC_EXPORT PermissionInfo {
+ public:
+  PermissionInfo();
+  PermissionInfo(uid_t file_uid, bool is_writable);
+  ~PermissionInfo();
+
+  bool IsValid() const;
+
+  uid_t file_uid() const { return file_uid_; }
+  bool is_writable() const { return is_writable_; }
+
+ private:
+  friend class PermissionInfoTest;
+
+  uid_t file_uid_;
+  bool is_writable_;
+  static const uid_t kInvalidUid;
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_PERMISSION_INFO_H_
diff --git a/src/posix_translation/permission_info_test.cc b/src/posix_translation/permission_info_test.cc
new file mode 100644
index 0000000..3741209
--- /dev/null
+++ b/src/posix_translation/permission_info_test.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2014 The Chromium OS 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 "gtest/gtest.h"
+#include "posix_translation/permission_info.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+class PermissionInfoTest : public FileSystemTestCommon {
+ protected:
+  static uid_t GetInvalidUid() {
+    return PermissionInfo::kInvalidUid;
+  }
+};
+
+TEST_F(PermissionInfoTest, TestDefaultConstructor) {
+  PermissionInfo info;
+  EXPECT_EQ(GetInvalidUid(), info.file_uid());
+  EXPECT_FALSE(info.IsValid());
+  EXPECT_FALSE(info.is_writable());
+}
+
+TEST_F(PermissionInfoTest, TestConstructor) {
+  static const uid_t kMyUid = 12345;
+  PermissionInfo info(kMyUid, true /* writable */);
+  EXPECT_EQ(kMyUid, info.file_uid());
+  EXPECT_TRUE(info.IsValid());
+  EXPECT_TRUE(info.is_writable());
+  PermissionInfo info2(kMyUid, false /* not writable */);
+  EXPECT_EQ(kMyUid, info2.file_uid());
+  EXPECT_TRUE(info2.IsValid());
+  EXPECT_FALSE(info2.is_writable());
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/proc/cmdline b/src/posix_translation/proc/cmdline
new file mode 100644
index 0000000..3f1de4a
--- /dev/null
+++ b/src/posix_translation/proc/cmdline
@@ -0,0 +1 @@
+qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=3
diff --git a/src/posix_translation/proc/loadavg b/src/posix_translation/proc/loadavg
new file mode 100644
index 0000000..53d9e7c
--- /dev/null
+++ b/src/posix_translation/proc/loadavg
@@ -0,0 +1 @@
+0.00 0.00 0.00 1/279 22477
diff --git a/src/posix_translation/proc/meminfo b/src/posix_translation/proc/meminfo
new file mode 100644
index 0000000..0c3bf2a
--- /dev/null
+++ b/src/posix_translation/proc/meminfo
@@ -0,0 +1,31 @@
+MemTotal:         516144 kB
+MemFree:          320060 kB
+Buffers:               0 kB
+Cached:           112408 kB
+SwapCached:            0 kB
+Active:           102812 kB
+Inactive:          78376 kB
+Active(anon):      82308 kB
+Inactive(anon):        0 kB
+Active(file):      20504 kB
+Inactive(file):    78376 kB
+Unevictable:           0 kB
+Mlocked:               0 kB
+SwapTotal:             0 kB
+SwapFree:              0 kB
+Dirty:                 0 kB
+Writeback:             0 kB
+AnonPages:         68788 kB
+Mapped:            55208 kB
+Slab:               6148 kB
+SReclaimable:       2336 kB
+SUnreclaim:         3812 kB
+PageTables:         2568 kB
+NFS_Unstable:          0 kB
+Bounce:                0 kB
+WritebackTmp:          0 kB
+CommitLimit:      258072 kB
+Committed_AS:     921816 kB
+VmallocTotal:     450560 kB
+VmallocUsed:       14468 kB
+VmallocChunk:     417796 kB
diff --git a/src/posix_translation/proc/net/tcp b/src/posix_translation/proc/net/tcp
new file mode 100644
index 0000000..ba25c06
--- /dev/null
+++ b/src/posix_translation/proc/net/tcp
@@ -0,0 +1,4 @@
+  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
+   0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 350 1 dfd77900 300 0 0 2 -1                               
+   1: 00000000:15B3 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 370 1 dfd774e0 300 0 0 2 -1                               
+   2: 0F02000A:15B3 0202000A:E99D 01 00000018:00000000 01:00000014 00000000     0        0 371 3 dfd76040 21 5 17 6 -1                               
diff --git a/src/posix_translation/proc/net/tcp6 b/src/posix_translation/proc/net/tcp6
new file mode 100644
index 0000000..7449419
--- /dev/null
+++ b/src/posix_translation/proc/net/tcp6
@@ -0,0 +1,2 @@
+  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+   0: 0000000000000000FFFF00000F02000A:91AB 0000000000000000FFFF0000A1EB7D4A:0050 08 00000000:00000001 00:00000000 00000000  1000        0 1653 1 d49f1760 21 4 6 3 -1
diff --git a/src/posix_translation/proc/net/udp b/src/posix_translation/proc/net/udp
new file mode 100644
index 0000000..8d846f7
--- /dev/null
+++ b/src/posix_translation/proc/net/udp
@@ -0,0 +1 @@
+  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops             
diff --git a/src/posix_translation/proc/net/udp6 b/src/posix_translation/proc/net/udp6
new file mode 100644
index 0000000..e591cfd
--- /dev/null
+++ b/src/posix_translation/proc/net/udp6
@@ -0,0 +1 @@
+  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops
diff --git a/src/posix_translation/proc/self/auxv b/src/posix_translation/proc/self/auxv
new file mode 100644
index 0000000..fb02cff
--- /dev/null
+++ b/src/posix_translation/proc/self/auxv
Binary files differ
diff --git a/src/posix_translation/proc/self/cmdline b/src/posix_translation/proc/self/cmdline
new file mode 100644
index 0000000..e3ffe06
--- /dev/null
+++ b/src/posix_translation/proc/self/cmdline
Binary files differ
diff --git a/src/posix_translation/proc/self/maps b/src/posix_translation/proc/self/maps
new file mode 100644
index 0000000..95a19c4
--- /dev/null
+++ b/src/posix_translation/proc/self/maps
@@ -0,0 +1,16 @@
+00008000-0002e000 r-xp 00000000 00:01 26         /sbin/adbd
+0002f000-00031000 rw-p 00026000 00:01 26         /sbin/adbd
+00031000-0004c000 rw-p 00031000 00:00 0          [heap]
+40000000-40008000 r--s 00000000 00:0a 47         /dev/__properties__ (deleted)
+40008000-40009000 r--p 40008000 00:00 0 
+40009000-4000a000 ---p 40009000 00:00 0 
+4000a000-40109000 rw-p 4000a000 00:00 0 
+40109000-4010a000 ---p 40109000 00:00 0 
+4010a000-40209000 rw-p 4010a000 00:00 0 
+40209000-4020a000 ---p 40209000 00:00 0 
+4020a000-40309000 rw-p 4020a000 00:00 0 
+40309000-4030a000 ---p 40309000 00:00 0 
+4030a000-40409000 rw-p 4030a000 00:00 0 
+40409000-4040a000 ---p 40409000 00:00 0 
+4040a000-40509000 rw-p 4040a000 00:00 0 
+bec72000-bec87000 rw-p befeb000 00:00 0          [stack]
diff --git a/src/posix_translation/proc/stat b/src/posix_translation/proc/stat
new file mode 100644
index 0000000..2f98f69
--- /dev/null
+++ b/src/posix_translation/proc/stat
@@ -0,0 +1,8 @@
+cpu  508576 11171 271926 60110351 0 172 440 0 0
+cpu0 508576 11171 271926 60110351 0 172 440 0 0
+intr 5026536 0 1 0 4203931 0 0 0 0 0 0 0 0 0 628 30362 1 0 3 791609 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ctxt 14286560
+btime 1372829766
+processes 151520
+procs_running 1
+procs_blocked 0
diff --git a/src/posix_translation/proc/version b/src/posix_translation/proc/version
new file mode 100644
index 0000000..75bbedd
--- /dev/null
+++ b/src/posix_translation/proc/version
@@ -0,0 +1 @@
+Linux version 2.6.29-gea477bb (root@localhost) (gcc version 4.6.x-google 20120106 (prerelease) (GCC) ) #1 Wed Sep 26 11:04:45 PDT 2012
diff --git a/src/posix_translation/process_environment.h b/src/posix_translation/process_environment.h
new file mode 100644
index 0000000..92f7864
--- /dev/null
+++ b/src/posix_translation/process_environment.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2014 The Chromium OS 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 POSIX_TRANSLATION_PROCESS_ENVIRONMENT_H_
+#define POSIX_TRANSLATION_PROCESS_ENVIRONMENT_H_
+
+#include <string>
+
+namespace posix_translation {
+
+// Interface to process specific logic.  Some of posix function is process
+// dependent, such as current working directory and pid.  All posix_translation
+// instance that cares about process should implement its ProcessEnvironment.
+//
+// TODO(crbug.com/346785): Move more ARC specific code out of
+// posix_translation, such as arc::ProcessEmulator::GetPid.
+class ProcessEnvironment {
+ public:
+  virtual ~ProcessEnvironment() {}
+  virtual std::string GetCurrentDirectory() const = 0;
+  virtual void SetCurrentDirectory(const std::string& dir) = 0;
+  virtual mode_t GetCurrentUmask() const = 0;
+  virtual void SetCurrentUmask(mode_t mask) = 0;
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_PROCESS_ENVIRONMENT_H_
diff --git a/src/posix_translation/readonly_file.cc b/src/posix_translation/readonly_file.cc
new file mode 100644
index 0000000..8ca6141
--- /dev/null
+++ b/src/posix_translation/readonly_file.cc
@@ -0,0 +1,448 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/readonly_file.h"
+
+#include <arpa/inet.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/lock.h"
+#include "common/arc_strace.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/nacl_manifest_file.h"
+#include "posix_translation/statfs.h"
+
+namespace posix_translation {
+
+ReadonlyFileHandler::ReadonlyFileHandler(const std::string& image_filename,
+                                         size_t read_ahead_size,
+                                         FileSystemHandler* underlying_handler)
+    : FileSystemHandler("ReadonlyFileHandler"),
+      image_filename_(image_filename),
+      read_ahead_size_(read_ahead_size),
+      underlying_handler_(underlying_handler),
+      image_stream_(NULL) {
+  if (!underlying_handler)
+    ALOGW("NULL underlying handler is passed");  // this is okay for unit tests
+  ALOG_ASSERT(read_ahead_size_ > 0);
+}
+
+ReadonlyFileHandler::~ReadonlyFileHandler() {
+  // Destructing |image_stream_| without holding the VirtualFileSystem::mutex_
+  // lock is safe because |image_stream_| is the only object that manipulates
+  // the ref counter in the file stream obtained from |underlying_handler|.
+}
+
+bool ReadonlyFileHandler::ParseReadonlyFsImage() {
+  ALOG_ASSERT(image_stream_);
+
+  struct stat buf = {};
+  if (image_stream_->fstat(&buf)) {
+    ALOGE("fstat %s failed", image_filename_.c_str());
+    return false;
+  }
+
+  void* addr = image_stream_->mmap(
+      NULL, buf.st_size, PROT_READ, MAP_PRIVATE, 0);
+  if (addr == MAP_FAILED) {
+    ALOGE("mmap %s failed", image_filename_.c_str());
+    return false;
+  }
+  image_reader_.reset(new ReadonlyFsReader(static_cast<uint8_t*>(addr)));
+
+  // Unmap the image immediately so that it will not take up virtual address
+  // space. However, keep the stream open for later use.
+  if (image_stream_->munmap(addr, buf.st_size) < 0) {
+    ALOGE("munmap %p with size=%llu failed",
+          addr, static_cast<uint64_t>(buf.st_size));
+    return false;
+  }
+  return true;
+}
+
+scoped_refptr<FileStream> ReadonlyFileHandler::CreateFileLocked(
+    const std::string& pathname, int oflag) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  ReadonlyFsReader::Metadata metadata;
+  if (!image_reader_->GetMetadata(pathname, &metadata)) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  return new ReadonlyFile(image_stream_, read_ahead_size_, pathname,
+                          metadata.offset, metadata.size, metadata.mtime,
+                          oflag);
+}
+
+bool ReadonlyFileHandler::IsInitialized() const {
+  if (!underlying_handler_)
+    return true;  // for testing.
+  return image_stream_;
+}
+
+void ReadonlyFileHandler::Initialize() {
+  underlying_handler_->Initialize();
+  image_stream_ =
+      underlying_handler_->open(-1, image_filename_.c_str(), O_RDONLY, 0);
+  if (!image_stream_) {
+    ALOGE("Failed to open %s", image_filename_.c_str());
+    return;
+  }
+  ARC_STRACE_REPORT("parsing an image file: %s", image_filename_.c_str());
+  if (!ParseReadonlyFsImage()) {
+    ALOG_ASSERT(false, "Failed to parse %s", image_filename_.c_str());
+    image_stream_ = NULL;
+  }
+}
+
+scoped_refptr<FileStream> ReadonlyFileHandler::open(
+    int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  const bool is_directory = image_reader_->IsDirectory(pathname);
+  if (oflag & (O_WRONLY | O_RDWR)) {
+    errno = (is_directory ? EISDIR : EACCES);
+    return NULL;
+  }
+  if (is_directory)
+    return new DirectoryFileStream("readonly", pathname, this);
+  return CreateFileLocked(pathname, oflag);
+}
+
+Dir* ReadonlyFileHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  return image_reader_->OpenDirectory(name);
+}
+
+int ReadonlyFileHandler::stat(const std::string& pathname, struct stat* out) {
+  if (image_reader_->IsDirectory(pathname)) {
+    // |pathname| exists and it is a directory.
+    DirectoryFileStream::FillStatData(pathname, out);
+    // TODO(crbug.com/242337): Fill better values in |st_nlink| and |st_size|.
+    return 0;
+  }
+  scoped_refptr<FileStream> file = CreateFileLocked(pathname, O_RDONLY);
+  if (file) {
+    // |pathname| exists and it is a file.
+    return file->fstat(out);
+  }
+  errno = ENOENT;
+  return -1;
+}
+
+int ReadonlyFileHandler::statfs(const std::string& pathname,
+                                struct statfs* out) {
+  if (image_reader_->Exist(pathname)) {
+    if (base::StringPiece(pathname).starts_with("/proc"))
+      return DoStatFsForProc(out);
+    else
+      return DoStatFsForSystem(out);
+  }
+  errno = ENOENT;
+  return -1;
+}
+
+int ReadonlyFileHandler::mkdir(const std::string& pathname, mode_t mode) {
+  if (image_reader_->Exist(pathname)) {
+    errno = EEXIST;
+    return -1;
+  }
+  errno = EACCES;
+  return -1;
+}
+
+int ReadonlyFileHandler::rename(const std::string& oldpath,
+                                const std::string& newpath) {
+  if (!image_reader_->Exist(oldpath) || newpath.empty()) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (oldpath == newpath)
+    return 0;
+  errno = EACCES;
+  return -1;
+}
+
+int ReadonlyFileHandler::truncate(const std::string& pathname, off64_t length) {
+  if (!image_reader_->Exist(pathname))
+    errno = ENOENT;
+  else
+    errno = EACCES;
+  return -1;
+}
+
+int ReadonlyFileHandler::unlink(const std::string& pathname) {
+  if (!image_reader_->Exist(pathname))
+    errno = ENOENT;
+  else
+    errno = EACCES;
+  return -1;
+}
+
+int ReadonlyFileHandler::utimes(const std::string& pathname,
+                                const struct timeval times[2]) {
+  errno = EROFS;
+  return -1;
+}
+
+ssize_t ReadonlyFileHandler::readlink(const std::string& pathname,
+                                      std::string* resolved) {
+  ReadonlyFsReader::Metadata metadata;
+  // This function can be called before ParseReadonlyFsImage().
+  if (image_reader_ &&
+      image_reader_->GetMetadata(pathname, &metadata) &&
+      metadata.file_type == ReadonlyFsReader::kSymbolicLink) {
+    *resolved = metadata.link_target;
+    return resolved->size();
+  }
+  errno = EINVAL;
+  return -1;
+}
+
+//------------------------------------------------------------------------------
+
+ReadonlyFile::ReadonlyFile(scoped_refptr<FileStream> image_stream,
+                           size_t read_ahead_size,
+                           const std::string& pathname,
+                           off_t file_offset, size_t file_size, time_t mtime,
+                           int oflag)
+  : FileStream(oflag, pathname),
+    write_mapped_(false), image_stream_(image_stream),
+    read_ahead_buf_max_size_(read_ahead_size), read_ahead_buf_offset_(0),
+    offset_in_image_(file_offset), size_(file_size), mtime_(mtime), pos_(0) {
+  ALOG_ASSERT(image_stream_);
+  ALOG_ASSERT(!pathname.empty());
+  ARC_STRACE_REPORT(
+      "%s is at offset 0x%08llx", pathname.c_str(), offset_in_image_);
+}
+
+ReadonlyFile::~ReadonlyFile() {}
+
+int ReadonlyFile::madvise(void* addr, size_t length, int advice) {
+  if (advice != MADV_DONTNEED)
+    return FileStream::madvise(addr, length, advice);
+
+  // Note: We should have |write_mapped_| here rather than in NaClManifestFile
+  // because the underlying stream is shared by all ReadonlyFile streams.
+
+  if (write_mapped_) {
+    // madvise(MADV_DONTNEED) is called against a region possibly mapped with
+    // PROT_WRITE and MAP_PRIVATE (yes, creating a writable map backed by a
+    // read-only file is possible). Since there is no reliable way to determine
+    // mmap parameters (e.g. a file offset which corresponds to the |addr|) for
+    // emulating MADV_DONTNEED, returns -1 with EINVAL.
+    // TODO(crbug.com/425955): Remove this restriction once the bug is fixed.
+    // See the other TODO(crbug.com/425955) below for more details.
+    ALOGW("MADV_DONTNEED is called against a writable region backed by a "
+          "read-only file %s (address=%p). This is not supported.",
+          pathname().c_str(), addr);
+    errno = EINVAL;
+    return -1;
+  }
+
+  // Since both the mapping and the file are read-only, returning 0 for
+  // MADV_DONTNEED without mapping the underlying file again is safe. However,
+  // this does not properly reduce the resident memory usage.
+  // TODO(crbug.com/425955): For better resident memory usage, do either of
+  // the following: (1) Add mprotect IRT to SFI and non-SFI NaCl and just call
+  // it, or (2) add a way to query the current prot, flags, and file offset of
+  // the |addr| (likely by improving the MemoryRegion class), and call mmap IRT
+  // again with these parameters plus MAP_FIXED. Both ways can be applied to
+  // nacl_manifest_file.cc (which is almost always mapped with PROT_WRITE to
+  // make .bss work) and pepper_file.cc (which is writable persistent file
+  // system) too.
+  ALOGW("MADV_DONTNEED is called against a read-only file %s (address=%p). "
+        "Returning 0 without releasing resident memory pages.",
+        pathname().c_str(), addr);
+  return 0;
+}
+
+void* ReadonlyFile::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  // TODO(crbug.com/326219): Implement a real proc file system and remove this
+  // check.
+  if (StartsWithASCII(pathname(), "/proc/", true)) {
+    errno = EIO;
+    return MAP_FAILED;
+  }
+  write_mapped_ |= prot & PROT_WRITE;
+  // Note: We should check neither |length| nor |offset| here to be consistent
+  // with Linux kernel's behavior. The kernel allows |length| and |offset|
+  // values greater than the size of the file as long as the |length| fits in
+  // the virtual address space and the |offset| is multiples of the page size.
+  // Mapped pages that do not have backing file are treated like PROT_NONE pages
+  // (i.e. SIGBUS when touched). We are not always able to raise SIGBUS
+  // (instead, subsequent files in the image might be accessed), but this is
+  // much better than returing MAP_FAILED here in terms of app compatibility.
+  return image_stream_->mmap(
+      addr, length, prot, flags, offset + offset_in_image_);
+}
+
+int ReadonlyFile::mprotect(void* addr, size_t length, int prot) {
+  write_mapped_ |= prot & PROT_WRITE;
+  return image_stream_->mprotect(addr, length, prot);
+}
+
+int ReadonlyFile::munmap(void* addr, size_t length) {
+  return image_stream_->munmap(addr, length);
+}
+
+ssize_t ReadonlyFile::read(void* buf, size_t count) {
+  const ssize_t read_size = PreadImpl(buf, count, pos_, true /* read-ahead */);
+  if (read_size > 0)
+    pos_ += read_size;
+  return read_size;
+}
+
+ssize_t ReadonlyFile::write(const void* buf, size_t count) {
+  errno = EINVAL;
+  return -1;
+}
+
+ssize_t ReadonlyFile::pread(void* buf, size_t count, off64_t offset) {
+  return PreadImpl(buf, count, offset, false /* no read-ahead */);
+}
+
+ssize_t ReadonlyFile::PreadImpl(void* buf, size_t count, off64_t offset,
+                                bool can_read_ahead) {
+  // Since the image file which |image_stream_| points to is much larger
+  // than |size_|, we need to adjust |count| so that pread() below does
+  // not read the next file in the image.
+  const ssize_t read_max = size_ - offset;
+  if (read_max <= 0)
+    return 0;
+  const size_t read_size = std::min<size_t>(count, read_max);
+
+  // Check if [offset, offset + read_size) is inside the read-ahead cache.
+  if (read_ahead_buf_offset_ <= offset &&
+      offset < read_ahead_buf_offset_ + read_ahead_buf_.size() &&
+      offset + read_size <= read_ahead_buf_offset_ + read_ahead_buf_.size()) {
+    ARC_STRACE_REPORT("Cache hit: pread %zu bytes from the read ahead cache",
+                        read_size);
+    const off_t offset_in_cache = offset - read_ahead_buf_offset_;
+    memcpy(buf, &read_ahead_buf_[0] + offset_in_cache, read_size);
+    return read_size;
+  }
+
+  // When |read_size| is large enough, do not try to use |read_ahead_buf_| to
+  // avoid unnecessary memcpy.
+  const int64_t pread_offset_in_image = offset_in_image_ + offset;
+  if ((read_size >= read_ahead_buf_max_size_) || !can_read_ahead) {
+    ARC_STRACE_REPORT("pread %zu bytes from the image at offset 0x%08llx",
+                        read_size, pread_offset_in_image);
+    return image_stream_->pread(buf, read_size, pread_offset_in_image);
+  }
+
+  // We should not read beyond the end of the file even though the underlying
+  // handler may allow it. Therefore the min() call.
+  read_ahead_buf_.resize(read_ahead_buf_max_size_);
+  const size_t read_ahead_size =
+      std::min<size_t>(read_ahead_buf_max_size_, read_max);
+  ARC_STRACE_REPORT("Cache miss: "
+                      "pread-ahead %zu bytes from the image at offset 0x%08llx",
+                      read_ahead_size, pread_offset_in_image);
+
+  // Note: The underlying pread() is allowed to return a value smaller than
+  // |read_ahead_size| although it does not do that in practice.
+  const ssize_t pread_result = image_stream_->pread(
+      &read_ahead_buf_[0], read_ahead_size, pread_offset_in_image);
+  if (pread_result <= 0) {
+    if (pread_result < 0 && errno == EINTR)
+      read_ahead_buf_.clear();
+    return pread_result;  // except the EINTR case, the cache remains intact
+  }
+
+  // Update the read-ahead cache.
+  ARC_STRACE_REPORT("Update the read ahead cache: "
+                      "%zu bytes from the image at offset 0x%08llx",
+                      pread_result, pread_offset_in_image);
+  read_ahead_buf_.resize(pread_result);
+  read_ahead_buf_offset_ = offset;
+
+  // Call min() again not to overflow the |buf| buffer.
+  const size_t copy_size = std::min<size_t>(read_size, pread_result);
+  memcpy(buf, &read_ahead_buf_[0], copy_size);
+  return copy_size;
+}
+
+off64_t ReadonlyFile::lseek(off64_t offset, int whence) {
+  switch (whence) {
+    case SEEK_SET:
+      pos_ = offset;
+      return pos_;
+    case SEEK_CUR:
+      pos_ += offset;
+      return pos_;
+    case SEEK_END:
+      pos_ = size_ + offset;
+      return pos_;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+  return 0;
+}
+
+int ReadonlyFile::fdatasync() {
+  return this->fsync();
+}
+
+int ReadonlyFile::fstat(struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  ALOG_ASSERT(!pathname().empty());
+  out->st_ino = inode();
+  out->st_mode = S_IFREG;
+  out->st_nlink = 1;
+  out->st_size = size_;
+  out->st_mtime = mtime_;
+  out->st_blksize = 4096;
+  // TODO(crbug.com/242337): Fill other fields.
+  return 0;
+}
+
+int ReadonlyFile::fsync() {
+  // TODO(crbug.com/236900): Hard-coding "/proc" here does not look very good.
+  // Revisit this when we implement /proc/self/maps. Note that ARC does not
+  // handle /proc/self/exe with this class.
+  if (StartsWithASCII(pathname(), "/proc/", true)) {
+    errno = EINVAL;
+    return -1;
+  }
+  return 0;
+}
+
+int ReadonlyFile::ioctl(int request, va_list ap) {
+  if (request == FIONREAD) {
+    // According to "man ioctl_list", FIONREAD stores its value as an int*.
+    int* argp = va_arg(ap, int*);
+    *argp = size_ - pos_;
+    return 0;
+  }
+  ALOGE("ioctl command %d not supported\n", request);
+  errno = EINVAL;
+  return -1;
+}
+
+bool ReadonlyFile::IsSelectWriteReady() const {
+  return false;
+}
+
+const char* ReadonlyFile::GetStreamType() const {
+  return "readonly";
+}
+
+size_t ReadonlyFile::GetSize() const {
+  // Note: sys->mutex() must be held here.
+  return size_;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/readonly_file.h b/src/posix_translation/readonly_file.h
new file mode 100644
index 0000000..ed4ac42
--- /dev/null
+++ b/src/posix_translation/readonly_file.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_READONLY_FILE_H_
+#define POSIX_TRANSLATION_READONLY_FILE_H_
+
+#include <time.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/export.h"
+#include "gtest/gtest_prod.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/readonly_fs_reader.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+class ReadonlyFile;
+
+// A class which handles read-only files in an image file specified by
+// |image_filename|. All operations in the handler including open() do not
+// require an IPC to the browser process and therefore are very fast. Only
+// one-time Initialize() call could require it depending on the actual type
+// of the |underlying_handler|. You can find the format of the image file
+// in scripts/create_readonly_fs_image.py.
+class ARC_EXPORT ReadonlyFileHandler : public FileSystemHandler {
+ public:
+  // |image_filename| is the full path name of the image. Can be NULL for
+  // unit testing. |underlying_handler| is a path handler for opening and
+  // reading the image file. Can be NULL for unit testing too. This object
+  // does not own |underlying_handler|. |underlying_handler| must outlive
+  // |this| object.
+  ReadonlyFileHandler(const std::string& image_filename,
+                      size_t read_ahead_size,
+                      FileSystemHandler* underlying_handler);
+  virtual ~ReadonlyFileHandler();
+
+  virtual bool IsInitialized() const OVERRIDE;
+  virtual void Initialize() OVERRIDE;
+
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual ssize_t readlink(const std::string& pathname,
+                           std::string* resolved) OVERRIDE;
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual int truncate(const std::string& pathname, off64_t length) OVERRIDE;
+  virtual int unlink(const std::string& pathname) OVERRIDE;
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]) OVERRIDE;
+
+ private:
+  scoped_refptr<FileStream> CreateFileLocked(const std::string& pathname,
+                                             int oflag);
+  bool ParseReadonlyFsImage();
+
+  const std::string image_filename_;
+  const size_t read_ahead_size_;
+  scoped_ptr<ReadonlyFsReader> image_reader_;
+  FileSystemHandler* underlying_handler_;
+  scoped_refptr<FileStream> image_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyFileHandler);
+};
+
+// A file stream for handling a read-only file. This is similar to
+// ReadonlyMemoryFile, but is even more memory efficient than that.
+// Unlike ReadonlyMemoryFile, this stream does not allocate memory
+// at all. Instead, just asks the underlying |image_stream| for the
+// content of the file. Therefore, if the underlying stream is a very
+// memory efficient one like NaClManifestFile, so does ReadonlyFile.
+class ReadonlyFile : public FileStream {
+ public:
+  ReadonlyFile(scoped_refptr<FileStream> image_stream,
+               size_t read_ahead_size,
+               const std::string& pathname, off_t file_offset,
+               size_t file_size, time_t file_mtime, int oflag);
+
+  virtual int fdatasync() OVERRIDE;
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual int fsync() OVERRIDE;
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual int madvise(void* addr, size_t length, int advice) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int mprotect(void* addr, size_t length, int prot) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t pread(void* buf, size_t count, off64_t offset) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  // Although ReadonlyFile does not support select/poll, override the function
+  // just in case.
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+  virtual size_t GetSize() const OVERRIDE;
+
+ protected:
+  virtual ~ReadonlyFile();
+
+ private:
+  friend class ReadonlyFileTest;
+
+  ssize_t PreadImpl(void* buf, size_t count, off64_t offset,
+                    bool can_read_ahead);
+
+  // True if the stream is possibly mapped with PROT_WRITE.
+  // TODO(crbug.com/425955): Remove this once MemoryRegion has rich information
+  // about each memory page such as prot, flags, and file offset.
+  bool write_mapped_;
+
+  // A stream of the readonly filesystem image.
+  scoped_refptr<FileStream> image_stream_;
+
+  // For read-ahead caching.
+  const size_t read_ahead_buf_max_size_;
+  std::vector<uint8_t> read_ahead_buf_;
+  int64_t read_ahead_buf_offset_;
+
+  const int64_t offset_in_image_;
+  const int64_t size_;
+  const time_t mtime_;
+
+  // The current position in the file.
+  int64_t pos_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyFile);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_READONLY_FILE_H_
diff --git a/src/posix_translation/readonly_file_test.cc b/src/posix_translation/readonly_file_test.cc
new file mode 100644
index 0000000..04cfcde
--- /dev/null
+++ b/src/posix_translation/readonly_file_test.cc
@@ -0,0 +1,501 @@
+// Copyright (c) 2013 The Chromium OS 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 <arpa/inet.h>  // htonl
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/readonly_file.h"
+#include "posix_translation/readonly_fs_reader_test.h"
+#include "posix_translation/readonly_memory_file.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/test_util/mmap_util.h"
+
+namespace posix_translation {
+
+namespace {
+
+const char kBadFile[] = "does_not_exist";
+const char kImageFile[] = "/tmp/test.img";
+const ssize_t kReadAheadSize = 256;
+
+// A stream for testing which works as the underlying stream for ReadonlyFile
+// (the test target) and provides actual file content to the stream.
+class TestUnderlyingStream : public ReadonlyMemoryFile {
+ public:
+  TestUnderlyingStream(const uint8_t* content, size_t size)
+      : ReadonlyMemoryFile(kImageFile, 0, time(NULL)),
+        content_(content, content + size) {
+  }
+  virtual ~TestUnderlyingStream() {}
+
+ private:
+  virtual const Content& GetContent() OVERRIDE {
+    return content_;
+  }
+
+  const Content content_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUnderlyingStream);
+};
+
+// A handler for creating a TestUnderlyingStream stream. A TestUnderlyingHandler
+// instance is passed to ReadonlyFileHandler (another test target), and
+// TestUnderlyingHandler::open() is called by the handler.
+class TestUnderlyingHandler : public FileSystemHandler {
+ public:
+  TestUnderlyingHandler() : FileSystemHandler("TestUnderlyingHandler") {
+    initialized_ = test_image_.Init(
+        ARC_TARGET_PATH "/posix_translation_fs_images/"
+        "test_readonly_fs_image.img");
+  }
+  virtual ~TestUnderlyingHandler() {}
+
+  virtual bool IsInitialized() const OVERRIDE { return initialized_; }
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE {
+    if (pathname == kImageFile) {
+      return new TestUnderlyingStream(reinterpret_cast<const uint8_t*>(
+          test_image_.data()), test_image_.size());
+    }
+    errno = ENOENT;
+    return NULL;
+  }
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE {
+    return -1;
+  }
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE {
+    return -1;
+  }
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE {
+    return NULL;
+  }
+
+ private:
+  MmappedFile test_image_;
+  bool initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUnderlyingHandler);
+};
+
+}  // namespace
+
+class ReadonlyFileTest : public FileSystemTestCommon {
+ protected:
+  ReadonlyFileTest() {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+
+    // Although we use NaClManifestHandler as an underlying handler for
+    // ReadonlyFileHandler for production, it does not work inside unit
+    // test. Assuming ReadonlyMemoryFileHandler works fine, we use it as a
+    // replacement.
+    underlying_handler_.reset(new TestUnderlyingHandler);
+    handler_.reset(new ReadonlyFileHandler(kImageFile, kReadAheadSize,
+                                           underlying_handler_.get()));
+    handler_->Initialize();
+    ASSERT_TRUE(handler_->IsInitialized());
+  }
+
+  virtual void TearDown() OVERRIDE {
+    underlying_handler_.reset();
+    FileSystemTestCommon::TearDown();
+  }
+  void CallIoctl(scoped_refptr<FileStream> stream, int request, ...);
+
+  scoped_ptr<ReadonlyFileHandler> handler_;
+
+ private:
+  scoped_ptr<FileSystemHandler> underlying_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyFileTest);
+};
+
+TEST_F(ReadonlyFileTest, TestOpen) {
+  // Can't open files in writable mode.
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[0].filename, O_WRONLY, 0);
+  EXPECT_FALSE(stream);
+  stream = handler_->open(-1, kTestFiles[0].filename, O_RDWR, 0);
+  EXPECT_FALSE(stream);
+
+  stream = handler_->open(-1, kTestFiles[0].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Test if it's possible to open the same file again.
+  scoped_refptr<FileStream> stream2 = handler_->open(
+      -1, kTestFiles[0].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream2);
+  EXPECT_NE(stream, stream2);
+}
+
+TEST_F(ReadonlyFileTest, TestMmap) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1, kTestFiles[0].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Try to map the first file in the image.
+  char* file0 = reinterpret_cast<char*>(stream->mmap(
+      NULL,  kTestFiles[0].size, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, file0);
+  EXPECT_EQ(0, strncmp(file0, "123\n", 4));
+
+  // Do the same again and compare two addresses.
+  char* file0_2 = reinterpret_cast<char*>(stream->mmap(
+      NULL,  kTestFiles[0].size, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, file0_2);
+  EXPECT_NE(file0, file0_2);
+  EXPECT_EQ(0, stream->munmap(file0, kTestFiles[0].size));
+  EXPECT_EQ(0, stream->munmap(file0_2, kTestFiles[0].size));
+
+  // Try to map the second file in the image with zero and non-zero offset.
+  stream = handler_->open(-1, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  const size_t kPageSizeMultiple = 64 * 1024;
+  char* file1 = reinterpret_cast<char*>(stream->mmap(
+      NULL, kPageSizeMultiple * 2, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, file1);
+  EXPECT_EQ(0, file1[0]);
+  EXPECT_EQ(0, file1[89999]);
+  EXPECT_EQ('X', file1[90000]);
+  EXPECT_EQ(0, stream->munmap(file1, kPageSizeMultiple * 2));
+
+  file1 = reinterpret_cast<char*>(stream->mmap(
+      NULL, kPageSizeMultiple, PROT_READ, MAP_PRIVATE, kPageSizeMultiple));
+  ASSERT_NE(MAP_FAILED, file1);
+  EXPECT_EQ(0, file1[0]);  // confirm this does not crash.
+  EXPECT_EQ(0, file1[89999 - kPageSizeMultiple]);
+  EXPECT_EQ('X', file1[90000 - kPageSizeMultiple]);
+  EXPECT_EQ(0, stream->munmap(file1, kPageSizeMultiple));
+
+  // Try to map the second file with too large offset. This should NOT be
+  // rejected (see the comment in ReadonlyFile::mmap).
+  file1 = reinterpret_cast<char*>(stream->mmap(
+      NULL, 1, PROT_READ, MAP_PRIVATE, kPageSizeMultiple * 10));
+  ASSERT_NE(MAP_FAILED, file1);
+  EXPECT_EQ(0, stream->munmap(file1, 1));
+
+  // Try to map the second file with too large length. This should NOT be
+  // rejected either (see the comment in ReadonlyFile::mmap).
+  file1 = reinterpret_cast<char*>(stream->mmap(
+      NULL, kTestFiles[1].size * 10, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, file1);
+  EXPECT_EQ(0, stream->munmap(file1, kTestFiles[1].size * 10));
+
+  // Try to map a file in the middle of the image file.
+  stream = handler_->open(-1, kTestFiles[5].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char* file5 = reinterpret_cast<char*>(stream->mmap(
+      NULL,  kTestFiles[5].size, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, file5);
+  EXPECT_EQ(0, strncmp(file5, "A", 1));
+  EXPECT_EQ(0, stream->munmap(file5, kTestFiles[5].size));
+
+  // TODO(crbug.com/373818): Re-enable the test.
+#if !(defined(__arm__) && !defined(__native_client__))
+  // Zero-length mmap should always fail.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, stream->mmap(NULL, 0, PROT_READ, MAP_PRIVATE, 0));
+  EXPECT_EQ(EINVAL, errno);
+#endif
+
+  // Unaligned offset should always be rejected.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, stream->mmap(NULL, 1, PROT_READ, MAP_PRIVATE, 1));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestMkdir) {
+  // mkdir is not supported.
+  EXPECT_EQ(-1, handler_->mkdir("/tmp/directory", 0777));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(-1, handler_->mkdir("/test/dir", 0777));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestTruncate) {
+  // truncate is not supported.
+  EXPECT_EQ(-1, handler_->truncate(kTestFiles[0].filename, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(-1, handler_->truncate(kBadFile, 0));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestUnlink) {
+  // unlink is not supported.
+  EXPECT_EQ(-1, handler_->unlink(kTestFiles[0].filename));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(-1, handler_->unlink(kBadFile));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestRename) {
+  // rename is not supported except renaming to the same file case.
+  EXPECT_EQ(
+      0, handler_->rename(kTestFiles[0].filename, kTestFiles[0].filename));
+  EXPECT_EQ(-1, handler_->rename(kBadFile, kBadFile));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->rename(kTestFiles[0].filename, ""));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_EQ(-1, handler_->rename(kTestFiles[0].filename, kBadFile));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestStat) {
+  const struct stat kZeroBuf = {};
+  struct stat statbuf = {};
+  EXPECT_EQ(-1, handler_->stat(kBadFile, &statbuf));
+  EXPECT_EQ(ENOENT, errno);
+  for (size_t i = 0; i < kNumTestFiles; ++i) {
+    EXPECT_EQ(0, handler_->stat(kTestFiles[i].filename, &statbuf)) << i;
+    EXPECT_EQ(kTestFiles[i].size, statbuf.st_size) << i;
+    // ReadonlyFile does not set permission bits, relying VirtualFileSystem.
+    EXPECT_EQ(static_cast<mode_t>(S_IFREG), statbuf.st_mode) << i;
+    EXPECT_NE(kZeroBuf.st_ino, statbuf.st_ino);
+    EXPECT_LT(kZeroBuf.st_mtime, statbuf.st_mtime);
+    EXPECT_EQ(kZeroBuf.st_atime, statbuf.st_atime);  // we do not support this.
+    EXPECT_EQ(kZeroBuf.st_ctime, statbuf.st_ctime);  // we do not support this.
+  }
+  struct stat statbuf2 = {};
+  EXPECT_EQ(0, handler_->stat(kTestFiles[0].filename, &statbuf2));
+  // Check i-node uniqueness.
+  EXPECT_NE(statbuf.st_ino, statbuf2.st_ino);
+
+  // Try to stat directories.
+  EXPECT_EQ(0, handler_->stat("/", &statbuf));
+  // ReadonlyFile does not set permission bits, relying VirtualFileSystem.
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR), statbuf.st_mode);
+  EXPECT_EQ(0, handler_->stat("/test/", &statbuf));
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR), statbuf.st_mode);
+  EXPECT_EQ(0, handler_->stat("/test", &statbuf));
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR), statbuf.st_mode);
+  EXPECT_EQ(0, handler_->stat("/test/dir/", &statbuf));
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR), statbuf.st_mode);
+  EXPECT_EQ(0, handler_->stat("/test/dir", &statbuf));
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR), statbuf.st_mode);
+  EXPECT_EQ(-1, handler_->stat("/test/dir2", &statbuf));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_F(ReadonlyFileTest, TestRead) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[0].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  char c = 0;
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('1', c);
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('2', c);
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('3', c);
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('\n', c);
+  EXPECT_EQ(0 /* EOF */, stream->read(&c, 1));
+  EXPECT_EQ(0 /* EOF */, stream->read(&c, 1));
+
+  // Seek then read again.
+  EXPECT_EQ(1, stream->lseek(1, SEEK_SET));
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('2', c);
+  EXPECT_EQ(4, stream->lseek(0, SEEK_END));
+  EXPECT_EQ(3, stream->lseek(-1, SEEK_CUR));
+  EXPECT_EQ(1, stream->read(&c, 1));
+  EXPECT_EQ('\n', c);
+  EXPECT_EQ(0, stream->read(&c, 1));
+
+  // Try pread(). Confirm the syscall does not update |offset_|.
+  EXPECT_EQ(1, stream->pread(&c, 1, 2));
+  EXPECT_EQ('3', c);
+  EXPECT_EQ(0, stream->read(&c, 1));  // still return zero
+  EXPECT_EQ(0, stream->pread(&c, 1, 12345));
+}
+
+TEST_F(ReadonlyFileTest, TestReadAhead) {
+  // Use the large (100k) file for this test.
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Confirm that we can read bytes larger than |kReadAheadSize| at once.
+  char buf[kReadAheadSize * 10];
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(kReadAheadSize + 1, stream->read(buf, kReadAheadSize + 1));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('X', buf[1]);
+  EXPECT_EQ('X', buf[kReadAheadSize]);
+  EXPECT_EQ('A', buf[kReadAheadSize + 1]);
+
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(kReadAheadSize, stream->read(buf, kReadAheadSize));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('X', buf[1]);
+  EXPECT_EQ('X', buf[kReadAheadSize - 1]);
+  EXPECT_EQ('A', buf[kReadAheadSize]);
+
+  // Try to fill the cache. Confirm that read() returns 1, not |kReadAheadSize|.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(1, stream->read(buf, 1));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('A', buf[1]);
+
+  // Test the cache-hit case.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(2, stream->read(buf, 2));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('X', buf[1]);
+  EXPECT_EQ('A', buf[2]);
+
+  // The same. Cache-hit case.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(kReadAheadSize - 1, stream->read(buf, kReadAheadSize - 1));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('X', buf[1]);
+  EXPECT_EQ('X', buf[kReadAheadSize - 2]);
+  EXPECT_EQ('A', buf[kReadAheadSize - 1]);
+
+  // Cache-miss.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89999, stream->lseek(89999, SEEK_SET));
+  EXPECT_EQ(kReadAheadSize, stream->read(buf, kReadAheadSize));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('X', buf[1]);
+  EXPECT_EQ('X', buf[kReadAheadSize - 1]);
+  EXPECT_EQ('A', buf[kReadAheadSize]);
+
+  // Cache-miss again.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(89998, stream->lseek(89998, SEEK_SET));
+  EXPECT_EQ(3, stream->read(buf, 3));
+  EXPECT_EQ('\0', buf[0]);
+  EXPECT_EQ('\0', buf[1]);
+  EXPECT_EQ('X', buf[2]);
+  EXPECT_EQ('A', buf[3]);
+
+  // Clear the cache.
+  stream = handler_->open(-1, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Seek near the end of the file. Confirm that read-ahead works fine in that
+  // case too.
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(99990, stream->lseek(-10, SEEK_END));
+  EXPECT_EQ(1, stream->read(buf, 1));
+  EXPECT_EQ('X', buf[0]);
+  EXPECT_EQ('A', buf[1]);
+
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(9, stream->read(buf, kReadAheadSize - 1));
+  EXPECT_EQ('X', buf[0]);
+  EXPECT_EQ('X', buf[8]);
+  EXPECT_EQ('A', buf[9]);
+
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(99980, stream->lseek(-20, SEEK_END));
+  EXPECT_EQ(20, stream->read(buf, kReadAheadSize - 1));
+  EXPECT_EQ('X', buf[0]);
+  EXPECT_EQ('X', buf[19]);
+  EXPECT_EQ('A', buf[20]);
+
+  memset(buf, 'A', sizeof(buf));
+  EXPECT_EQ(99970, stream->lseek(-30, SEEK_END));
+  EXPECT_EQ(30, stream->read(buf, kReadAheadSize));
+  EXPECT_EQ('X', buf[0]);
+  EXPECT_EQ('X', buf[29]);
+  EXPECT_EQ('A', buf[30]);
+}
+
+TEST_F(ReadonlyFileTest, TestReadAheadOneByte) {
+  // Test the sequential 1-byte read case (crbug.com/288552).
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+
+  // Use assert not to output 90k failures.
+  char c;
+  for (size_t i = 0; i < 90000; ++i) {
+    c = 0xff;
+    // Because we are in a tight loop, try to avoid using SCOPED_TRACE
+    // and only construct strings lazily when there is actually a
+    // failure.
+    ASSERT_EQ(1, stream->read(&c, 1)) << "at " << i;
+    ASSERT_EQ('\0', c) << "at " << i;
+  }
+  // The same. Use assert.
+  for (size_t i = 0; i < 10000; ++i) {
+    c = 0xff;
+    // The same. Only lazily construct error messages.
+    ASSERT_EQ(1, stream->read(&c, 1)) << "at " << i;
+    ASSERT_EQ('X', c)<< "at " << i;
+  }
+
+  // Just in case, confirm that read() recognizes EOF properly.
+  EXPECT_EQ(0, stream->read(&c, 1));
+  EXPECT_EQ(0, stream->read(&c, 1));
+}
+
+TEST_F(ReadonlyFileTest, TestWrite) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[0].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  char c = 'a';
+  EXPECT_EQ(-1, stream->write(&c, 1));
+  EXPECT_EQ(EINVAL, errno);
+  EXPECT_EQ(-1, stream->pwrite(&c, 1, 0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+void ReadonlyFileTest::CallIoctl(
+    scoped_refptr<FileStream> stream, int request, ...) {
+  va_list ap;
+  va_start(ap, request);
+  EXPECT_EQ(0, stream->ioctl(request, ap));
+  va_end(ap);
+}
+
+TEST_F(ReadonlyFileTest, TestIoctl) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  int remain;
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(static_cast<int>(kTestFiles[1].size), remain);
+  char c[kTestFiles[1].size];
+  EXPECT_EQ(static_cast<ssize_t>(kTestFiles[1].size - 1),
+            stream->read(c, kTestFiles[1].size - 1));
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(1, remain);
+  EXPECT_EQ(1, stream->read(c, 1));
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(0, remain);
+}
+
+TEST_F(ReadonlyFileTest, TestGetStreamType) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_NE(std::string("unknown"), stream->GetStreamType());
+  EXPECT_NE(std::string(), stream->GetStreamType());
+}
+
+TEST_F(ReadonlyFileTest, TestGetSize) {
+  scoped_refptr<FileStream> stream =
+      handler_->open(-1 /* fd */, kTestFiles[1].filename, O_RDONLY, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(kTestFiles[1].size, stream->GetSize());
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/readonly_fs_reader.cc b/src/posix_translation/readonly_fs_reader.cc
new file mode 100644
index 0000000..a8177a4
--- /dev/null
+++ b/src/posix_translation/readonly_fs_reader.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium OS 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 "posix_translation/readonly_fs_reader.h"
+
+#include <arpa/inet.h>
+#include <string.h>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "common/alog.h"
+#include "posix_translation/address_util.h"
+#include "posix_translation/path_util.h"
+
+namespace posix_translation {
+
+namespace {
+
+struct FileInfo_ {
+  std::string filename;
+  std::string link_target;
+  uint32_t offset;
+  size_t size;
+  time_t mtime;
+  ReadonlyFsReader::FileType file_type;
+};
+
+}  // namespace
+
+ReadonlyFsReader::ReadonlyFsReader(const unsigned char* filesystem_image) {
+  ParseImage(filesystem_image);
+}
+
+ReadonlyFsReader::~ReadonlyFsReader() {
+}
+
+bool ReadonlyFsReader::GetMetadata(const std::string& filename,
+                                   Metadata* metadata) const {
+  FileToMemory::const_iterator it = file_objects_.find(filename);
+  if (it == file_objects_.end())
+    return false;
+  *metadata = it->second;
+  return true;
+}
+
+bool ReadonlyFsReader::Exist(const std::string& filename) const {
+  if (file_objects_.count(filename) > 0)
+    return true;
+  return file_names_.StatDirectory(filename);
+}
+
+bool ReadonlyFsReader::IsDirectory(const std::string& filename) const {
+  return file_names_.StatDirectory(filename);
+}
+
+Dir* ReadonlyFsReader::OpenDirectory(const std::string& name) {
+  return file_names_.OpenDirectory(name);
+}
+
+void ReadonlyFsReader::ParseImage(const unsigned char* image_metadata) {
+  // The padding in the image is always for the 64k-page environment.
+  static const size_t kNaCl64PageSize = 64 * 1024;
+  // FS image must be aligned to the (native) page size. Otherwise, mmap() will
+  // return unaligned address.
+  ALOG_ASSERT(AlignTo(image_metadata, util::GetPageSize()) == image_metadata);
+
+  const unsigned char* p = image_metadata;
+  size_t num_files = 0;
+  p = ReadUInt32BE(p, &num_files);
+
+  std::vector<struct FileInfo_> files;
+  files.reserve(num_files);
+
+  for (size_t i = 0; i < num_files; ++i) {
+    uint32_t offset = 0;
+    p = ReadUInt32BE(p, &offset);
+    uint32_t size = 0;
+    p = ReadUInt32BE(p, &size);
+    uint32_t mtime = 0;
+    p = ReadUInt32BE(p, &mtime);
+    uint32_t file_type = 0;
+    p = ReadUInt32BE(p, &file_type);
+    std::string filename = reinterpret_cast<const char*>(p);
+    p += filename.length() + 1;
+    std::string link_target;
+    if (file_type == kSymbolicLink) {
+      link_target = reinterpret_cast<const char*>(p);
+      p += link_target.length() + 1;
+    }
+    const struct FileInfo_ f =
+        { filename, link_target, offset, size, static_cast<time_t>(mtime),
+          static_cast<FileType>(file_type) };
+    files.push_back(f);
+  }
+
+  // Find the beginning of the content.
+  ptrdiff_t metadata_size = p - image_metadata;
+  size_t pad_len = 0;
+  if (metadata_size % kNaCl64PageSize)
+    pad_len = kNaCl64PageSize - (metadata_size % kNaCl64PageSize);
+  metadata_size += pad_len;
+
+  for (size_t i = 0; i < files.size(); ++i) {
+#if defined(DEBUG_POSIX_TRANSLATION)
+    ALOGI("Found a read-only file: %s %zu bytes "
+          "(at offset 0x%x, mtime %ld)",
+          files[i].filename.c_str(), files[i].size,
+          files[i].offset + metadata_size, files[i].mtime);
+#endif
+    const Metadata value = {
+      // The offset value in the image is relative to the beginning of the
+      // content. To convert it to the file offset, add |metadata_size|.
+      static_cast<off_t>(files[i].offset) + metadata_size,
+      files[i].size, files[i].mtime, files[i].file_type,
+      files[i].link_target
+    };
+    if (files[i].file_type == kEmptyDirectory) {
+      file_names_.MakeDirectories(files[i].filename);
+    } else {
+      bool result = file_objects_.insert(
+          std::make_pair(files[i].filename, value)).second;
+      ALOG_ASSERT(result);
+      result = file_names_.AddFile(files[i].filename);
+      ALOG_ASSERT(result);
+    }
+  }
+}
+
+// static
+const unsigned char* ReadonlyFsReader::ReadUInt32BE(
+    const unsigned char* p, uint32_t* out_result) {
+  p = AlignTo(p, 4);
+  *out_result = ntohl(*reinterpret_cast<const uint32_t*>(p));
+  return p + sizeof(uint32_t);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/readonly_fs_reader.h b/src/posix_translation/readonly_fs_reader.h
new file mode 100644
index 0000000..d89d6c7
--- /dev/null
+++ b/src/posix_translation/readonly_fs_reader.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_READONLY_FS_READER_H_
+#define POSIX_TRANSLATION_READONLY_FS_READER_H_
+
+#include <time.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/containers/hash_tables.h"
+#include "gtest/gtest_prod.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+// Note: This class is not thread-safe.
+class ReadonlyFsReader {
+ public:
+  // ReadonlyFsReader does not own the |filesystem_image| pointer.
+  explicit ReadonlyFsReader(const unsigned char* filesystem_image);
+  ~ReadonlyFsReader();
+
+  // File type constants, which should be consistent with ones in
+  // create_readonly_fs_image.py.
+  enum FileType {
+    kRegularFile = 0,
+    kSymbolicLink = 1,
+    kEmptyDirectory = 2,
+  };
+
+  struct Metadata {
+    off_t offset;
+    size_t size;
+    time_t mtime;
+    FileType file_type;
+    std::string link_target;
+    bool operator==(const Metadata& rhs) const {  // for EXPECT_EQ.
+      return (offset == rhs.offset && size == rhs.size && mtime == rhs.mtime &&
+              file_type == rhs.file_type && link_target == rhs.link_target);
+    }
+  };
+
+  // Returns true and writes information of the |filename| in
+  // |metadata|. Returns false if the file does not exist in the file system.
+  bool GetMetadata(const std::string& filename, Metadata* metadata) const;
+
+  // Returns true if |filename| exists in the file system. Note that this
+  // function returns true when |filename| is a directory name.
+  bool Exist(const std::string& filename) const;
+
+  // Returns true if |filename| refers an existing directory.
+  bool IsDirectory(const std::string& filename) const;
+
+  // Returns a list of files in the |name| directory. NULL if |name| is unknown.
+  Dir* OpenDirectory(const std::string& name);
+
+ private:
+  friend class ReadonlyFsReaderTest;
+  FRIEND_TEST(ReadonlyFsReaderTest, TestAlignTo);
+  FRIEND_TEST(ReadonlyFsReaderTest, TestParseImage);
+  FRIEND_TEST(ReadonlyFsReaderTest, TestParseImageProd);
+
+  template<typename T>
+  static T* AlignTo(T* p, size_t boundary) {
+    uintptr_t u = reinterpret_cast<uintptr_t>(p);
+    u = (u + (boundary - 1)) & ~(boundary - 1);
+    return reinterpret_cast<T*>(u);
+  }
+
+  // Parses |filesystem_image| and update member variables.
+  void ParseImage(const unsigned char* filesystem_image);
+
+  // Reads 4-byte big-endian integer from a next 4B boundary of |p|, assigns the
+  // integer to |out_result|, and returns |p| + padding-to-the-boundary +
+  // sizeof(uint32_t).
+  static const unsigned char* ReadUInt32BE(const unsigned char* p,
+                                           uint32_t* out_result);
+
+  // A hash_map from a file name to its metadata such as the size of the file.
+  typedef base::hash_map<std::string, Metadata> FileToMemory;  // NOLINT
+  FileToMemory file_objects_;
+  DirectoryManager file_names_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyFsReader);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_READONLY_FS_READER_H_
diff --git a/src/posix_translation/readonly_fs_reader_test.cc b/src/posix_translation/readonly_fs_reader_test.cc
new file mode 100644
index 0000000..2eec49e
--- /dev/null
+++ b/src/posix_translation/readonly_fs_reader_test.cc
@@ -0,0 +1,449 @@
+// Copyright (c) 2013 The Chromium OS 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 <arpa/inet.h>  // htonl
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/readonly_fs_reader.h"
+#include "posix_translation/readonly_fs_reader_test.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/test_util/mmap_util.h"
+
+namespace posix_translation {
+
+class ReadonlyFsReaderTest : public FileSystemTestCommon {
+ protected:
+  ReadonlyFsReaderTest() : cc_factory_(this) {
+  }
+
+  virtual ~ReadonlyFsReaderTest() {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+
+    std::string image_directory =
+        ARC_TARGET_PATH "/posix_translation_fs_images";
+    std::string prod_filename = image_directory + "/readonly_fs_image.img";
+    std::string test_filename = image_directory + "/test_readonly_fs_image.img";
+
+    ASSERT_TRUE(prod_image_.Init(prod_filename));
+    ASSERT_TRUE(test_image_.Init(test_filename));
+
+    reader_.reset(new ReadonlyFsReader(
+        reinterpret_cast<const unsigned char*>(test_image_.data())));
+    reader_prod_.reset(new ReadonlyFsReader(
+        reinterpret_cast<const unsigned char*>(prod_image_.data())));
+  }
+
+  const ReadonlyFsReader::Metadata* FindFile(
+      const ReadonlyFsReader::FileToMemory& files,
+      const std::string& file_to_find) {
+    for (ReadonlyFsReader::FileToMemory::const_iterator it = files.begin();
+         it != files.end(); ++it) {
+      if (it->first.find(file_to_find) != std::string::npos)
+        return &it->second;
+    }
+    return NULL;
+  }
+
+  const ReadonlyFsReader::Metadata* FindNonVendorLibraries(
+      const ReadonlyFsReader::FileToMemory& files) {
+    const std::string kSo(".so");
+    const std::string kVendorLib("/vendor/lib");
+    for (ReadonlyFsReader::FileToMemory::const_iterator it = files.begin();
+         it != files.end(); ++it) {
+      if (EndsWith(it->first, kSo, true) &&
+          !StartsWithASCII(it->first, kVendorLib, true))
+        return &it->second;
+    }
+    return NULL;
+  }
+
+  static const unsigned char* ReadUInt32BE(const unsigned char* p,
+                                           uint32_t* out_result) {
+    return ReadonlyFsReader::ReadUInt32BE(p, out_result);
+  }
+
+  pp::CompletionCallbackFactory<ReadonlyFsReaderTest> cc_factory_;
+  scoped_ptr<ReadonlyFsReader> reader_;
+  scoped_ptr<ReadonlyFsReader> reader_prod_;
+  MmappedFile prod_image_;
+  MmappedFile test_image_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyFsReaderTest);
+};
+
+TEST_F(ReadonlyFsReaderTest, TestReadUInt32BE) {
+  static const uint32_t kValue = 0x12345678;
+
+  const uint32_t value_big_endian = htonl(kValue);
+  const unsigned char* p =
+    // Casting to char* does not violate the -fstrict-aliasing rule.
+    reinterpret_cast<const unsigned char*>(&value_big_endian);
+
+  uint32_t result = 0;
+  EXPECT_EQ(p + sizeof(uint32_t), ReadUInt32BE(p, &result));
+  EXPECT_EQ(kValue, result);
+}
+
+TEST_F(ReadonlyFsReaderTest, TestReadUInt32BEUnaligned) {
+  static const unsigned char buf[7] = {};
+  for (size_t i = 0; i < 4; ++i) {
+    uint32_t result = 0x12345678;
+    // Make sure ReadUInt32BE() does not crash.
+    ReadUInt32BE(&buf[i], &result);
+    EXPECT_EQ(0U, result);
+  }
+}
+
+TEST_F(ReadonlyFsReaderTest, TestAlignTo) {
+  static const size_t kNaCl64PageSize = 64 * 1024;
+
+  uintptr_t p = 0;
+  uintptr_t p_aligned = 0;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = 1;
+  p_aligned = kNaCl64PageSize;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned - 1;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned + 1;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned * 2),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned * 2 - 1;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned * 2),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned * 2;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned * 2),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+  p = p_aligned * 2 + 1;
+  EXPECT_EQ(reinterpret_cast<void*>(p_aligned * 3),
+            reader_->AlignTo(reinterpret_cast<void*>(p), kNaCl64PageSize));
+}
+
+TEST_F(ReadonlyFsReaderTest, TestExist) {
+  EXPECT_TRUE(reader_->Exist(""));
+  EXPECT_TRUE(reader_->Exist("/"));
+  EXPECT_FALSE(reader_->Exist("/tes"));
+  EXPECT_TRUE(reader_->Exist("/test"));
+  EXPECT_FALSE(reader_->Exist("/testa"));
+  EXPECT_TRUE(reader_->Exist("/test/"));
+  EXPECT_FALSE(reader_->Exist("/test/a.ode"));
+  EXPECT_TRUE(reader_->Exist("/test/a.odex"));
+  EXPECT_FALSE(reader_->Exist("/test/a.odexa"));
+  EXPECT_TRUE(reader_->Exist("/test/c.odex"));
+  EXPECT_TRUE(reader_->Exist("/test/c0.odex"));
+  EXPECT_FALSE(reader_->Exist("/test/c1.odex"));
+  EXPECT_TRUE(reader_->Exist("/test/dir"));
+  EXPECT_TRUE(reader_->Exist("/test/dir/"));
+  EXPECT_TRUE(reader_->Exist("/test/dir/empty.odex"));
+  EXPECT_FALSE(reader_->Exist("/test/dir/empty.odexa"));
+  EXPECT_TRUE(reader_->Exist("/test/emptydir"));
+  EXPECT_TRUE(reader_->Exist("/test/emptydir/"));
+  EXPECT_TRUE(reader_->Exist("/test/emptyfile"));
+  EXPECT_TRUE(reader_->Exist("/test/symlink1"));
+  EXPECT_TRUE(reader_->Exist("/test/symlink2"));
+  EXPECT_FALSE(reader_->Exist("/test/symlink3"));
+}
+
+TEST_F(ReadonlyFsReaderTest, TestIsDirectory) {
+  EXPECT_TRUE(reader_->IsDirectory(""));
+  EXPECT_TRUE(reader_->IsDirectory("/"));
+  EXPECT_TRUE(reader_->IsDirectory("/test"));
+  EXPECT_TRUE(reader_->IsDirectory("/test/"));
+  EXPECT_FALSE(reader_->IsDirectory("/test/a.odex"));
+  EXPECT_TRUE(reader_->IsDirectory("/test/dir"));
+  EXPECT_TRUE(reader_->IsDirectory("/test/dir/"));
+  EXPECT_TRUE(reader_->IsDirectory("/test/emptydir"));
+  EXPECT_TRUE(reader_->IsDirectory("/test/emptydir/"));
+  EXPECT_FALSE(reader_->IsDirectory("/test/emptyfile"));
+  EXPECT_FALSE(reader_->IsDirectory("/test/dir/empty.odex"));
+  EXPECT_FALSE(reader_->IsDirectory("/test/symlink1"));
+}
+
+TEST_F(ReadonlyFsReaderTest, TestGetMetadata) {
+  ReadonlyFsReader::Metadata metadata;
+  EXPECT_FALSE(reader_->GetMetadata("", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/tes", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/testa", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test/", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test/a.ode", &metadata));
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/a.odex", &metadata));
+  EXPECT_EQ(4U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kRegularFile, metadata.file_type);
+
+  EXPECT_FALSE(reader_->GetMetadata("/test/a.odexa", &metadata));
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/c.odex", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kRegularFile, metadata.file_type);
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/c0.odex", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kRegularFile, metadata.file_type);
+
+  EXPECT_FALSE(reader_->GetMetadata("/test/c1.odex", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test/dir", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test/dir/", &metadata));
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/dir/empty.odex", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kRegularFile, metadata.file_type);
+
+  EXPECT_FALSE(reader_->GetMetadata("/test/dir/empty.odexa", &metadata));
+
+  EXPECT_FALSE(reader_->GetMetadata("/test/emptydir", &metadata));
+  EXPECT_FALSE(reader_->GetMetadata("/test/emptydir/", &metadata));
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/emptyfile", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kRegularFile, metadata.file_type);
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/symlink1", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kSymbolicLink, metadata.file_type);
+  EXPECT_EQ("/test/a.odex", metadata.link_target);
+
+  EXPECT_TRUE(reader_->GetMetadata("/test/symlink2", &metadata));
+  EXPECT_EQ(0U, metadata.size);
+  EXPECT_LT(0L, metadata.mtime);
+  EXPECT_EQ(ReadonlyFsReader::kSymbolicLink, metadata.file_type);
+  EXPECT_EQ("/test/b.odex", metadata.link_target);
+}
+
+TEST_F(ReadonlyFsReaderTest, TestOpenReadCloseDir) {
+  static const Dir* kNullDir = NULL;
+
+  // Try to scan "/".
+  scoped_ptr<Dir> dirp(reader_->OpenDirectory("/"));
+  ASSERT_NE(kNullDir, dirp.get());
+  dirent entry;
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string("test"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_FALSE(dirp->GetNext(&entry));
+
+  // Try to scan "/test".
+  dirp.reset(reader_->OpenDirectory("/test"));
+  ASSERT_NE(kNullDir, dirp.get());
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("a.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("b.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("big.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("c.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("c0.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string("dir"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("emptydir"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("emptyfile"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("symlink1"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("symlink2"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_FALSE(dirp->GetNext(&entry));
+
+  // Try to scan "/test/dir".
+  dirp.reset(reader_->OpenDirectory("/test/dir/"));
+  ASSERT_NE(kNullDir, dirp.get());
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string("."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_DIR, entry.d_type);
+  EXPECT_EQ(std::string(".."), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("c.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(DT_REG, entry.d_type);
+  EXPECT_EQ(std::string("empty.odex"), entry.d_name);
+  EXPECT_NE(0U, entry.d_ino);
+  ASSERT_FALSE(dirp->GetNext(&entry));
+
+  // Try to scan unknown dir, "/test/dirX".
+  dirp.reset(reader_->OpenDirectory("/test/dirX"));
+  EXPECT_EQ(kNullDir, dirp.get());
+}
+
+TEST_F(ReadonlyFsReaderTest, TestParseImage) {
+  EXPECT_EQ(kNumTestFiles, reader_->file_objects_.size());
+  ReadonlyFsReader::FileToMemory::const_iterator it =
+    reader_->file_objects_.find(kTestFiles[0].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[0].size, it->second.size);
+  EXPECT_LT(0L, it->second.mtime);
+  const char* file_head = test_image_.data() + it->second.offset;
+  EXPECT_EQ('1', file_head[0]);
+  EXPECT_EQ('2', file_head[1]);
+  EXPECT_EQ('3', file_head[2]);
+  EXPECT_EQ('\n', file_head[3]);
+
+  it = reader_->file_objects_.find(kTestFiles[1].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[1].size, it->second.size);
+  EXPECT_LT(0L, it->second.mtime);
+  file_head = test_image_.data() + it->second.offset;
+  for (size_t i = 0; i < kTestFiles[1].size; ++i) {
+    // See scripts/create_test_fs_image.py for the magic numbers.
+    if (i < 90000)
+      ASSERT_EQ(0x0, file_head[i]) << i;
+    else
+      ASSERT_EQ('X', file_head[i]) << i;
+  }
+  it = reader_->file_objects_.find(kTestFiles[2].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[2].size, it->second.size);
+  EXPECT_LT(0L, it->second.mtime);
+  file_head = test_image_.data() + it->second.offset;
+  EXPECT_EQ('Z', file_head[0]);
+
+  it = reader_->file_objects_.find(kTestFiles[3].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[3].size, it->second.size);  // empty file
+  EXPECT_LT(0L, it->second.mtime);
+
+  it = reader_->file_objects_.find(kTestFiles[4].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[4].size, it->second.size);  // empty file
+  EXPECT_LT(0L, it->second.mtime);
+
+  it = reader_->file_objects_.find(kTestFiles[5].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[5].size, it->second.size);
+  EXPECT_LT(0L, it->second.mtime);
+  file_head = test_image_.data() + it->second.offset;
+  EXPECT_EQ('A', file_head[0]);
+
+  it = reader_->file_objects_.find(kTestFiles[6].filename);
+  ASSERT_TRUE(it != reader_->file_objects_.end());
+  ASSERT_EQ(kTestFiles[6].size, it->second.size);  // empty file
+  EXPECT_LT(0L, it->second.mtime);
+
+  it = reader_->file_objects_.find("test/a.odex");
+  EXPECT_TRUE(it == reader_->file_objects_.end());
+  it = reader_->file_objects_.find("/test/a.ode");
+  EXPECT_TRUE(it == reader_->file_objects_.end());
+  it = reader_->file_objects_.find("/test");
+  EXPECT_TRUE(it == reader_->file_objects_.end());
+  it = reader_->file_objects_.find("does_not_exist");
+  EXPECT_TRUE(it == reader_->file_objects_.end());
+}
+
+// Test if the production rootfs img is valid.
+TEST_F(ReadonlyFsReaderTest, TestParseImageProd) {
+  // Note: Do not check files that are not open sourced.
+  // Otherwise nacl-i686-weird builder will fail.
+
+  EXPECT_GT(reader_prod_->file_objects_.size(), 0U);
+  // These files should exist in the image.
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_, "/proc/version"));
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_, "/proc/meminfo"));
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_, "/proc/self/maps"));
+  const ReadonlyFsReader::Metadata* metadata =
+      FindFile(reader_prod_->file_objects_, "/proc/self/cmdline");
+  ASSERT_TRUE(metadata);
+  EXPECT_EQ(9U, metadata->size);
+
+  const char* file_head = prod_image_.data() + metadata->offset;
+  EXPECT_EQ(std::string("NaClMain\0", 9),
+            std::string(file_head, metadata->size));
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_, "/system/bin/sh"));
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_,
+                       "/system/usr/share/zoneinfo/tzdata"));
+  EXPECT_TRUE(FindFile(reader_prod_->file_objects_,
+                       "/vendor/lib/libstdc++.so"));
+  // These files should NOT exist in the image.
+  EXPECT_FALSE(FindFile(reader_prod_->file_objects_, "root/proc/version"));
+  EXPECT_FALSE(FindFile(reader_prod_->file_objects_, "intermediates/"));
+
+  EXPECT_FALSE(FindFile(reader_prod_->file_objects_, "dexopt"));
+  EXPECT_FALSE(FindNonVendorLibraries(reader_prod_->file_objects_));
+  // These directories should exist in the image.
+  EXPECT_TRUE(reader_prod_->IsDirectory("/cache"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/data"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/dev"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/mnt"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/proc"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/sys"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/sys/kernel/debug/tracing"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/system"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/system/bin"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/system/lib"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/usr/lib"));
+  EXPECT_TRUE(reader_prod_->IsDirectory("/vendor/chromium/crx"));
+  // These directories should NOT exist in the image.
+  EXPECT_FALSE(reader_prod_->IsDirectory("/bin"));
+  EXPECT_FALSE(reader_prod_->IsDirectory("/deva"));
+  EXPECT_FALSE(reader_prod_->IsDirectory("/dev/foo"));
+  EXPECT_FALSE(reader_prod_->IsDirectory("/syste"));
+  EXPECT_FALSE(reader_prod_->IsDirectory("/usr/bin"));
+  EXPECT_FALSE(reader_prod_->IsDirectory("/tmp"));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/readonly_fs_reader_test.h b/src/posix_translation/readonly_fs_reader_test.h
new file mode 100644
index 0000000..02073b0
--- /dev/null
+++ b/src/posix_translation/readonly_fs_reader_test.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_READONLY_FS_READER_TEST_H_
+#define POSIX_TRANSLATION_READONLY_FS_READER_TEST_H_
+
+#include <cstddef>
+
+namespace posix_translation {
+
+// A list of files |kTestFsImage| generated by create_test_fs_image.py contains.
+const struct TestFile {
+  const char* filename;
+  const char* link_target;
+  size_t size;
+} kTestFiles[] = {
+  { "/test/a.odex", NULL, 4U },  // "123\n"
+  { "/test/big.odex", NULL, 100000U },  // '\0' x90000, then 'X' x10000
+  { "/test/b.odex", NULL, 1U },  // "Z"
+  { "/test/c0.odex", NULL, 0U },
+  { "/test/c.odex", NULL, 0U },
+  { "/test/dir/c.odex", NULL, 1U },  // "A"
+  { "/test/dir/empty.odex", NULL, 0U },
+  { "/test/emptyfile", NULL, 0U },
+  { "/test/symlink1", "/test/a.odex", 0U },
+  { "/test/symlink2", "/test/b.odex", 0U },
+};
+const size_t kNumTestFiles = arraysize(kTestFiles);
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_READONLY_FS_READER_TEST_H_
diff --git a/src/posix_translation/readonly_memory_file.cc b/src/posix_translation/readonly_memory_file.cc
new file mode 100644
index 0000000..52c0129
--- /dev/null
+++ b/src/posix_translation/readonly_memory_file.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 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 "posix_translation/readonly_memory_file.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <algorithm>  // std::min
+
+#include "common/arc_strace.h"
+#include "posix_translation/address_util.h"
+#include "posix_translation/statfs.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+ReadonlyMemoryFile::ReadonlyMemoryFile(const std::string& pathname,
+                                       int errno_for_mmap,
+                                       time_t mtime)
+  : FileStream(O_RDONLY, pathname), errno_for_mmap_(errno_for_mmap),
+    mtime_(mtime), pos_(0) {
+  ALOG_ASSERT(errno_for_mmap_ >= 0);
+}
+
+ReadonlyMemoryFile::~ReadonlyMemoryFile() {
+}
+
+int ReadonlyMemoryFile::fstat(struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  ALOG_ASSERT(!pathname().empty());
+  out->st_ino = inode();
+  out->st_mode = S_IFREG;
+  out->st_nlink = 1;
+  out->st_size = GetContent().size();
+  out->st_mtime = mtime_;
+  out->st_blksize = 4096;
+  // TODO(crbug.com/242337): Fill other fields.
+  return 0;
+}
+
+int ReadonlyMemoryFile::ioctl(int request, va_list ap) {
+  if (request == FIONREAD) {
+    // According to "man ioctl_list", FIONREAD stores its value as an int*.
+    int* argp = va_arg(ap, int*);
+    *argp = GetContent().size() - pos_;
+    return 0;
+  }
+  ALOGE("ioctl command %d not supported", request);
+  errno = EINVAL;
+  return -1;
+}
+
+off64_t ReadonlyMemoryFile::lseek(off64_t offset, int whence) {
+  switch (whence) {
+    case SEEK_SET:
+      pos_ = offset;
+      return pos_;
+    case SEEK_CUR:
+      pos_ += offset;
+      return pos_;
+    case SEEK_END:
+      pos_ = GetContent().size() + offset;
+      return pos_;
+    default:
+      errno = EINVAL;
+      return -1;
+  }
+  return 0;
+}
+
+void* ReadonlyMemoryFile::mmap(
+    void* addr, size_t length, int prot, int flags, off_t offset) {
+  if ((prot & PROT_WRITE) && (flags & MAP_SHARED)) {
+    // Since this is a readonly file, refuse the combination. Note that this
+    // check should be done before checking |errno_for_mmap_| for better Linux
+    // kernel emulation.
+    errno = EACCES;
+    return MAP_FAILED;
+  }
+
+  if (errno_for_mmap_) {
+    errno = errno_for_mmap_;
+    return MAP_FAILED;
+  }
+
+  if (flags & MAP_SHARED) {
+    // For now, reject PROT_READ + MAP_SHARED with EINVAL for simplicity. If
+    // this is too restrictive, it is okay to remove this check. However,
+    // in that case, derived classes have to do either of the following:
+    // (1) Implement GetContent() as a constant function which always returns
+    //     the same content.
+    // (2) Or, pass a non-zero errno to this constructor so that all mmap()
+    //     fails.
+    ALOGE("This stream does not support mmap with MAP_SHARED: %s",
+          pathname().c_str());
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  // Emulate file-backed mmap with MAP_ANONYMOUS. Unlike MemoryFile, this
+  // implementation is POSIX-compliant in that it returns different addresses
+  // when it is called twice.
+  uint8_t* result = static_cast<uint8_t*>(::mmap(
+      // We need PROT_WRITE for the memcpy call below.
+      NULL, length, prot | PROT_WRITE, flags | MAP_ANONYMOUS, -1, offset));
+  if (result == MAP_FAILED)
+    return MAP_FAILED;
+
+  const Content& content = GetContent();
+
+  if (static_cast<off_t>(content.size()) > offset) {
+    const size_t length_rounded_up = util::RoundToPageSize(length);
+    const size_t write_size =
+        std::min<size_t>(content.size() - offset, length_rounded_up);
+    memcpy(result, &content[0] + offset, write_size);
+  }
+
+  if (!(prot & PROT_WRITE)) {
+    // Drop PROT_WRITE added for memcpy.
+    if (::mprotect(result, length, prot) == -1) {
+      ALOGE("mprotect failed: prot=%d, errno=%d", prot, errno);
+      ::munmap(result, length);
+      return MAP_FAILED;
+    }
+  }
+  return result;
+}
+
+int ReadonlyMemoryFile::munmap(void* addr, size_t length) {
+  ALOG_ASSERT(!errno_for_mmap_);
+  return ::munmap(addr, length);
+}
+
+ssize_t ReadonlyMemoryFile::pread(void* buf, size_t count, off64_t offset) {
+  const Content& content = GetContent();
+  const ssize_t read_max = content.size() - offset;
+  if (read_max <= 0)
+    return 0;
+  const size_t read_size = std::min<size_t>(count, read_max);
+  memcpy(buf, &content[0] + offset, read_size);
+  return read_size;
+}
+
+ssize_t ReadonlyMemoryFile::read(void* buf, size_t count) {
+  const ssize_t read_size = this->pread(buf, count, pos_);
+  if (read_size > 0)
+    pos_ += read_size;
+  return read_size;
+}
+
+ssize_t ReadonlyMemoryFile::write(const void* buf, size_t count) {
+  errno = EBADF;
+  return -1;
+}
+
+bool ReadonlyMemoryFile::IsSelectWriteReady() const {
+  return true;
+}
+
+const char* ReadonlyMemoryFile::GetStreamType() const {
+  // Should be <= 8 characters for better MemoryRegion::GetMemoryMapAsString()
+  // output.
+  return "ro-mem";
+}
+
+size_t ReadonlyMemoryFile::GetSize() const {
+  return const_cast<ReadonlyMemoryFile*>(this)->GetContent().size();
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/readonly_memory_file.h b/src/posix_translation/readonly_memory_file.h
new file mode 100644
index 0000000..1d21f6b
--- /dev/null
+++ b/src/posix_translation/readonly_memory_file.h
@@ -0,0 +1,78 @@
+// Copyright 2014 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 POSIX_TRANSLATION_READONLY_MEMORY_FILE_H_
+#define POSIX_TRANSLATION_READONLY_MEMORY_FILE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "posix_translation/file_stream.h"
+
+namespace posix_translation {
+
+// A file stream for handling dynamically created (and possibly updated) but
+// read-only files like /proc/cpuinfo whose content could dynamically change
+// based on the number of CPU cores currently online etc.
+//
+// Note: Unlike ReadonlyFile where its file content is provided by another
+// |image_stream| (which is NaClManifestFile in most cases), this class holds
+// its content on memory as the class name suggests. Unlike MemoryFile, this
+// class fully supports MAP_PRIVATE mmap and is also very memory efficient.
+// It consumes only ~size bytes of memory while MemoryFile sometimes allocates
+// a fixed size of memory chunk like 1MB.
+class ReadonlyMemoryFile : public FileStream {
+ public:
+  typedef std::vector<uint8_t> Content;
+
+  // Initializes the stream with the |content| of |size|. The object does not
+  // take ownership of the |content|, but copies it to its member variable
+  // |content_|. |pathname| is for generating an inode number for fstat(), so
+  // is |mtime|. |errno_for_mmap| should be a positive number like ENODEV
+  // when the stream should always return the number from mmap(). When
+  // |errno_for_mmap| is zero, mmap() tries to map the |content| to memory.
+  ReadonlyMemoryFile(const std::string& pathname, int errno_for_mmap,
+                     time_t mtime);
+
+  // FileStream overrides:
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE;
+  virtual int munmap(void* addr, size_t length) OVERRIDE;
+  virtual ssize_t pread(void* buf, size_t count, off64_t offset) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  // Although this class does not support select, override the function
+  // just in case.
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+  virtual size_t GetSize() const OVERRIDE;
+
+ protected:
+  virtual ~ReadonlyMemoryFile();
+
+  // Gets the current content of the file.
+  virtual const Content& GetContent() = 0;
+
+  void set_mtime(time_t new_mtime) { mtime_ = new_mtime; }
+
+ private:
+  const int errno_for_mmap_;
+  time_t mtime_;
+
+  // The current position in the file.
+  size_t pos_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReadonlyMemoryFile);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_READONLY_MEMORY_FILE_H_
diff --git a/src/posix_translation/readonly_memory_file_test.cc b/src/posix_translation/readonly_memory_file_test.cc
new file mode 100644
index 0000000..b4cef93
--- /dev/null
+++ b/src/posix_translation/readonly_memory_file_test.cc
@@ -0,0 +1,586 @@
+// Copyright 2014 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 <errno.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "gtest/gtest.h"
+#include "posix_translation/readonly_memory_file.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+namespace {
+
+const char kFileName[] = "/path/to/file.txt";
+
+// A stream for testing that simply returns a file of |size| bytes. The content
+// of the file is initialized with UpdateContent() in both constructor and
+// SetSize().
+class TestReadonlyMemoryFile : public ReadonlyMemoryFile {
+ public:
+  TestReadonlyMemoryFile(const std::string& pathname, int errno_for_mmap,
+                         size_t size, time_t mtime)
+      : ReadonlyMemoryFile(pathname, errno_for_mmap, mtime) {
+    SetSize(size);
+  }
+
+  void SetSize(size_t size) {
+    content_.resize(size);
+    UpdateContent();
+  }
+
+  // To allow TEST_F tests to call the protected function.
+  using ReadonlyMemoryFile::set_mtime;
+
+ private:
+  virtual ~TestReadonlyMemoryFile() {}
+
+  virtual const Content& GetContent() OVERRIDE {
+    return content_;
+  }
+
+  void UpdateContent() {
+    for (size_t i = 0; i < content_.size(); ++i) {
+      char c;
+      if (i == 0)
+        c = '\0';
+      else if (i < content_.size() / 2)
+        c = 'A';
+      else
+        c = 'B';
+      content_[i] = c;
+    }
+    // |content_| is now like "\0AABBBB" (without a \0 termination at the end of
+    // the buffer).
+  }
+
+  Content content_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestReadonlyMemoryFile);
+};
+
+scoped_refptr<TestReadonlyMemoryFile> GetStream(size_t size, time_t mtime) {
+  return new TestReadonlyMemoryFile(kFileName, 0 /* allow mmap */, size, mtime);
+}
+
+void CallIoctl(scoped_refptr<FileStream> stream, int request, ...) {
+  va_list ap;
+  va_start(ap, request);
+  EXPECT_EQ(0, stream->ioctl(request, ap));
+  va_end(ap);
+}
+
+// Use TEST_F with a class derived from FileSystemTestCommon to initialize
+// VirtualFileSystem before executing a test. VirtualFileSystem is needed
+// e.g. to assign an inode number to |kFileName|.
+class ReadonlyMemoryFileTest : public FileSystemTestCommon {
+};
+
+}  // namespace
+
+TEST_F(ReadonlyMemoryFileTest, TestReadEmptyStream) {
+  static const ssize_t kSize = 0;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[32];
+  EXPECT_EQ(0, stream->read(buf, sizeof(buf)));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadEmptyBuf) {
+  static const ssize_t kSize = 0;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf;
+  EXPECT_EQ(0, stream->read(&buf, 0));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestRead) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize * 2];
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadShort) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize / 2];
+  EXPECT_EQ(kSize / 2, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadExact) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadRepeat) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize * 2];
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+
+  EXPECT_EQ(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(1, stream->lseek(1, SEEK_SET));
+  EXPECT_EQ(kSize - 1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 2]);
+  EXPECT_EQ('B', buf[kSize / 2 - 1]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadTwoStreams) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  scoped_refptr<FileStream> stream2 = GetStream(kSize, 0);
+  ASSERT_TRUE(stream2);
+  // Read from two streams to make sure streams do not share internal status
+  // like the current position.
+  char buf[kSize];
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+  memset(buf, 0, sizeof(buf));
+  EXPECT_EQ(kSize, stream2->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestPread) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize * 2];
+  EXPECT_EQ(kSize / 2 + 1, stream->pread(buf, sizeof(buf), kSize / 2 - 1));
+  EXPECT_EQ('A', buf[0]);
+  EXPECT_EQ('B', buf[1]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestReadAfterPread) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize * 2];
+  EXPECT_EQ(kSize / 2 + 1, stream->pread(buf, sizeof(buf), kSize / 2 - 1));
+  // Then call read() to confirm that the |pos_| has not been modified by
+  // pread().
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[kSize / 2 - 1]);
+  EXPECT_EQ('B', buf[kSize / 2]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestPreadOutOfBound) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+  EXPECT_EQ(0, stream->pread(buf, sizeof(buf), kSize * 100));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestLseekSet) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+  EXPECT_EQ(kSize / 2 - 1, stream->lseek(kSize / 2 - 1, SEEK_SET));
+  EXPECT_EQ(kSize / 2 + 1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[0]);
+  EXPECT_EQ('B', buf[1]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestLseekCur) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(kSize / 2 - 1, stream->lseek(kSize / 2 - 1, SEEK_SET));
+  EXPECT_EQ(kSize / 2 - 2, stream->lseek(-1, SEEK_CUR));
+  EXPECT_EQ(kSize / 2, stream->lseek(2, SEEK_CUR));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestLseekEnd) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+  EXPECT_EQ(kSize, stream->lseek(0, SEEK_END));
+  EXPECT_EQ(0, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ(kSize - 1, stream->lseek(-1, SEEK_END));
+  EXPECT_EQ(1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('B', buf[0]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestFstat) {
+  static const ssize_t kSize = 16;
+  const time_t now = time(NULL);
+
+  scoped_refptr<FileStream> stream = GetStream(kSize, now);
+  ASSERT_TRUE(stream);
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_EQ(static_cast<mode_t>(S_IFREG), st.st_mode);
+  EXPECT_EQ(kSize, st.st_size);
+  // Bionic uses unsigned long for st_*time instead of time_t.
+  EXPECT_EQ(now, static_cast<time_t>(st.st_mtime));
+  EXPECT_LT(0U, st.st_ino);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestFstatMtime) {
+  static const ssize_t kSize = 16;
+  const time_t now = time(NULL);
+
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(kSize, now);
+  ASSERT_TRUE(stream);
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+  // Bionic uses unsigned long for st_*time instead of time_t.
+  EXPECT_EQ(now, static_cast<time_t>(st.st_mtime));
+
+  stream->set_mtime(now + 1);
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_EQ(now + 1, static_cast<time_t>(st.st_mtime));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestWrite) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char c = 'X';
+  EXPECT_EQ(-1, stream->write(&c, 1));
+  EXPECT_EQ(EBADF, errno);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestPwrite) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char c = 'X';
+  EXPECT_EQ(-1, stream->pwrite(&c, 1, kSize / 2));
+  EXPECT_EQ(EBADF, errno);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestIoctl) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  int remain;
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(kSize, remain);
+  char buf[kSize];
+  EXPECT_EQ(kSize - 1, stream->read(buf, kSize - 1));
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(1, remain);
+  EXPECT_EQ(1, stream->read(buf, kSize));
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(0, remain);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapUnsupported) {
+  static const size_t kSize = 3;
+
+  scoped_refptr<FileStream> stream = new TestReadonlyMemoryFile(
+      kFileName, ENODEV /* do not support mmap */, kSize, 0);
+
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_READ, MAP_PRIVATE, 0));
+  EXPECT_EQ(ENODEV, errno);
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_WRITE, MAP_PRIVATE, 0));
+  EXPECT_EQ(ENODEV, errno);
+
+  // EACCES should be preferred over ENODEV.
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_WRITE, MAP_SHARED, 0));
+  EXPECT_EQ(EACCES, errno);
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_READ | PROT_WRITE, MAP_SHARED, 0));
+  EXPECT_EQ(EACCES, errno);
+
+  // PROT_READ + MAP_SHARED mmap is not allowed either (at least for now).
+  // See the comment in ReadonlyMemoryFile::mmap.
+  stream = new TestReadonlyMemoryFile(
+      kFileName, 0 /* support mmap */, kSize, 0);
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_READ, MAP_SHARED, 0));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmap) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+
+  char buf[kSize];
+  EXPECT_EQ(kSize, stream->read(buf, sizeof(buf)));
+
+  void* addr = stream->mmap(NULL, kSize, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ(0, memcmp(addr, buf, kSize));
+  EXPECT_EQ(0, stream->munmap(addr, kSize));
+
+  // Retry with length == 1.
+  uint8_t* addr2 = static_cast<uint8_t*>(
+      stream->mmap(NULL, 1, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, addr2);
+  EXPECT_EQ('\0', addr2[0]);
+  // This should not fail/crash even though the map length is 1.
+  EXPECT_EQ('A', addr2[1]);
+  EXPECT_EQ(0, stream->munmap(addr2, 1));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestHugeMmap) {
+  const int page_size = sysconf(_SC_PAGE_SIZE);
+  ASSERT_LT(0, page_size);
+
+  const ssize_t size = page_size * 2;
+  scoped_refptr<FileStream> stream = GetStream(size, 0);
+  ASSERT_TRUE(stream);
+
+  uint8_t* addr = static_cast<uint8_t*>(
+      stream->mmap(NULL, size, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ('A', addr[size / 2 - 1]);
+  EXPECT_EQ('B', addr[size / 2]);
+  EXPECT_EQ(0, stream->munmap(addr, size));
+
+  // Confirm that mmap with non-zero offset also works.
+  addr = static_cast<uint8_t*>(
+      stream->mmap(NULL, 1, PROT_READ, MAP_PRIVATE, page_size));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ('B', addr[0]);
+  EXPECT_EQ('B', addr[1]);  // same - should not fail/crash.
+  EXPECT_EQ(0, stream->munmap(addr, 1));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapTwice) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  void* addr1 = stream->mmap(NULL, kSize, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, addr1);
+  void* addr2 = stream->mmap(NULL, kSize, PROT_READ, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, addr2);
+  EXPECT_NE(addr1, addr2);  // POSIX requires this.
+  EXPECT_EQ(0, stream->munmap(addr1, kSize));
+  EXPECT_EQ(0, stream->munmap(addr2, kSize));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapWithOffset) {
+  static const ssize_t kSize = (64 * 1024) + 1;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  char* addr = reinterpret_cast<char*>(stream->mmap(
+      NULL, 1, PROT_READ, MAP_PRIVATE, 64 * 1024));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ('B', addr[0]);
+  EXPECT_EQ(0, stream->munmap(addr, 1));
+
+  // Retry with too larget offset. Confirm it does return a valid address
+  // and it does not crash.
+  addr = reinterpret_cast<char*>(stream->mmap(
+      NULL, 2, PROT_READ, MAP_PRIVATE, 64 * 1024 * 2));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ(0, stream->munmap(addr, 2));
+
+  stream->SetSize(kSize - 1);
+  addr = reinterpret_cast<char*>(stream->mmap(
+      NULL, 2, PROT_READ, MAP_PRIVATE, 64 * 1024 * 2));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ(0, stream->munmap(addr, 2));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapWritablePrivate) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  // Although the stream is readonly, PROT_WRITE mmap should be allowed as long
+  // as the type of the mapping is MAP_PRIVATE.
+  void* addr = stream->mmap(NULL, kSize, PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, addr);
+  memset(addr, 0, kSize);  // this should not crash.
+  EXPECT_EQ(0, stream->munmap(addr, kSize));
+
+  addr = stream->mmap(NULL, kSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0);
+  ASSERT_NE(MAP_FAILED, addr);
+  // Confirm that the previous memset() does not affect the actual content in
+  // the stream.
+  EXPECT_EQ('\0', static_cast<uint8_t*>(addr)[0]);
+  EXPECT_EQ('A', static_cast<uint8_t*>(addr)[1]);
+  EXPECT_EQ(0, stream->munmap(addr, kSize));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapWritableShared) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  // MAP_SHARED mapping combined with PROT_WRITE is not allowed.
+  EXPECT_EQ(MAP_FAILED,
+            stream->mmap(NULL, kSize, PROT_WRITE, MAP_SHARED, 0));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestGetStreamType) {
+  scoped_refptr<FileStream> stream = GetStream(0, 0);
+  ASSERT_TRUE(stream);
+  ASSERT_TRUE(stream->GetStreamType());
+  EXPECT_NE(std::string("unknown"), stream->GetStreamType());
+  EXPECT_NE(std::string(), stream->GetStreamType());
+  EXPECT_GE(8U, strlen(stream->GetStreamType()));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestGetSize) {
+  const size_t kSize = 123;
+  scoped_refptr<FileStream> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(kSize, stream->GetSize());
+}
+
+// Do similar tests with SetSize() calls.
+
+TEST_F(ReadonlyMemoryFileTest, TestReadDynamicallySizedFile) {
+  static const ssize_t kSize = 30;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(5, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+
+  // Increase the size after read.
+  EXPECT_EQ(5, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[1]);
+  EXPECT_EQ('B', buf[2]);
+  stream->SetSize(6);
+  EXPECT_EQ(1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('B', buf[0]);
+
+  // Increase the size during read. The size here is 6.
+  EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(5, stream->read(buf, 5));
+  EXPECT_EQ('A', buf[2]);
+  EXPECT_EQ('B', buf[3]);
+  stream->SetSize(20);
+  EXPECT_EQ(15, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[4]);
+  EXPECT_EQ('B', buf[5]);
+
+  // Decrease the size after read. The size here is 20.
+  EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(20, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('A', buf[9]);
+  EXPECT_EQ('B', buf[10]);
+  stream->SetSize(10);
+  EXPECT_EQ(0, stream->read(buf, sizeof(buf)));
+
+  // Decrease the size during read. The size here is 10.
+  EXPECT_EQ(0, stream->lseek(0, SEEK_SET));
+  EXPECT_EQ(5, stream->read(buf, 5));
+  stream->SetSize(6);
+  EXPECT_EQ(1, stream->read(buf, sizeof(buf)));
+  EXPECT_EQ('B', buf[0]);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestPreadDynamicallySizedFile) {
+  // Directly test pread() too, just in case.
+  static const ssize_t kSize = 30;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(6, 0);
+  ASSERT_TRUE(stream);
+  char buf[kSize];
+
+  EXPECT_EQ(4, stream->pread(buf, sizeof(buf), 2));
+  EXPECT_EQ('A', buf[0]);
+  stream->SetSize(3);
+  EXPECT_EQ(1, stream->pread(buf, sizeof(buf), 2));
+  EXPECT_EQ('B', buf[0]);
+  stream->SetSize(2);
+  EXPECT_EQ(0, stream->pread(buf, sizeof(buf), 2));
+  stream->SetSize(1);
+  EXPECT_EQ(0, stream->pread(buf, sizeof(buf), 2));
+  stream->SetSize(0);
+  EXPECT_EQ(0, stream->pread(buf, sizeof(buf), 2));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestMmapDynamicallySizedFile) {
+  static const ssize_t kSize = 20;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+
+  // Compare two mmap results before and after SetSize().
+  char* addr = static_cast<char*>(
+      stream->mmap(NULL, kSize, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, addr);
+  EXPECT_EQ('A', addr[9]);
+  EXPECT_EQ('B', addr[10]);
+
+  stream->SetSize(10);
+  char* addr2 = static_cast<char*>(
+      stream->mmap(NULL, 10, PROT_READ, MAP_PRIVATE, 0));
+  ASSERT_NE(MAP_FAILED, addr2);
+  EXPECT_EQ('A', addr2[4]);
+  EXPECT_EQ('B', addr2[5]);
+
+  EXPECT_EQ(0, stream->munmap(addr, kSize));
+  EXPECT_EQ(0, stream->munmap(addr2, 10));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestFstatDynamicallySizedFile) {
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(6, 0);
+  ASSERT_TRUE(stream);
+
+  struct stat st;
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_EQ(6U, st.st_size);
+  stream->SetSize(3);
+  EXPECT_EQ(0, stream->fstat(&st));
+  EXPECT_EQ(3U, st.st_size);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestIoctlDynamicallySizedFile) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  int remain;
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(kSize, remain);
+  char buf[kSize];
+  EXPECT_EQ(1, stream->read(buf, 1));
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(kSize - 1, remain);
+  stream->SetSize(2);
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(1, remain);
+  stream->SetSize(1);
+  CallIoctl(stream, FIONREAD, &remain);
+  EXPECT_EQ(0, remain);
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestLseekDynamicallySizedFile) {
+  static const ssize_t kSize = 16;
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(kSize, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(kSize, stream->lseek(0, SEEK_END));
+  stream->SetSize(2);
+  EXPECT_EQ(2, stream->lseek(0, SEEK_END));
+}
+
+TEST_F(ReadonlyMemoryFileTest, TestGetSizeDynamicallySizedFile) {
+  scoped_refptr<TestReadonlyMemoryFile> stream = GetStream(6, 0);
+  ASSERT_TRUE(stream);
+  EXPECT_EQ(6U, stream->GetSize());
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/redirect.cc b/src/posix_translation/redirect.cc
new file mode 100644
index 0000000..9383059
--- /dev/null
+++ b/src/posix_translation/redirect.cc
@@ -0,0 +1,208 @@
+// Copyright 2014 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 "posix_translation/redirect.h"
+
+#include <dirent.h>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "common/arc_strace.h"
+#include "common/file_util.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/path_util.h"
+
+namespace posix_translation {
+
+RedirectHandler::RedirectHandler(
+    FileSystemHandler* underlying,
+    const std::vector<std::pair<std::string, std::string> >& symlinks)
+    : FileSystemHandler("RedirectHandler"),
+      is_initialized_(false), underlying_(underlying) {
+  ALOG_ASSERT(underlying);
+  for (size_t i = 0; i < symlinks.size(); ++i)
+    AddSymlink(symlinks[i].first, symlinks[i].second);
+}
+
+RedirectHandler::~RedirectHandler() {
+}
+
+bool RedirectHandler::IsInitialized() const {
+  return underlying_->IsInitialized() && is_initialized_;
+}
+
+void RedirectHandler::Initialize() {
+  if (!underlying_->IsInitialized())
+    underlying_->Initialize();
+  if (!is_initialized_) {
+    is_initialized_ = true;
+    // Note: Once you remove gmock from posix_translation/, you can call
+    // this->symlink() here for the paths passed to the constructor. Right
+    // now, doing so breaks some unit tests because this->symlink() calls
+    // into the |underlying_| handler which may end up calling gmock mocks.
+  }
+}
+
+void RedirectHandler::OnMounted(const std::string& path) {
+  return underlying_->OnMounted(path);
+}
+
+void RedirectHandler::OnUnmounted(const std::string& path) {
+  return underlying_->OnUnmounted(path);
+}
+
+void RedirectHandler::InvalidateCache() {
+  return underlying_->InvalidateCache();
+}
+
+void RedirectHandler::AddToCache(const std::string& path,
+                                 const PP_FileInfo& file_info,
+                                 bool exists) {
+  return underlying_->AddToCache(path, file_info, exists);
+}
+
+bool RedirectHandler::IsWorldWritable(const std::string& pathname) {
+  return underlying_->IsWorldWritable(pathname);
+}
+
+std::string RedirectHandler::SetPepperFileSystem(
+    const pp::FileSystem* pepper_file_system,
+    const std::string& mount_source_in_pepper_file_system,
+    const std::string& mount_dest_in_vfs) {
+  return underlying_->SetPepperFileSystem(
+      pepper_file_system,
+      mount_source_in_pepper_file_system,
+      mount_dest_in_vfs);
+}
+
+int RedirectHandler::mkdir(const std::string& pathname, mode_t mode) {
+  // Note: |pathname| is already canonicalized in VFS. VFS calls
+  // RedirectHandler::readlink() and resolves the symlink before
+  // calling into this method. The same is true for other methods too.
+  return underlying_->mkdir(pathname, mode);
+}
+
+scoped_refptr<FileStream> RedirectHandler::open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) {
+  scoped_refptr<FileStream> stream =
+      underlying_->open(fd, pathname, oflag, cmode);
+  if (stream && (stream->oflag() & O_DIRECTORY)) {
+    // Return a new stream when |pathname| points to a directory so that our
+    // OnDirectoryContentsNeeded() is called back from stream->getdents().
+    ALOG_ASSERT(
+        EndsWith(stream->GetStreamType(), "_dir", true),  // sanity check
+        "pathname=%s, oflag=%d", pathname.c_str(), oflag);
+    return new DirectoryFileStream("redirect", stream->pathname(), this);
+  }
+  return stream;
+}
+
+Dir* RedirectHandler::OnDirectoryContentsNeeded(const std::string& name) {
+  Dir* dir = underlying_->OnDirectoryContentsNeeded(name);
+  if (dir) {
+    base::hash_map<std::string, std::vector<std::string> >::const_iterator it =
+        dir_to_symlinks_.find(name);
+    if (it != dir_to_symlinks_.end()) {
+      for (size_t i = 0; i < it->second.size(); ++i)
+        dir->Add(it->second[i], Dir::SYMLINK);
+    }
+  }
+  return dir;
+}
+
+ssize_t RedirectHandler::readlink(const std::string& pathname,
+                                  std::string* resolved) {
+  const std::string rewritten = GetSymlinkTarget(pathname);
+  if (rewritten.empty()) {
+    // Not a link.
+    errno = EINVAL;
+    return -1;
+  }
+  *resolved = rewritten;
+  return resolved->size();
+}
+
+int RedirectHandler::remove(const std::string& pathname) {
+  // Note: Currently, removing, renaming, or unlinking the symbolic link itself
+  // it not supported since our code does not do that at all (and we cannot
+  // support removing symlinks in the readonly file image anyway). If you really
+  // need to support it, you can modify VFS so that VFS calls these methods with
+  // the symbolic link path name itself.
+  return underlying_->remove(pathname);
+}
+
+int RedirectHandler::rename(const std::string& oldpath,
+                            const std::string& newpath) {
+  // See the comment in remove().
+  return underlying_->rename(oldpath, newpath);
+}
+
+int RedirectHandler::rmdir(const std::string& pathname) {
+  // See the comment in remove(). When the |pathname| is a symbolic link itself,
+  // this method thouls return ENOTDIR.
+  return underlying_->rmdir(pathname);
+}
+
+int RedirectHandler::stat(const std::string& pathname, struct stat* out) {
+  return underlying_->stat(pathname, out);
+}
+
+int RedirectHandler::statfs(const std::string& pathname, struct statfs* out) {
+  return underlying_->statfs(pathname, out);
+}
+
+int RedirectHandler::symlink(const std::string& oldpath,
+                             const std::string& newpath) {
+  struct stat st;
+  // Save errno because it can be changed by stat below.
+  int old_errno = errno;
+  if (!GetSymlinkTarget(newpath).empty() || !underlying_->stat(newpath, &st)) {
+    errno = EEXIST;
+    return -1;
+  }
+  errno = old_errno;
+  AddSymlink(oldpath, newpath);
+  return 0;
+}
+
+int RedirectHandler::truncate(const std::string& pathname, off64_t length) {
+  return underlying_->truncate(pathname, length);
+}
+
+int RedirectHandler::unlink(const std::string& pathname) {
+  // See the comment in remove().
+  return underlying_->unlink(pathname);
+}
+
+int RedirectHandler::utimes(const std::string& pathname,
+                            const struct timeval times[2]) {
+  return underlying_->utimes(pathname, times);
+}
+
+void RedirectHandler::AddSymlink(const std::string& dest,
+                                 const std::string& src) {
+  ALOG_ASSERT(!util::EndsWithSlash(src));
+
+  const bool result = symlinks_.insert(std::make_pair(src, dest)).second;
+  ALOG_ASSERT(result, "Failed to add a symbolic link: %s -> %s",
+              src.c_str(), dest.c_str());
+
+  const std::string dir_name = util::GetDirName(src);
+  const std::string link_name = arc::GetBaseName(src.c_str());
+  ALOG_ASSERT(!dir_name.empty(), "src=%s", src.c_str());
+  ALOG_ASSERT(!link_name.empty(), "src=%s", src.c_str());
+
+  dir_to_symlinks_[dir_name].push_back(link_name);
+}
+
+std::string RedirectHandler::GetSymlinkTarget(const std::string& src) const {
+  base::hash_map<std::string, std::string>::const_iterator it =  // NOLINT
+      symlinks_.find(src);
+  if (it == symlinks_.end())
+    return std::string();
+  return it->second;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/redirect.h b/src/posix_translation/redirect.h
new file mode 100644
index 0000000..d9da3ca
--- /dev/null
+++ b/src/posix_translation/redirect.h
@@ -0,0 +1,97 @@
+// Copyright 2014 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 POSIX_TRANSLATION_REDIRECT_H_
+#define POSIX_TRANSLATION_REDIRECT_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/export.h"
+#include "posix_translation/file_system_handler.h"
+
+namespace posix_translation {
+
+// A thin wrapper around an existing FileSystemHandler class. This class handles
+// symlink() and readlink() calls to add a non-persistent symbolic link feature
+// to the existing handler class.
+class ARC_EXPORT RedirectHandler : public FileSystemHandler {
+ public:
+  // |underlying| is the handler which handles all FileSystemHandler calls
+  // except readlink() and symlink(). The handler must be used only by a
+  // redirect handler because the redirect handler delegates all calls
+  // including IsInitialized, Initialize, and so on. RedirectHandler takes
+  // ownership if the |underlying| handler. |symlinks| are an array of a
+  // pair of dest/src path names that are added to the handler during its
+  // initialization. Unlike symlink() which may return EEXIST, the existence
+  // of src paths passed to the constructor are never checked.
+  RedirectHandler(
+      FileSystemHandler* underlying,
+      const std::vector<std::pair<std::string, std::string> >& symlinks);
+  virtual ~RedirectHandler();
+
+  // FileSystemHandler overrides. This class should override ALL virtual
+  // functions in FileSystemHandler.
+  virtual bool IsInitialized() const OVERRIDE;
+  virtual void Initialize() OVERRIDE;
+  virtual void OnMounted(const std::string& path) OVERRIDE;
+  virtual void OnUnmounted(const std::string& path) OVERRIDE;
+  virtual void InvalidateCache() OVERRIDE;
+  virtual void AddToCache(const std::string& path,
+                          const PP_FileInfo& file_info,
+                          bool exists) OVERRIDE;
+  virtual bool IsWorldWritable(const std::string& pathname) OVERRIDE;
+  virtual std::string SetPepperFileSystem(
+      const pp::FileSystem* pepper_file_system,
+      const std::string& mount_source_in_pepper_file_system,
+      const std::string& mount_dest_in_vfs) OVERRIDE;
+
+  virtual int mkdir(const std::string& pathname, mode_t mode) OVERRIDE;
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE;
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE;
+  virtual ssize_t readlink(const std::string& pathname,
+                           std::string* resolved) OVERRIDE;
+  virtual int remove(const std::string& pathname) OVERRIDE;
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE;
+  virtual int rmdir(const std::string& pathname) OVERRIDE;
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE;
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE;
+  virtual int symlink(const std::string& oldpath,
+                      const std::string& newpath) OVERRIDE;
+  virtual int truncate(const std::string& pathname, off64_t length) OVERRIDE;
+  virtual int unlink(const std::string& pathname) OVERRIDE;
+  virtual int utimes(const std::string& pathname,
+                     const struct timeval times[2]) OVERRIDE;
+
+ private:
+  void AddSymlink(const std::string& dest, const std::string& src);
+  std::string GetSymlinkTarget(const std::string& src) const;
+
+  // True if this handler has been initialized.
+  bool is_initialized_;
+
+  // A map from a source file to a link target.
+  base::hash_map<std::string, std::string> symlinks_;  // NOLINT
+
+  // A map from a directory containing symlink(s) to the symlinks. For
+  // example, when /dir/a points to /foo, and /dir/b points to /bar,
+  // dir_to_symlinks_ has "/dir" as a key, and ["a", "b"] as its value.
+  base::hash_map<  // NOLINT
+    std::string, std::vector<std::string> > dir_to_symlinks_;
+
+  // The handler which handles all calls except readlink() and symlink().
+  scoped_ptr<FileSystemHandler> underlying_;
+
+  DISALLOW_COPY_AND_ASSIGN(RedirectHandler);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_REDIRECT_H_
diff --git a/src/posix_translation/redirect_test.cc b/src/posix_translation/redirect_test.cc
new file mode 100644
index 0000000..f97fafe
--- /dev/null
+++ b/src/posix_translation/redirect_test.cc
@@ -0,0 +1,183 @@
+// Copyright 2014 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 <errno.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/directory_manager.h"
+#include "posix_translation/redirect.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+
+namespace posix_translation {
+
+namespace {
+
+const char kPathAlreadyExists[] = "/alreadyexists";
+
+class TestUnderlyingHandler : public FileSystemHandler {
+ public:
+  TestUnderlyingHandler()
+      : FileSystemHandler("TestUnderlyingHandler"), is_initialized_(false) {
+  }
+  virtual ~TestUnderlyingHandler() {
+  }
+
+  virtual void Initialize() OVERRIDE { is_initialized_ = true; }
+  virtual bool IsInitialized() const OVERRIDE { return is_initialized_; }
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& pathname, int oflag, mode_t cmode) OVERRIDE {
+    return NULL;
+  }
+  virtual int stat(const std::string& pathname, struct stat* out) OVERRIDE {
+    if (pathname == kPathAlreadyExists)
+      return 0;
+    return -1;
+  }
+  virtual int statfs(const std::string& pathname, struct statfs* out) OVERRIDE {
+    return -1;
+  }
+  virtual Dir* OnDirectoryContentsNeeded(const std::string& name) OVERRIDE {
+    DirectoryManager manager;
+    manager.MakeDirectories(name);
+    manager.AddFile(name + "/0");
+    manager.AddFile(name + "/1");
+    return manager.OpenDirectory(name);
+  }
+
+  bool is_initialized_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestUnderlyingHandler);
+};
+
+}  // namespace
+
+class RedirectHandlerTestTest : public FileSystemTestCommon {
+ protected:
+  RedirectHandlerTestTest() {
+  }
+  virtual ~RedirectHandlerTestTest() {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    TestUnderlyingHandler* underlying = new TestUnderlyingHandler;
+
+    std::vector<std::pair<std::string, std::string> > symlinks;
+    symlinks.push_back(std::make_pair("/dest", "/src0"));
+    symlinks.push_back(std::make_pair("/dest", "/src1"));
+
+    handler_.reset(new RedirectHandler(underlying, symlinks));
+    handler_->Initialize();
+    EXPECT_TRUE(handler_->IsInitialized());
+    // Confirm that RedirectHandler delegates the call to the underlying
+    // handler.
+    EXPECT_TRUE(underlying->IsInitialized());
+  }
+
+  scoped_ptr<FileSystemHandler> handler_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RedirectHandlerTestTest);
+};
+
+TEST_F(RedirectHandlerTestTest, TestInit) {
+  // Empty. Confirms EXPECT_TRUE calls in SetUp() do not fail.
+}
+
+// Tests if the symlinks passed to the constructor work.
+TEST_F(RedirectHandlerTestTest, TestSymlinksPassedToConstructor) {
+  std::string result;
+
+  errno = 0;
+  EXPECT_EQ(5, handler_->readlink("/src0", &result));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("/dest", result);
+  result.clear();
+  errno = 0;
+  EXPECT_EQ(5, handler_->readlink("/src1", &result));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("/dest", result);
+
+  errno = 0;
+  EXPECT_EQ(-1, handler_->readlink("/src2", &result));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, handler_->readlink("/src", &result));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(RedirectHandlerTestTest, TestSymlink) {
+  EXPECT_EQ(0, handler_->symlink("/proc/42", "/proc/self"));
+  // Try to create the same symlink which should fail.
+  errno = 0;
+  EXPECT_EQ(-1, handler_->symlink("/proc/42", "/proc/self"));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(RedirectHandlerTestTest, TestSymlinkExist) {
+  // Try to create a symlink with the same name underlying_ file system
+  // already has.
+  errno = 0;
+  EXPECT_EQ(-1, handler_->symlink("/proc/42", kPathAlreadyExists));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_F(RedirectHandlerTestTest, TestReadlink) {
+  EXPECT_EQ(0, handler_->symlink("/proc/42", "/proc/self"));
+
+  std::string result;
+  errno = 0;
+  EXPECT_EQ(-1, handler_->readlink("/proc/sel", &result));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, handler_->readlink("/proc/self0", &result));
+  EXPECT_EQ(EINVAL, errno);
+  errno = 0;
+  EXPECT_EQ(-1, handler_->readlink("/proc/self/maps", &result));
+  EXPECT_EQ(EINVAL, errno);
+
+  EXPECT_EQ(8, handler_->readlink("/proc/self", &result));
+  EXPECT_EQ("/proc/42", result);
+  // We do not have to test "/proc/self/" case because our VFS always normalizes
+  // it to "/proc/self".
+}
+
+TEST_F(RedirectHandlerTestTest, TestOnDirectoryContentsNeeded) {
+  EXPECT_EQ(0, handler_->symlink("/proc/42", "/dir/1"));
+  EXPECT_EQ(0, handler_->symlink("/proc/42", "/dir/2"));
+  EXPECT_EQ(0, handler_->symlink("/proc/42", "/dir/3"));
+  scoped_ptr<Dir> dirp(handler_->OnDirectoryContentsNeeded("/dir"));
+  ASSERT_TRUE(dirp.get());
+
+  dirent entry;
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("."), entry.d_name);
+
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string(".."), entry.d_name);
+
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("0"), entry.d_name);
+  EXPECT_EQ(DT_REG, entry.d_type);
+
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("1"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);  // not DT_REG
+
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("2"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+
+  EXPECT_TRUE(dirp->GetNext(&entry));
+  EXPECT_EQ(std::string("3"), entry.d_name);
+  EXPECT_EQ(DT_LNK, entry.d_type);
+
+  EXPECT_FALSE(dirp->GetNext(&entry));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/scripts/create_readonly_fs_image.py b/src/posix_translation/scripts/create_readonly_fs_image.py
new file mode 100755
index 0000000..995d101
--- /dev/null
+++ b/src/posix_translation/scripts/create_readonly_fs_image.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+# Copyright 2014 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.
+
+# Generates a read-only file system image.
+
+# Image file format:
+#
+# [Number of files]     ; 32bit unsigned, big endian
+# [Offset of file #1]   ; 32bit unsigned, big endian (always 0x00000000)
+# [Size of file #1]     ; 32bit unsigned, big endian
+# [mtime of file #1]    ; 32bit unsigned, big endian
+# [Type of file #1]     ; 32bit unsigned, big endian
+# [Name of file #1]     ; Variable length, zero terminated, full path w/ slashes
+# (optional) [Link target of file #1] if file #1 is a symlink
+# [Zero padding to a 4-byte boundary]
+# [Offset of file #2]   ; 32bit unsigned, big endian
+# [Size of file #2]     ; 32bit unsigned, big endian
+# [mtime of file #2]    ; 32bit unsigned, big endian
+# [Type of file #2] ; 32bit unsigned, big endian
+# [Name of file #2]     ; Variable length, zero terminated, full path w/ slashes
+# (optional) [Link target of file #2] if file #1 is a symlink
+# [Zero padding to a 4-byte boundary]
+# ...
+# [Zero padding to a 4-byte boundary]
+# [Offset of file #n]   ; 32bit unsigned, big endian
+# [Size of file #n]     ; 32bit unsigned, big endian
+# [mtime of file #n]    ; 32bit unsigned, big endian
+# [Name of file #n]     ; Variable length, zero terminated, full path w/ slashes
+# [Zero padding to a 4k or 64k page boundary]
+# [Content of file #1]  ; Variable length, page aligned
+# [Zero padding to a 4k or 64k page boundary]
+# [Content of file #2]  ; Variable length, page aligned
+# [Zero padding to a 4k or 64k page boundary]
+# ...
+# [Content of file #n]  ; Variable length, page aligned
+# EOF
+#
+# * All offset values are relative to the beginning of the content of file #1.
+# * Each file's content is aligned to a 64k page so our mmap() implementation
+#   can return page aligned address on both 4k-page and 64k-page environments.
+# * The image file itself should be mapped on a native (4k or 64k) page
+#   boundary.
+
+import argparse
+import array
+import os
+import re
+import struct
+import sys
+import time
+
+
+_PAGE_SIZE = 64 * 1024  # NaCl uses 64k page.
+
+# File type constants, which should be consistent with ones in
+# readonly_fs_reader.h.
+_REGULAR_FILE = 0
+_SYMBOLIC_LINK = 1
+_EMPTY_DIRECTORY = 2
+
+
+def _normalize_path(input_filename):
+  """Remove leading dots and adds / if the first character is not /."""
+  input_filename = re.sub(r'^\.+', '', input_filename)
+  input_filename = re.sub(r'^([^/])', r'/\1', input_filename)
+  input_filename = re.sub(r'^/out/target/.*/root', '', input_filename)
+  return input_filename
+
+
+def _update_metadata(metadata, content, filename, file_size, file_mtime,
+                     file_type, link_target):
+  """Adds name, size, and offset of the |filename| to |metadata|."""
+  # |content| is all the content up to this point.
+  padded_content_size = content.buffer_info()[1]
+  _pad_array(metadata, 4)
+  metadata.fromstring(struct.pack('>iiii',
+                                  padded_content_size,
+                                  file_size,
+                                  file_mtime,
+                                  file_type)
+                      + _normalize_path(filename).encode('utf_8')
+                      + '\0')
+  if link_target:
+    metadata.fromstring(link_target.encode('utf_8') + '\0')
+
+
+def _update_content(content, filename, size):
+  """Adds the content of the |filename| to |content|."""
+  with open(filename, 'r') as f:
+    content.fromfile(f, size)
+
+
+def _pad_array(array, boundary):
+  """Adds padding to the array so the array size aligns to a next boundary."""
+  size = array.buffer_info()[1]
+  pad_bytes = boundary - (size % boundary)
+  if pad_bytes == boundary:
+    return
+  for _ in xrange(pad_bytes):
+    array.append(0)
+
+
+def _write_image(image, output_filename):
+  with open(output_filename, 'w') as f:
+    image.tofile(f)
+
+
+def _format_message(i, num_files, size, mtime, file_type, filename,
+                    link_target):
+  if file_type == _REGULAR_FILE:
+    file_type_name = 'file'
+  elif file_type == _SYMBOLIC_LINK:
+    file_type_name = 'symlink'
+  elif file_type == _EMPTY_DIRECTORY:
+    file_type_name = 'empty_dir'
+  message = 'VERBOSE: [%d/%d] [%s] %s: %d bytes (stored as %s)' % (
+      i + 1, num_files, file_type_name, filename, size,
+      _normalize_path(filename))
+  if file_type == _SYMBOLIC_LINK:
+    message += '-> %s' % link_target
+  return message
+
+
+def _get_metadata(filename, input_filenames, symlink_map, empty_dirs,
+                  empty_files):
+  if filename in symlink_map:
+    file_type = _SYMBOLIC_LINK
+    link_target = symlink_map[filename]
+    size = 0
+    mtime = time.time()  # Using the current time for a symlink.
+  elif filename in empty_dirs:
+    file_type = _EMPTY_DIRECTORY
+    children = filter(lambda x: x.startswith(filename + '/'),
+                      input_filenames)
+    if children:
+      print '%s is not empty' % filename
+      sys.exit(1)
+    link_target = None
+    size = 0
+    mtime = time.time()  # Using the current time for an empty directory.
+  elif filename in empty_files:
+    file_type = _REGULAR_FILE
+    link_target = None
+    size = 0
+    mtime = time.time()  # Using the current time for an empty file.
+  else:
+    file_type = _REGULAR_FILE
+    link_target = None
+    try:
+      size = os.stat(filename).st_size
+      mtime = os.stat(filename).st_mtime
+    except OSError, e:
+      sys.exit(e)
+  return file_type, link_target, size, mtime
+
+
+def _generate_readonly_image(input_filenames, symlink_map, empty_dirs,
+                             empty_files, verbose, output_filename):
+  metadata = array.array('B')
+  content = array.array('B')
+
+  input_filenames.extend(symlink_map.keys())
+  input_filenames.extend(empty_dirs)
+  input_filenames.extend(empty_files)
+
+  num_files = len(input_filenames)
+  _pad_array(metadata, 4)
+  metadata.fromstring(struct.pack('>i', num_files))
+  for i in xrange(num_files):
+    filename = input_filenames[i]
+    if filename.endswith('/'):
+      print '%s should not end with /' % filename
+      sys.exit(1)
+    file_type, link_target, size, mtime = _get_metadata(
+        filename, input_filenames, symlink_map, empty_dirs, empty_files)
+    if verbose:
+      print _format_message(i, num_files, size, mtime, file_type, filename,
+                            link_target)
+    _update_metadata(metadata, content, filename, size, mtime, file_type,
+                     link_target)
+    if file_type == _REGULAR_FILE and size > 0:
+      _update_content(content, filename, size)
+    if i < num_files - 1:
+      _pad_array(content, _PAGE_SIZE)
+  _pad_array(metadata, _PAGE_SIZE)
+  image = metadata + content
+  _write_image(image, output_filename)
+
+
+def main(args):
+  parser = argparse.ArgumentParser()
+  parser.add_argument('-o', '--output', metavar='FILE', required=True, help=
+                      'Write the output to filename.')
+  parser.add_argument('-s', '--symlink-map', metavar='SYMLINK_MAP', required=
+                      True, help='Map of symlinks to create.')
+  parser.add_argument('-d', '--empty-dirs', metavar='EMPTY_DIRS', required=
+                      True, help='List of empty directories.')
+  parser.add_argument('-f', '--empty-files', metavar='EMPTY_FILES', required=
+                      True, help='List of empty files.')
+  parser.add_argument('-v', '--verbose', action='store_true', help='Emit '
+                      'verbose output.')
+  parser.add_argument(dest='input', metavar='INPUT', nargs='+', help='Input '
+                      'file(s) to process.')
+  args = parser.parse_args()
+
+  empty_dirs = args.empty_dirs.split(',') if args.empty_dirs else []
+  empty_files = args.empty_files.split(',') if args.empty_files else []
+  symlink_map = dict([x.split(':') for x in args.symlink_map.split(',')])
+
+  _generate_readonly_image(args.input, symlink_map, empty_dirs, empty_files,
+                           args.verbose, args.output)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/src/posix_translation/scripts/create_runtime_file_list.py b/src/posix_translation/scripts/create_runtime_file_list.py
new file mode 100755
index 0000000..edc114b
--- /dev/null
+++ b/src/posix_translation/scripts/create_runtime_file_list.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+# Copyright 2014 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.
+
+# Generates a C++ file which can be used for constructing a
+# NaClManifestFileHandler object.
+#
+# Example:
+# ...
+# const posix_translation::NaClManifestEntry kRuntimeFiles[67] = {
+#  // { name, mode, size, mtime }
+#  { "/system/lib/libc.so", 0100755, 2054063, 1393551434 },
+#  { "/system/lib/libc_malloc_debug_leak.so", 0100755, 185355, 1393551434 },
+# ...
+
+import argparse
+import os
+import re
+import stat
+import string
+import sys
+
+
+def _get_metadata(filename):
+  try:
+    st = os.stat(filename)
+    if st.st_mode & stat.S_IXUSR:
+      mode = stat.S_IFREG | 0755
+    else:
+      mode = stat.S_IFREG | 0644
+    size = st.st_size
+    mtime = st.st_mtime
+  except OSError, e:
+    sys.exit(e)
+  return mode, size, mtime
+
+
+def _write_file(output_filename, namespace, variable_name,
+                content, num_elements):
+  with open(output_filename, 'w') as f:
+    include_guard = re.sub(r'[^A-Za-z0-9]', '_', output_filename).upper()
+    # A symbol starting with _[A-Z] is reserved by the compiler.
+    include_guard = re.sub(r'^_([A-Z])', r'\1', include_guard)
+    include_guard = re.sub(r'^[0-9]+', '', include_guard)
+    cc_file = string.Template(
+        """// Generated by create_runtime_file_list.py. DO NOT EDIT.
+
+#include "posix_translation/nacl_manifest_file.h"
+
+namespace ${namespace} {
+
+extern const posix_translation::NaClManifestEntry
+${variable_name}[${length}] = {
+  // { name, mode, size, mtime }
+${content}};
+extern const size_t ${variable_name}Len = ${length};
+
+}  // namespace ${namespace}
+""")
+    f.write(cc_file.substitute(dict(namespace=namespace,
+                                    variable_name=variable_name,
+                                    content=content,
+                                    length=num_elements)))
+
+
+def _generate_runtime_file_list(input_filenames, src_dir, dest_dir,
+                                namespace, variable_name, output_filename):
+  content = ''
+  num_files = len(input_filenames)
+  for i in xrange(num_files):
+    filename = input_filenames[i]
+    if filename.endswith('/'):
+      print '%s should not end with /' % filename
+      sys.exit(1)
+    mode, size, mtime = _get_metadata(filename)
+    if filename.startswith(src_dir):
+      filename = filename.replace(src_dir, dest_dir, 1)
+    else:
+      filename = os.path.join(dest_dir, os.path.basename(filename))
+    content += '  { "%s", 0%o, %d, %d },\n' % (
+        filename, mode, size, mtime)
+  _write_file(output_filename, namespace, variable_name, content, num_files)
+
+
+def main(args):
+  parser = argparse.ArgumentParser()
+  parser.add_argument('-o', '--output', metavar='FILE', required=True, help=
+                      'Write the output to filename.')
+  parser.add_argument('-s', '--src-dir', metavar='SRC_DIR', required=True,
+                      help='A directory name to be replaced with --dest-dir.')
+  parser.add_argument('-d', '--dest-dir', metavar='DEST_DIR', required=True,
+                      help='A directory name which replaces --src-dir.')
+  parser.add_argument('-n', '--namespace', metavar='NAMESPACE', required=True,
+                      help='A namespace for the generated file.')
+  parser.add_argument('-v', '--variable-name', metavar='VARIABLE_NAME',
+                      required=True, help='A variable name for the generated'
+                      ' file.')
+  parser.add_argument(dest='input', metavar='INPUT', nargs='+', help='Input '
+                      'file(s) to process.')
+  args = parser.parse_args()
+
+  _generate_runtime_file_list(args.input, args.src_dir, args.dest_dir,
+                              args.namespace, args.variable_name, args.output)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
diff --git a/src/posix_translation/scripts/create_test_fs_image.py b/src/posix_translation/scripts/create_test_fs_image.py
new file mode 100755
index 0000000..7edcbd0
--- /dev/null
+++ b/src/posix_translation/scripts/create_test_fs_image.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+# Copyright 2014 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.
+
+# Creates a test file system image for posix_translation_tests.
+#
+# Usage: create_test_fs_image.py output_dir/
+#
+# Example:
+# $ mkdir /tmp/test_image
+# $ ./src/posix_translation/scripts/create_test_fs_image.py /tmp/test_image
+# $ ls -s /tmp/test_image
+#  384 test_readonly_image.img
+# $ ./src/posix_translation/scripts/dump_readonly_fs_image.py \
+#     /tmp/test_image/test_readonly_image.img
+# [file] /test/a.odex 4 bytes at 0x00000000 (page 0, "Sat May 10 11:12:13 2014")
+# [file] /test/big.odex 100000 bytes at 0x00010000 (page 1, "...")
+# [file] /test/b.odex 1 bytes at 0x00030000 (page 3, "...")
+# [file] /test/c0.odex 0 bytes at 0x00040000 (page 4, "...")
+# [file] /test/c.odex 0 bytes at 0x00040000 (page 4, "...")
+# [file] /test/dir/c.odex 1 bytes at 0x00040000 (page 4, "...")
+# [file] /test/dir/empty.odex 0 bytes at 0x00050000 (page 5, "...")
+# [symlink] /test/symlink2 0 bytes at 0x00050000 (page 5, "...") -> /test/b.odex
+# [symlink] /test/symlink1 0 bytes at 0x00050000 (page 5, "...") -> /test/a.odex
+# [empty_dir] /test/emptydir 0 bytes at 0x00050000 (page 5, "...")
+# [file] /test/emptyfile 0 bytes at 0x00050000 (page 5, "...")
+
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+
+def main(args):
+  py_script = re.sub('create_test_fs_image.py',
+                     'create_readonly_fs_image.py', os.path.realpath(args[0]))
+  outdir = os.path.realpath(args[1])
+  extra_args = ' '.join(args[2:])
+  if not os.access(outdir, os.F_OK):
+    os.makedirs(outdir)
+  workdir = tempfile.mkdtemp()
+
+  os.chdir(workdir)
+  os.makedirs('test/dir/')
+  files = ['test/a.odex', 'test/big.odex', 'test/b.odex', 'test/c0.odex',
+           'test/c.odex', 'test/dir/c.odex', 'test/dir/empty.odex']
+  symlink_map = {
+      '/test/symlink1': '/test/a.odex',
+      '/test/symlink2': '/test/b.odex',
+  }
+  encoded_symlink_map = ','.join(
+      [x + ':' + y for x, y in symlink_map.iteritems()])
+
+  empty_dirs = ['/test/emptydir']
+  encoded_empty_dirs = ','.join(empty_dirs)
+
+  empty_files = ['/test/emptyfile']
+  encoded_empty_files = ','.join(empty_files)
+
+  page_size = 1 << 16
+  expected_file_size = 0
+  with open(files[0], 'w') as f:
+    f.write('123\n')
+    expected_file_size += page_size
+  with open(files[1], 'w') as f:
+    for _ in xrange(90000):
+      f.write(chr(0))
+    for _ in xrange(10000):
+      f.write('X')
+    expected_file_size += page_size * 2
+  with open(files[2], 'w') as f:
+    f.write('Z')
+    expected_file_size += page_size
+  with open(files[3], 'w') as f:
+    pass
+  with open(files[4], 'w') as f:
+    pass
+  with open(files[5], 'w') as f:
+    f.write('A')
+    expected_file_size += page_size
+  with open(files[6], 'w') as f:
+    pass
+  expected_file_size += page_size  # For the metadata at the beginning.
+
+  subprocess.call('%s %s -o %s/test_readonly_fs_image.img -s "%s" -d "%s" '
+                  '-f "%s" %s' %
+                  (py_script, extra_args, outdir, encoded_symlink_map,
+                   encoded_empty_dirs, encoded_empty_files, ' '.join(files)),
+                  shell=True)
+  subprocess.call('rm -rf %s' % workdir, shell=True)
+
+  # Check the output image size. This ensures that empty files don't consume
+  # 64KB pages.
+  file_size = os.path.getsize('%s/test_readonly_fs_image.img' % outdir)
+  assert file_size == expected_file_size
+
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
diff --git a/src/posix_translation/scripts/dump_readonly_fs_image.py b/src/posix_translation/scripts/dump_readonly_fs_image.py
new file mode 100755
index 0000000..2866c31
--- /dev/null
+++ b/src/posix_translation/scripts/dump_readonly_fs_image.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# Copyright 2014 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.
+
+# Dumps a readonly image file generated by create_readonly_fs_image.py.
+#
+# Usage:
+#
+# $ src/posix_translation/scripts/dump_readonly_fs_image.py \
+#     out/target/<target>/posix_translation_gen_sources/readonly_fs_image.img
+#
+
+import argparse
+import array
+import os
+import struct
+import sys
+import time
+import traceback
+
+
+_PAGE_SIZE = 64 * 1024  # NaCl 64bit uses 64k page.
+
+# File type constants, which should be consistent with ones in
+# readonly_fs_reader.h.
+_REGULAR_FILE = 0
+_SYMBOLIC_LINK = 1
+_EMPTY_DIRECTORY = 2
+
+
+def _read_integer(image, offset):
+  # Reads a 4-byte big endian integer from the next word boundary of
+  # image[offset] and return a tuple of the integer and new offset.
+  offset = _seek_to_next_boundary(image, offset, 4)
+  result = struct.unpack_from('>i', image, offset)[0]
+  return (result, offset + 4)
+
+
+def _read_string(image, offset):
+  # Reads a zero-terminated string from image[offset] and return a tuple of the
+  # string and new offset.
+  result = ''
+  while image[offset] != 0:
+    result += chr(image[offset])
+    offset += 1
+  return (result, offset + 1)
+
+
+def _seek_to_next_boundary(image, offset, boundary):
+  # Rounds up the offset to a next boundary.
+  offset = (offset + boundary - 1) & ~(boundary - 1)
+  image[offset]  # validate offset
+  return offset
+
+
+def _format_message(offset, size, mtime, filetype, filename, link_target):
+  if filetype == _REGULAR_FILE:
+    filetype_name = "file"
+  elif filetype == _SYMBOLIC_LINK:
+    filetype_name = "symlink"
+  elif filetype == _EMPTY_DIRECTORY:
+    filetype_name = "empty_dir"
+  page_num = offset / _PAGE_SIZE
+  message = '[%s] %s %d bytes at 0x%08x (page %d, "%s")' % (
+      filetype_name, filename, size, offset, page_num, time.ctime(mtime))
+  if link_target:
+    message += ' -> %s' % link_target
+  return message
+
+
+def _find_file(image, num_files, index, dump_filename, verbose):
+  dump_offset = -1
+  dump_size = -1
+  dump_mtime = -1
+  for i in xrange(num_files):
+    if verbose:
+      print 'VERBOSE: Reading file #%d at file offset %d.' % (i, index)
+    (offset, index) = _read_integer(image, index)
+    (size, index) = _read_integer(image, index)
+    (mtime, index) = _read_integer(image, index)
+    (filetype, index) = _read_integer(image, index)
+    (filename, index) = _read_string(image, index)
+    link_target = None
+    if filetype == _SYMBOLIC_LINK:
+      (link_target, index) = _read_string(image, index)
+    if not dump_filename or verbose:
+      # ls mode or verbose mode.
+      print _format_message(offset, size, mtime, filetype, filename,
+                            link_target)
+    if dump_filename == filename:
+      dump_offset = offset
+      dump_size = size
+      dump_mtime = mtime
+      break
+
+  return dump_offset, dump_size, dump_mtime
+
+
+def _read_image(image_filename, dump_filename, verbose):
+  # Parses the metadata part of image_filename. If dump_filename is None, prints
+  # the metadata in human-readable form. If dump_filename is not None, prints
+  # the content of the dump_filename.
+  image = array.array('B')
+  with open(image_filename, "r") as f:
+    size = os.stat(image_filename).st_size
+    image.fromfile(f, size)
+
+    if verbose:
+      print 'VERBOSE: Image %s opened (size=%d)' % (image_filename, size)
+
+    try:
+      index = 0
+      num_files, index = _read_integer(image, index)
+
+      if verbose:
+        print 'VERBOSE: Image contains %d files.' % num_files
+
+      dump_offset, dump_size, dump_mtime = _find_file(image, num_files, index,
+                                                      dump_filename, verbose)
+
+      if not dump_filename:
+        return
+
+      # dump mode.
+      if dump_offset == -1:
+        print '%s is not in image' % dump_filename
+        sys.exit(-1)
+
+      index = _seek_to_next_boundary(image, index, _PAGE_SIZE)
+      dump_offset += index  # fix-up the offset
+      if verbose:
+        print 'VERBOSE: Dumping %s at file offset %d.' % (dump_filename,
+                                                          dump_offset)
+      image[dump_offset:dump_offset + dump_size].tofile(sys.stdout)
+    except IndexError:
+      traceback.print_exc()
+      sys.exit(-1)
+
+
+def main(args):
+  parser = argparse.ArgumentParser()
+  parser.add_argument('-v', '--verbose', action='store_true', help='Emit '
+                      'verbose output.')
+  parser.add_argument('-d', '--dump', metavar='FILENAME', help='Instead of '
+                      'printing a list of files, dump the list to a file.')
+  parser.add_argument(dest='input', metavar=('INPUT'), help='Image file.')
+  args = parser.parse_args()
+
+  _read_image(args.input, args.dump, args.verbose)
+  return 0
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv[1:]))
+
+# TODO(crbug.com/242315): Remove this script. We can provide the same command
+# based on posix_translation/readonly_fs_reader.cc. This way, we can remove
+# one of the two readonly fs image decoders.
diff --git a/src/posix_translation/socket_stream.cc b/src/posix_translation/socket_stream.cc
new file mode 100644
index 0000000..724bbf1
--- /dev/null
+++ b/src/posix_translation/socket_stream.cc
@@ -0,0 +1,240 @@
+// Copyright 2014 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 "posix_translation/socket_stream.h"
+
+#include <netinet/in.h>
+
+#include "common/alog.h"
+#include "common/process_emulator.h"
+#include "posix_translation/permission_info.h"
+#include "posix_translation/socket_util.h"
+#include "posix_translation/time_util.h"
+
+namespace posix_translation {
+namespace {
+
+const int kMinSocketBuffSize = 128;
+const int kMaxSocketBuffSize = 4 * 1024 * 1024;
+
+// Converts |value| to timeval and copies the value into |optval|.
+// Returns 0 on success, or -1 on error with setting system error number to
+// errno.
+int GetTimeoutSocketOption(const base::TimeDelta& value,
+                           void* optval, socklen_t* optlen) {
+  int error = internal::VerifyGetSocketOption(optval, optlen);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  timeval value_timeval = {};
+  // If Timeout is set to negative value, getsockopt() returns {0, 0}.
+  if (value > base::TimeDelta())
+    value_timeval = internal::TimeDeltaToTimeVal(value);
+
+  internal::CopySocketOption(
+      &value_timeval, SIZEOF_AS_SOCKLEN(value_timeval), optval, optlen);
+  return 0;
+}
+
+// Interprets |optval| as a timeval structure, converts the value to TimeDelta
+// and stores it to |storage|. Returns 0 on success, or -1 on error with
+// setting system error number to errno.
+int SetTimeoutSocketOption(
+    const void* optval, socklen_t optlen, base::TimeDelta* storage) {
+  ALOG_ASSERT(storage);
+
+  int error = internal::VerifySetSocketOption(
+      optval, optlen, sizeof(timeval));  // NOLINT(runtime/sizeof)
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  const timeval& timeout = *static_cast<const timeval*>(optval);
+  error = internal::VerifyTimeoutSocketOption(timeout);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  *storage = internal::TimeValToTimeDelta(timeout);
+  return 0;
+}
+
+}  // namespace
+
+const int SocketStream::kUnknownSocketFamily = -1;
+
+SocketStream::SocketStream(int socket_family, int oflag)
+    : FileStream(oflag, ""), socket_family_(socket_family),
+      broadcast_(0), error_(0), reuse_addr_(0),
+      recv_buffer_size_(0), send_buffer_size_(0) {
+  memset(&linger_, 0, sizeof(linger_));
+  memset(&recv_timeout_, 0, sizeof(recv_timeout_));
+  memset(&send_timeout_, 0, sizeof(send_timeout_));
+  set_permission(PermissionInfo(arc::ProcessEmulator::GetUid(),
+                                true  /* is_writable */));
+  EnableListenerSupport();
+}
+
+SocketStream::~SocketStream() {
+}
+
+int SocketStream::fdatasync() {
+  errno = EINVAL;
+  return -1;
+}
+
+int SocketStream::fsync() {
+  errno = EINVAL;
+  return -1;
+}
+
+bool SocketStream::GetOptNameData(int level, int optname, socklen_t* len,
+                                  void** storage, const void* user_data,
+                                  socklen_t user_data_len) {
+  // We cannot use SIZEOF_AS_SOCKLEN(int) for this as the linter is
+  // confused by this and emits two warnings (readability/casting and
+  // readability/function).
+  static const socklen_t sizeof_int = sizeof(int);  // NOLINT(runtime/sizeof)
+  if (level == SOL_SOCKET) {
+    switch (optname) {
+      case SO_ERROR:
+        // TODO(igorc): Consider disabling SO_ERROR for setsockopt().
+        *storage = &error_;
+        *len = sizeof_int;
+        ALOG_ASSERT(*len == sizeof(error_));
+        return true;
+      case SO_REUSEADDR:
+        // TODO(crbug.com/233914): Pass this setting to Pepper. Now we claim
+        // this option is supported since failing would cause JDWP to fail
+        // during setup of the listening socket.
+        if (user_data != NULL && user_data_len >= sizeof_int &&
+            *(static_cast<const int*>(user_data)) != 0)
+          ALOGW("setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, ..) not supported");
+        *storage = &reuse_addr_;
+        *len = sizeof_int;
+        ALOG_ASSERT(*len == sizeof(reuse_addr_));
+        return true;
+      case SO_RCVBUF:
+      case SO_SNDBUF:
+        // TODO(crbug.com/242619): Support read/write buffer size options.
+        // Note that OS defaults could be rather large (200K-2M),
+        // so it is probably even more important to change the defaults.
+        // The return value should normally be doubled, but we will be
+        // returning the value that was originally set.
+        if (user_data != NULL && user_data_len >= sizeof_int) {
+          int value = *(static_cast<const int*>(user_data));
+          if (value < kMinSocketBuffSize || value > kMaxSocketBuffSize) {
+            return false;
+          }
+          ALOGW("Setting socket buffer size is not supported, opt=%d value=%d",
+                optname, value);
+        }
+        *storage = (optname == SO_RCVBUF ?
+            &recv_buffer_size_ : &send_buffer_size_);
+        *len = sizeof_int;
+        ALOG_ASSERT(*len == sizeof(recv_buffer_size_));
+        ALOG_ASSERT(*len == sizeof(send_buffer_size_));
+        return true;
+      case SO_BROADCAST:
+        *storage = &broadcast_;
+        *len = sizeof_int;
+        ALOG_ASSERT(*len == sizeof(broadcast_));
+        return true;
+      case SO_LINGER:
+        socklen_t expected_len = sizeof(struct linger);
+        *storage = &linger_;
+        *len = expected_len;
+        ALOG_ASSERT(*len == sizeof(linger_));
+        return true;
+    }
+  } else if (level == IPPROTO_IPV6) {
+    if (optname == IPV6_MULTICAST_HOPS) {
+      // IoBridge.java is setting this to work around a Linux kernel oddity.
+      // Merely ignore this value as Pepper does not support it.
+      *storage = NULL;
+      *len = sizeof_int;
+      return true;
+    }
+  }
+  return false;
+}
+
+int SocketStream::fstat(struct stat* out) {
+  memset(out, 0, sizeof(struct stat));
+  // Empirical values based on fstat of a connected socket on Linux-3.2.5.
+  out->st_mode = S_IFSOCK | 0777;
+  out->st_nlink = 1;
+  out->st_blksize = 4096;
+  return 0;
+}
+
+int SocketStream::getsockopt(int level, int optname, void* optval,
+                             socklen_t* optlen) {
+  if (level == SOL_SOCKET) {
+    // TODO(hidehiko): Merge other options to this switch.
+    switch (optname) {
+      case SO_RCVTIMEO:
+        return GetTimeoutSocketOption(recv_timeout_, optval, optlen);
+      case SO_SNDTIMEO:
+        return GetTimeoutSocketOption(send_timeout_, optval, optlen);
+    }
+  }
+
+  socklen_t len = 0;
+  void* storage = NULL;
+  if (optlen == NULL) {
+    errno = EFAULT;
+    return -1;
+  }
+  if (!GetOptNameData(level, optname, &len, &storage, NULL, 0)) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (*optlen < len && optval != NULL) {
+    errno = EINVAL;
+    return -1;
+  }
+  *optlen = len;
+  if (optval != NULL) {
+    if (storage != NULL) {
+      memcpy(optval, storage, len);
+    } else {
+      memset(optval, 0, len);
+    }
+  }
+  return 0;
+}
+
+int SocketStream::setsockopt(int level, int optname, const void* optval,
+                             socklen_t optlen) {
+  if (level == SOL_SOCKET) {
+    // TODO(hidehiko): Merge other options to this switch.
+    switch (optname) {
+      case SO_RCVTIMEO:
+        return SetTimeoutSocketOption(optval, optlen, &recv_timeout_);
+      case SO_SNDTIMEO:
+        return SetTimeoutSocketOption(optval, optlen, &send_timeout_);
+    }
+  }
+
+  socklen_t len = 0;
+  void* storage = NULL;
+  if (!GetOptNameData(level, optname, &len, &storage, optval, optlen)) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (optlen < len) {
+    errno = EINVAL;
+    return -1;
+  }
+  if (optval != NULL && storage != NULL)
+    memcpy(storage, optval, len);
+  return 0;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/socket_stream.h b/src/posix_translation/socket_stream.h
new file mode 100644
index 0000000..9526dd8
--- /dev/null
+++ b/src/posix_translation/socket_stream.h
@@ -0,0 +1,54 @@
+// Copyright 2014 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.
+//
+// FileStream for sockets.
+
+#ifndef POSIX_TRANSLATION_SOCKET_STREAM_H_
+#define POSIX_TRANSLATION_SOCKET_STREAM_H_
+
+#include <errno.h>
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "posix_translation/file_stream.h"
+
+namespace posix_translation {
+
+class SocketStream : public FileStream {
+ public:
+  static const int kUnknownSocketFamily;
+
+  // socket_family is the protocol family of this socket, such as AF_INET
+  // or AF_INET6. oflag is the flag passed via open(), and is just redirected
+  // to FileStream. See FileStream for more details.
+  SocketStream(int socket_family, int oflag);
+
+  virtual int fdatasync() OVERRIDE;
+  virtual int fstat(struct stat* out) OVERRIDE;
+  virtual int fsync() OVERRIDE;
+  virtual int getsockopt(int level, int optname, void* optval,
+                         socklen_t* optlen) OVERRIDE;
+  virtual int setsockopt(int level, int optname, const void* optval,
+                         socklen_t optlen) OVERRIDE;
+
+ protected:
+  virtual ~SocketStream();
+  virtual bool GetOptNameData(int level, int optname, socklen_t* len,
+                              void** storage, const void* user_data,
+                              socklen_t user_data_len);
+
+  int socket_family_;
+  int broadcast_;
+  int error_;
+  struct linger linger_;
+  int reuse_addr_;
+  base::TimeDelta recv_timeout_;
+  base::TimeDelta send_timeout_;
+  int recv_buffer_size_;
+  int send_buffer_size_;
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_SOCKET_STREAM_H_
diff --git a/src/posix_translation/socket_util.cc b/src/posix_translation/socket_util.cc
new file mode 100644
index 0000000..4ebcbe7
--- /dev/null
+++ b/src/posix_translation/socket_util.cc
@@ -0,0 +1,395 @@
+// Copyright 2014 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 "posix_translation/socket_util.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <algorithm>
+
+#include "common/alog.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/cpp/net_address.h"
+
+namespace posix_translation {
+namespace internal {
+
+// Because the trailing padding is not actually necessary, the min size of
+// the addrlen is slightly less than the size of the sockaddr_{in,in6}.
+const socklen_t kIPv4MinAddrLen =
+    offsetof(sockaddr_in, sin_addr) + sizeof(in_addr);
+const socklen_t kIPv6MinAddrLen =
+    offsetof(sockaddr_in6, sin6_addr) + sizeof(in6_addr);
+
+namespace {
+
+// Converts PP_NetAddress_IPv4 to sockaddr_in.
+void NetAddressIPv4ToSockAddrIn(
+    const PP_NetAddress_IPv4& net_address, sockaddr_in* saddr) {
+  saddr->sin_family = AF_INET;
+  // Copy the value as is to keep network byte order.
+  saddr->sin_port = net_address.port;
+  memcpy(&saddr->sin_addr.s_addr, net_address.addr, sizeof(net_address.addr));
+}
+
+// Convert PP_NetAddress_IPv4 to sockaddr_in6 as v4mapped address.
+void NetAddressIPv4ToSockAddrIn6V4Mapped(
+    const PP_NetAddress_IPv4& net_address, sockaddr_in6* saddr6) {
+  saddr6->sin6_family = AF_INET6;
+  // Copy the value as is to keep network byte order.
+  saddr6->sin6_port = net_address.port;
+  // V4Mapped address forms: 0::FFFF:xxx.yyy.zzz.www.
+  memset(saddr6->sin6_addr.s6_addr, 0, 10);  // Leading 10 bytes are 0.
+  saddr6->sin6_addr.s6_addr[10] = 0xFF;
+  saddr6->sin6_addr.s6_addr[11] = 0xFF;
+  memcpy(&saddr6->sin6_addr.s6_addr[12], net_address.addr,
+         sizeof(net_address.addr));
+}
+
+// Converts PP_NetAddress_IPv6 to sockaddr_in6.
+void NetAddressIPv6ToSockAddrIn6(
+    const PP_NetAddress_IPv6& net_address, sockaddr_in6* saddr6) {
+  saddr6->sin6_family = AF_INET6;
+  // Copy the value as is to keep network byte order.
+  saddr6->sin6_port = net_address.port;
+  memcpy(&saddr6->sin6_addr.s6_addr, net_address.addr,
+         sizeof(net_address.addr));
+}
+
+// Converts sockaddr_in to PP_NetAddress_IPv4.
+// sockaddr_in may have trailing padding, but it is ensured in this function
+// that the padding is not touched in this function.
+// In other words, although saddr has type sockaddr_in, the min size of the
+// buffer is IPv4MinAddrLen defined above, which can be smaller than
+// sizeof(sockaddr_in).
+void SockAddrInToNetAddressIPv4(
+    const sockaddr_in* saddr, PP_NetAddress_IPv4* net_address) {
+  ALOG_ASSERT(saddr->sin_family == AF_INET);
+  // Copy the value as is to keep network byte order.
+  net_address->port = saddr->sin_port;
+  memcpy(net_address->addr, &saddr->sin_addr.s_addr,
+         sizeof(net_address->addr));
+}
+
+// Converts sockaddr_in6 to PP_NetAddress_IPv6.
+// Similar to sockaddr_in, sockaddr_in6 also may have trailing padding, and
+// the min size of saddr6 is IPv6MinAddrLen. See also the comment for
+// SockAddrInToNetAddressIPv4.
+void SockAddrIn6ToNetAddressIPv6(
+    const sockaddr_in6* saddr6, PP_NetAddress_IPv6* net_address) {
+  ALOG_ASSERT(saddr6->sin6_family == AF_INET6);
+  // Copy the value as is to keep network byte order.
+  net_address->port = saddr6->sin6_port;
+  memcpy(net_address->addr, &saddr6->sin6_addr.s6_addr,
+         sizeof(net_address->addr));
+}
+
+}  // namespace
+
+int VerifyInputSocketAddress(
+    const sockaddr* addr, socklen_t addrlen, int address_family) {
+  ALOG_ASSERT(address_family == AF_INET || address_family == AF_INET6);
+
+  if (addrlen <= 0) {
+    ALOGW("addrlen is not positive: %d", addrlen);
+    return EINVAL;
+  }
+
+  if (!addr) {
+    ALOGW("Given addr is NULL");
+    return EFAULT;
+  }
+
+  // If the addr size is too small or too large, raise EINVAL.
+  const socklen_t kMinAddrLen =
+      address_family == AF_INET ? kIPv4MinAddrLen : kIPv6MinAddrLen;
+  if (addrlen < kMinAddrLen || addrlen > SIZEOF_AS_SOCKLEN(sockaddr_storage)) {
+    ALOGW("The addr has invalid size: %d, %d", address_family, addrlen);
+    return EINVAL;
+  }
+
+  if (addr->sa_family != address_family) {
+    ALOGW("The family is differnt from what is expected: %d, %d",
+          addr->sa_family, address_family);
+    // Note: for bind(), there seems no spec on man in this case.
+    // However, as same as connect(), practically bind() raises
+    // EAFNOSUPPORT in this case.
+    return EAFNOSUPPORT;
+  }
+
+  return 0;
+}
+
+int VerifyOutputSocketAddress(
+    const sockaddr* addr, const socklen_t* addrlen) {
+  if (!addrlen) {
+    return EFAULT;
+  }
+
+  if (*addrlen < 0) {
+    return EINVAL;
+  }
+
+  // Note that if addrlen is 0, addr can be NULL, because we will not copy
+  // the data to it.
+  if (*addrlen != 0 && addr == NULL) {
+    return EFAULT;
+  }
+
+  return 0;
+}
+
+void CopySocketAddress(
+    const sockaddr_storage& address, sockaddr* name, socklen_t* namelen) {
+  int family = address.ss_family;
+  ALOG_ASSERT(family == AF_INET || family == AF_INET6);
+  const socklen_t address_length =
+      (family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
+  if (name) {
+    memcpy(name, &address, std::min(*namelen, address_length));
+  }
+  *namelen = address_length;
+}
+
+bool SocketAddressEqual(
+    const sockaddr_storage& addr1, const sockaddr_storage& addr2) {
+  if (addr1.ss_family != addr2.ss_family)
+    return false;
+
+  if (addr1.ss_family == AF_INET) {
+    const sockaddr_in& saddr_in1 = reinterpret_cast<const sockaddr_in&>(addr1);
+    const sockaddr_in& saddr_in2 = reinterpret_cast<const sockaddr_in&>(addr2);
+    return (saddr_in1.sin_port == saddr_in2.sin_port) &&
+           (saddr_in1.sin_addr.s_addr == saddr_in2.sin_addr.s_addr);
+  }
+
+  if (addr1.ss_family == AF_INET6) {
+    const sockaddr_in6& saddr_in6_1 =
+        reinterpret_cast<const sockaddr_in6&>(addr1);
+    const sockaddr_in6& saddr_in6_2 =
+        reinterpret_cast<const sockaddr_in6&>(addr2);
+    return (saddr_in6_1.sin6_port == saddr_in6_2.sin6_port) &&
+           (memcmp(&saddr_in6_1.sin6_addr, &saddr_in6_2.sin6_addr,
+                   sizeof(in6_addr)) == 0);
+  }
+
+  // Unknown family.
+  ALOGE("SocketAddressEqual Unknown socket family: %d", addr1.ss_family);
+  return false;
+}
+
+bool NetAddressToSockAddrStorage(
+    const pp::NetAddress& net_address,
+    int dest_family, bool allow_v4mapped, sockaddr_storage* storage) {
+  ALOG_ASSERT(dest_family == AF_UNSPEC ||
+              dest_family == AF_INET ||
+              dest_family == AF_INET6);
+  memset(storage, 0, sizeof(sockaddr_storage));
+  switch (net_address.GetFamily()) {
+    case PP_NETADDRESS_FAMILY_IPV4: {
+      // If IPv6 address is required but the v4map is prohibited, there is
+      // no way to return the address.
+      if (dest_family == AF_INET6 && !allow_v4mapped)
+        return false;
+
+      PP_NetAddress_IPv4 ipv4 = {};
+      if (!net_address.DescribeAsIPv4Address(&ipv4)) {
+        return false;
+      }
+      if (dest_family == AF_INET6) {
+        NetAddressIPv4ToSockAddrIn6V4Mapped(
+            ipv4, reinterpret_cast<sockaddr_in6*>(storage));
+      } else {
+        NetAddressIPv4ToSockAddrIn(
+            ipv4, reinterpret_cast<sockaddr_in*>(storage));
+      }
+      return true;
+    }
+    case PP_NETADDRESS_FAMILY_IPV6: {
+      // IPv6 address cannot return in IPv4 address format.
+      if (dest_family == AF_INET)
+        return false;
+
+      PP_NetAddress_IPv6 ipv6 = {};
+      if (!net_address.DescribeAsIPv6Address(&ipv6)) {
+        return false;
+      }
+      NetAddressIPv6ToSockAddrIn6(
+          ipv6, reinterpret_cast<sockaddr_in6*>(storage));
+      return true;
+    }
+    default:
+      return false;
+  }
+}
+
+pp::NetAddress SockAddrToNetAddress(
+    const pp::InstanceHandle& instance, const sockaddr* saddr) {
+  ALOG_ASSERT(saddr->sa_family == AF_INET || saddr->sa_family == AF_INET6);
+  if (saddr->sa_family == AF_INET) {
+    PP_NetAddress_IPv4 ipv4;
+    SockAddrInToNetAddressIPv4(
+        reinterpret_cast<const sockaddr_in*>(saddr), &ipv4);
+    return pp::NetAddress(instance, ipv4);
+  }
+
+  if (saddr->sa_family == AF_INET6) {
+    PP_NetAddress_IPv6 ipv6;
+    SockAddrIn6ToNetAddressIPv6(
+        reinterpret_cast<const sockaddr_in6*>(saddr), &ipv6);
+    return pp::NetAddress(instance, ipv6);
+  }
+
+  return pp::NetAddress();
+}
+
+// TODO(hidehiko): Clean up the code as the some part of code inside this
+// function can be shared with above methods.
+bool StringToSockAddrStorage(
+    const char* hostname, uint16_t port,
+    int dest_family, bool allow_v4mapped, sockaddr_storage* storage) {
+  ALOG_ASSERT(dest_family == AF_UNSPEC ||
+              dest_family == AF_INET ||
+              dest_family == AF_INET6);
+  memset(storage, 0, sizeof(*storage));
+
+  in6_addr addr6;
+  if (inet_pton(AF_INET6, hostname, &addr6) == 1) {
+    if (dest_family == AF_INET)
+      return false;
+
+    // TODO(crbug.com/243012): handle scope_id
+    sockaddr_in6* saddr6 = reinterpret_cast<sockaddr_in6*>(storage);
+    saddr6->sin6_family = AF_INET6;
+    saddr6->sin6_port = port;
+    memcpy(saddr6->sin6_addr.s6_addr, &addr6, sizeof(addr6));
+    return true;
+  }
+
+  in_addr addr4;
+  if (inet_pton(AF_INET, hostname, &addr4) == 1) {
+    if (dest_family == AF_INET6) {
+      // Convert to V4Mapped address.
+      if (!allow_v4mapped)
+        return false;
+
+      sockaddr_in6* saddr6 = reinterpret_cast<sockaddr_in6*>(storage);
+      saddr6->sin6_family = AF_INET6;
+      saddr6->sin6_port = port;
+      // V4Mapped address forms: 0::FFFF:xxx.yyy.zzz.www.
+      memset(saddr6->sin6_addr.s6_addr, 0, 10);  // Leading 10 bytes are 0.
+      saddr6->sin6_addr.s6_addr[10] = 0xFF;
+      saddr6->sin6_addr.s6_addr[11] = 0xFF;
+      memcpy(&saddr6->sin6_addr.s6_addr[12], &addr4, sizeof(addr4));
+      return true;
+    }
+
+    sockaddr_in* saddr = reinterpret_cast<sockaddr_in*>(storage);
+    saddr->sin_family = AF_INET;
+    saddr->sin_port = port;
+    memcpy(&saddr->sin_addr.s_addr, &addr4, sizeof(addr4));
+    return true;
+  }
+
+  // Failed to convert into sockaddr_storage.
+  return false;
+}
+
+uint16_t ServiceNameToPort(const char* service_name) {
+  if (service_name == NULL)
+    return 0;
+
+  char* end;
+  long int port = strtol(service_name, &end, 10);  // NOLINT(runtime/int)
+  if (port < 0 || port >= 65536 || *end != '\0') {
+    ALOGW("Unsupported network service name %s", service_name);
+    return 0;
+  }
+
+  return htons(static_cast<uint16_t>(port));
+}
+
+addrinfo* SockAddrStorageToAddrInfo(
+    const sockaddr_storage& storage, int socktype, int protocol,
+    const std::string& name) {
+  ALOG_ASSERT(storage.ss_family == AF_INET || storage.ss_family == AF_INET6);
+  size_t addrlen = storage.ss_family == AF_INET ?
+      sizeof(sockaddr_in) : sizeof(sockaddr_in6);
+  sockaddr* saddr = static_cast<sockaddr*>(malloc(addrlen));
+  memcpy(saddr, &storage, addrlen);
+
+  addrinfo* info = static_cast<addrinfo*>(malloc(sizeof(addrinfo)));
+  info->ai_flags = 0;
+  info->ai_family = storage.ss_family;
+  info->ai_socktype = socktype ? socktype : SOCK_STREAM;
+  info->ai_protocol = protocol;
+  info->ai_addrlen = addrlen;
+  info->ai_addr = saddr;
+  // Use malloc + memcpy, instead of stdup. Valgrind seems to detect strdup
+  // has some invalid memory access.
+  info->ai_canonname = static_cast<char*>(malloc(name.size() + 1));
+  memcpy(info->ai_canonname, name.c_str(), name.size() + 1);
+  info->ai_next = NULL;
+  return info;
+}
+
+void ReleaseAddrInfo(addrinfo* info) {
+  free(info->ai_canonname);
+  free(info->ai_addr);
+  free(info);
+}
+
+int VerifyGetSocketOption(const void* optval, const socklen_t* optlen) {
+  if (!optlen) {
+    return EFAULT;
+  }
+
+  if (*optlen < 0) {
+    return EINVAL;
+  }
+
+  // Note that if optlen is 0, optval can be NULL, because we will not copy
+  // the data to it.
+  if (*optlen != 0 && !optval) {
+    return EFAULT;
+  }
+
+  return 0;
+}
+
+int VerifySetSocketOption(
+    const void* optval, socklen_t optlen, socklen_t expected_optlen) {
+  if (optlen < expected_optlen) {
+    return EINVAL;
+  }
+
+  if (!optval) {
+    return EFAULT;
+  }
+
+  return 0;
+}
+
+int VerifyTimeoutSocketOption(const timeval& timeout) {
+  // tv_usec must be in the range of [0, 1000000).
+  if (timeout.tv_usec < 0 || timeout.tv_usec >= 1000000) {
+    return EDOM;
+  }
+  return 0;
+}
+
+void CopySocketOption(const void* storage, socklen_t storage_length,
+                      void* optval, socklen_t* optlen) {
+  ALOG_ASSERT(storage);
+  *optlen = std::min(*optlen, storage_length);
+  if (optval)
+    memcpy(optval, storage, *optlen);
+}
+
+}  // namespace internal
+}  // namespace posix_translation
diff --git a/src/posix_translation/socket_util.h b/src/posix_translation/socket_util.h
new file mode 100644
index 0000000..eeda690
--- /dev/null
+++ b/src/posix_translation/socket_util.h
@@ -0,0 +1,139 @@
+// Copyright 2014 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 POSIX_TRANSLATION_SOCKET_UTIL_H_
+#define POSIX_TRANSLATION_SOCKET_UTIL_H_
+
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string>
+
+// In bionic, socklen_t is int so we cannot compare socklen_t with the
+// result of sizeof. With this macro, we can absorb this difference.
+#define SIZEOF_AS_SOCKLEN(x) static_cast<socklen_t>(sizeof(x))
+
+namespace base {
+class TimeDelta;
+}  // namespace base
+
+namespace pp {
+class InstanceHandle;
+class NetAddress;
+}  // namespace pp
+
+namespace posix_translation {
+
+class VirtualFileSystem;
+
+namespace internal {
+
+// Common verification of the input (sockaddr, socklen_t) argument (such as
+// arguments for bind() or connect()).
+// Returns 0 on success, or a system error number (e.g. EINVAL). This does
+// not modify errno.
+int VerifyInputSocketAddress(
+    const sockaddr* addr, socklen_t addrlen, int address_family);
+
+// Common verification of the output (sockaddr, socklen_t) argument (such as
+// arguments for accept() or getsockname()).
+// Returns 0 on success, or a system error number (e.g. EINVAL). This does
+// not modify errno.
+int VerifyOutputSocketAddress(
+    const sockaddr* addr, const socklen_t* addrlen);
+
+// Copies the content of the address to the name, and sets the size of the
+// original address to namelen. The size is automatically calculated
+// based on the socket family of address.
+// Caller must set namelen to the size of name before calling this.
+// If the size is not enough, the name will have truncated result, but namelen
+// will have the size of the result. (i.e., namelen will have the bigger value
+// than the input).
+// address must represent an address for sockaddr_in or sockaddr_in6. name and
+// namelen must pass the verification done by VerifyOutputSocketAddress().
+void CopySocketAddress(
+    const sockaddr_storage& address, sockaddr* name, socklen_t* namelen);
+
+// Returns whether addr1 and addr2 have same family, port and address.
+// Returns false when the family is different from AF_INET or AF_INET6
+// even if these are same, just because this function only supports those
+// families.
+bool SocketAddressEqual(
+    const sockaddr_storage& addr1, const sockaddr_storage& addr2);
+
+// Converts pp::NetAddress to sockaddr_storage.
+// Returns whether the net_address is successfully converted into the storage.
+// The dest_family must be one of AF_UNSPEC, AF_INET or AF_INET6. If AF_UNSPEC
+// is given, returned storage will have either AF_INET or AF_INET6 address.
+// The allow_v4mapped is effective only if dest_family == AF_INET6, If it is
+// set and the net_address represents an IPv4 address, the returned storage
+// will have the IPv6 address representing the given IPv4 address.
+bool NetAddressToSockAddrStorage(
+    const pp::NetAddress& net_address,
+    int dest_family, bool allow_v4mapped, sockaddr_storage* storage);
+
+// Converts sockaddr to pp::NetAddress.
+// saddr should be verified by VerifyInputSocketAddress in advance, in order
+// to avoid illegal memory access.
+// The given instance will be used to create a new pp::NetAdress instance.
+pp::NetAddress SockAddrToNetAddress(
+    const pp::InstanceHandle& instance, const sockaddr* saddr);
+
+// Converts a stringified IPv4 or IPV6 address |hostname| (e.g. "127.0.0.1" or
+// "::1") and a port to sockaddr_storage. Returns whether they are
+// successfully converted.
+// This function works similar to NetAddressPrivateToSockAddrStorage() declared
+// above, but different input type. See its comments for how dest_family and
+// allow_v4mapped work.
+// Note that this function does not resolve the host name (e.g.
+// "www.google.co.jp"). Also note that port must be in the network-byte-order.
+bool StringToSockAddrStorage(
+    const char* hostname, uint16_t port,
+    int dest_family, bool allow_v4mapped, sockaddr_storage* storage);
+
+// Parses the given service_name to a port number. On error, returns 0.
+// Returned port is in the network-byte-order.
+// This function can parse only numbers, e.g. "80" or "22", but not
+// named service, such as "http".
+uint16_t ServiceNameToPort(const char* service_name);
+
+// Converts sockaddr_storage, socktype, protocol and name into addrinfo
+// structure. The storage must have AF_INET or AF_INET6 socket address.
+// If socktype is set to 0, returned addrinfo will have SOCK_STREAM as default
+// value.
+// The result resource must be released by ReleaseAddrInfo declared below.
+addrinfo* SockAddrStorageToAddrInfo(
+    const sockaddr_storage& storage, int socktype, int protocol,
+    const std::string& name);
+
+// Releases the info, allocated by SockAddrStorageToAddrInfo declared above.
+void ReleaseAddrInfo(addrinfo* info);
+
+// Common verification for getsockopt().
+// Returns 0 on success, or a system error number (e.g. EINVAL). This does not
+// modify errno.
+int VerifyGetSocketOption(const void* optval, const socklen_t* optlen);
+
+// Common verification for the setsockopt().
+// Returns 0 on success, or a system error number (e.g. EINVAL). This does not
+// modify errno.
+int VerifySetSocketOption(
+    const void* optval, socklen_t optlen, socklen_t expected_optlen);
+
+// Verification for SO_RCVTIMEO and SO_SNDTIMEO.
+// Returns 0 on success, or a system error number (e.g. EINVAL). This does not
+// modify errno.
+int VerifyTimeoutSocketOption(const timeval& timeout);
+
+// Copies the content of |storage| whose size is |storage_size| to |optval|.
+// The size of copied content is min(storage_size, optlen).
+// Upon completion, |optlen| will be set to the copied content size.
+void CopySocketOption(const void* storage, socklen_t storage_size,
+                      void* optval, socklen_t* optlen);
+
+}  // namespace internal
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_SOCKET_UTIL_H_
diff --git a/src/posix_translation/socket_util_test.cc b/src/posix_translation/socket_util_test.cc
new file mode 100644
index 0000000..284b488
--- /dev/null
+++ b/src/posix_translation/socket_util_test.cc
@@ -0,0 +1,633 @@
+// Copyright 2014 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 "posix_translation/socket_util.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/alog.h"
+#include "gtest/gtest.h"
+
+namespace posix_translation {
+namespace internal {
+namespace {
+
+struct AddrInfoDeleter {
+  void operator()(addrinfo* info) {
+    ReleaseAddrInfo(info);
+  }
+};
+
+// Returns true if buffer contains only 0. Otherwise false.
+bool IsFilledByZero(const void* buffer, size_t length) {
+  const char* ptr = reinterpret_cast<const char*>(buffer);
+  for (size_t i = 0; i < length; ++i) {
+    if (ptr[i])
+      return false;
+  }
+  return true;
+}
+
+// htons is defined by macro in Bionic, so it confuses gtest template codes.
+// This is just a wrapper to avoid the compile errors.
+uint16_t HostToNetShort(uint16_t value) {
+  return htons(value);
+}
+
+}  // namespace
+
+TEST(SocketUtilTest, VerifyInputSocketAddressIPv4) {
+  sockaddr_in addr_in = {};
+  addr_in.sin_family = AF_INET;
+  const sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_in);
+
+  // Typical usage.
+  EXPECT_EQ(0, VerifyInputSocketAddress(addr, sizeof(sockaddr_in), AF_INET));
+
+  // Test for addrlen.
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(NULL, 0, AF_INET));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 0, AF_INET));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(NULL, -1, AF_INET));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, -1, AF_INET));
+
+  // Test for NULL check of addr.
+  EXPECT_EQ(EFAULT, VerifyInputSocketAddress(NULL, 1, AF_INET));
+  EXPECT_EQ(EFAULT,
+            VerifyInputSocketAddress(NULL, sizeof(sockaddr_in), AF_INET));
+
+  // If the size is not enough, EINVAL is expected.
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 1, AF_INET));
+  // The min size for INET is 8.
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 7, AF_INET));
+  EXPECT_EQ(0, VerifyInputSocketAddress(addr, 8, AF_INET));
+  // The max size for INET is sizeof(sockaddr_storage).
+  char too_large_addr[sizeof(sockaddr_storage) + 1] = {};
+  memcpy(too_large_addr, &addr_in, sizeof(addr_in));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(
+      reinterpret_cast<sockaddr*>(too_large_addr),
+      sizeof(too_large_addr), AF_INET));
+
+  // Set other family.
+  addr_in.sin_family = AF_UNSPEC;
+  EXPECT_EQ(EAFNOSUPPORT,
+            VerifyInputSocketAddress(addr, sizeof(sockaddr_in), AF_INET));
+
+  addr_in.sin_family = AF_INET6;
+  EXPECT_EQ(EAFNOSUPPORT,
+            VerifyInputSocketAddress(addr, sizeof(sockaddr_in), AF_INET));
+}
+
+TEST(SocketUtilTest, VerifyInputSocketAddressIPv6) {
+  sockaddr_in6 addr_in6 = {};
+  addr_in6.sin6_family = AF_INET6;
+  const sockaddr* addr = reinterpret_cast<sockaddr*>(&addr_in6);
+
+  // Typical usage.
+  EXPECT_EQ(
+      0, VerifyInputSocketAddress(addr, sizeof(sockaddr_in6), AF_INET6));
+
+  // Test for addrlen.
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(NULL, 0, AF_INET6));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 0, AF_INET6));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(NULL, -1, AF_INET6));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, -1, AF_INET6));
+
+  // Test for NULL check of addr.
+  EXPECT_EQ(EFAULT, VerifyInputSocketAddress(NULL, 1, AF_INET6));
+  EXPECT_EQ(EFAULT,
+            VerifyInputSocketAddress(NULL, sizeof(sockaddr_in6), AF_INET6));
+
+  // If the size is not enough, EINVAL is expected.
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 1, AF_INET6));
+  // The min size for INET6 is 24
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(addr, 23, AF_INET6));
+  EXPECT_EQ(0, VerifyInputSocketAddress(addr, 24, AF_INET6));
+  // The max size for INET6 is sizeof(sockaddr_storage).
+  char too_large_addr[sizeof(sockaddr_storage) + 1] = {};
+  memcpy(too_large_addr, &addr_in6, sizeof(addr_in6));
+  EXPECT_EQ(EINVAL, VerifyInputSocketAddress(
+      reinterpret_cast<sockaddr*>(too_large_addr),
+      sizeof(too_large_addr), AF_INET6));
+
+  // Set other family.
+  addr_in6.sin6_family = AF_UNSPEC;
+  EXPECT_EQ(EAFNOSUPPORT,
+            VerifyInputSocketAddress(addr, sizeof(sockaddr_in6), AF_INET6));
+
+  addr_in6.sin6_family = AF_INET;
+  EXPECT_EQ(EAFNOSUPPORT,
+            VerifyInputSocketAddress(addr, sizeof(sockaddr_in6), AF_INET6));
+}
+
+TEST(SocketUtilTest, VerifyOutputSocketAddress) {
+  sockaddr_storage storage = {};
+  const sockaddr* addr = reinterpret_cast<sockaddr*>(&storage);
+
+  // Typical usage.
+  socklen_t addrlen = sizeof(storage);
+  EXPECT_EQ(0, VerifyOutputSocketAddress(addr, &addrlen));
+  addrlen = sizeof(sockaddr_in);
+  EXPECT_EQ(0, VerifyOutputSocketAddress(addr, &addrlen));
+  addrlen = sizeof(sockaddr_in6);
+  EXPECT_EQ(0, VerifyOutputSocketAddress(addr, &addrlen));
+
+  // Or, addrlen can be small or even 0.
+  addrlen = 1;
+  EXPECT_EQ(0, VerifyOutputSocketAddress(addr, &addrlen));
+  addrlen = 0;
+  EXPECT_EQ(0, VerifyOutputSocketAddress(addr, &addrlen));
+
+  // addr can be NULL only when addrlen is 0.
+  addrlen = 0;
+  EXPECT_EQ(0, VerifyOutputSocketAddress(NULL, &addrlen));
+  addrlen = 1;
+  EXPECT_EQ(EFAULT, VerifyOutputSocketAddress(NULL, &addrlen));
+
+  // addrlen cannot be NULL or negative.
+  EXPECT_EQ(EFAULT, VerifyOutputSocketAddress(addr, NULL));
+  addrlen = -1;
+  EXPECT_EQ(EINVAL, VerifyOutputSocketAddress(addr, &addrlen));
+  EXPECT_EQ(EINVAL, VerifyOutputSocketAddress(NULL, &addrlen));
+}
+
+TEST(SocketUtilTest, CopySocketAddressIPv4) {
+  // Fake IPv4 address.
+  sockaddr_in addr_in = {};
+  addr_in.sin_family = AF_INET;
+  addr_in.sin_port = htons(12345);
+  addr_in.sin_addr.s_addr = htonl(0x12345678);
+
+  sockaddr_storage storage = {};
+  memcpy(&storage, &addr_in, sizeof(addr_in));
+
+  sockaddr_storage result = {};
+  socklen_t result_len;
+
+  // Test with the buffer size equal to socketaddr_storage.
+  result_len = sizeof(result);
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in, &result, sizeof(addr_in)));
+
+  // Test with the buffer size equal to sockaddr_in.
+  memset(&result, 0, sizeof(result));
+  result_len = sizeof(addr_in);
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in, &result, sizeof(addr_in)));
+
+  // Test with the buffer size smaller than sockaddr_in.
+  memset(&result, 0, sizeof(result));
+  const socklen_t kHalfSize = sizeof(addr_in) / 2;
+  result_len = kHalfSize;
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in, &result, kHalfSize));
+  // Make sure the remaining half is untouched.
+  EXPECT_TRUE(
+      IsFilledByZero(reinterpret_cast<const char*>(&result) + kHalfSize,
+                     sizeof(result) - kHalfSize));
+
+  // Test with the buffer size of zero.
+  memset(&result, 0, sizeof(result));
+  result_len = 0;
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in), result_len);
+  EXPECT_TRUE(IsFilledByZero(&result, sizeof(result)));
+
+  // If result_len is 0, the second param can be NULL.
+  memset(&result, 0, sizeof(result));
+  result_len = 0;
+  CopySocketAddress(storage, NULL, &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in), result_len);
+}
+
+TEST(SocketUtilTest, CopySocketAddressIPv6) {
+  // Fake IPv6 address.
+  sockaddr_in6 addr_in6 = {};
+  addr_in6.sin6_family = AF_INET6;
+  addr_in6.sin6_port = htons(54321);
+  const in6_addr kDummyIPv6Addr = {{{
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+  }}};
+  addr_in6.sin6_addr = kDummyIPv6Addr;
+
+  sockaddr_storage storage = {};
+  memcpy(&storage, &addr_in6, sizeof(addr_in6));
+
+  sockaddr_storage result = {};
+  socklen_t result_len;
+
+  // Test with the buffer size equal to sockaddr_storage.
+  result_len = sizeof(result);
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in6), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in6, &result, sizeof(addr_in6)));
+
+  // Test with the buffer size equal to sockaddr_in6.
+  memset(&result, 0, sizeof(result));
+  result_len = sizeof(addr_in6);
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in6), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in6, &result, sizeof(addr_in6)));
+
+  // Test with the buffer size smaller than sockaddr_in6.
+  memset(&result, 0, sizeof(result));
+  const socklen_t kHalfSize = sizeof(addr_in6) / 2;
+  result_len = kHalfSize;
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in6), result_len);
+  EXPECT_EQ(0, memcmp(&addr_in6, &result, kHalfSize));
+  // Make sure the remaining half is untouched.
+  EXPECT_TRUE(
+      IsFilledByZero(reinterpret_cast<const char*>(&result) + kHalfSize,
+                     sizeof(result) - kHalfSize));
+
+  // Test with the buffer size of zero.
+  memset(&result, 0, sizeof(result));
+  result_len = 0;
+  CopySocketAddress(
+      storage, reinterpret_cast<sockaddr*>(&result), &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in6), result_len);
+  EXPECT_TRUE(IsFilledByZero(&result, sizeof(result)));
+
+  // If result_len is 0, the second param can be NULL.
+  memset(&result, 0, sizeof(result));
+  result_len = 0;
+  CopySocketAddress(storage, NULL, &result_len);
+  EXPECT_EQ(SIZEOF_AS_SOCKLEN(addr_in6), result_len);
+}
+
+TEST(SocketUtilTest, SocketAddressEqualIPv4) {
+  sockaddr_storage addr1 = {};
+  sockaddr_storage addr2 = {};
+
+  addr1.ss_family = AF_INET;
+  reinterpret_cast<sockaddr_in&>(addr1).sin_port = htons(8080);
+  reinterpret_cast<sockaddr_in&>(addr1).sin_addr.s_addr =
+      htonl(0x7F000001);  // 127.0.0.1
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  EXPECT_TRUE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if family is different.
+  addr2.ss_family = AF_UNIX;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+  addr2.ss_family = AF_UNSPEC;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+  addr2.ss_family = AF_INET6;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if port is different.
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  reinterpret_cast<sockaddr_in&>(addr2).sin_port = htons(12345);
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if address is different.
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  reinterpret_cast<sockaddr_in&>(addr2).sin_addr.s_addr =
+      htonl(0xC0A80001);  // 192.168.0.1
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+}
+
+TEST(SocketUtilTest, SocketAddressEqualIPv6) {
+  sockaddr_storage addr1 = {};
+  sockaddr_storage addr2 = {};
+
+  addr1.ss_family = AF_INET6;
+  reinterpret_cast<sockaddr_in6&>(addr1).sin6_port = htons(8080);
+  const in6_addr kAddress = {{{
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+      0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+  }}};
+  reinterpret_cast<sockaddr_in6&>(addr1).sin6_addr = kAddress;
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  EXPECT_TRUE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if family is different.
+  addr2.ss_family = AF_UNIX;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+  addr2.ss_family = AF_UNSPEC;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+  addr2.ss_family = AF_INET;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if port is different.
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  reinterpret_cast<sockaddr_in6&>(addr2).sin6_port = htons(12345);
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+
+  // Not equal if address is different.
+  memcpy(&addr2, &addr1, sizeof(sockaddr_storage));
+  const in6_addr kDifferentAddress = {{{
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+  }}};
+  reinterpret_cast<sockaddr_in6&>(addr2).sin6_addr = kDifferentAddress;
+  EXPECT_FALSE(SocketAddressEqual(addr1, addr2));
+}
+
+TEST(SocketUtilTest, StringToSockAddrStorageUnspec) {
+  // Parse IPv4 address.
+  sockaddr_storage storage;
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "127.0.0.1", htons(22), AF_UNSPEC, false, &storage));
+  {
+    sockaddr_in saddr4 = {};
+    saddr4.sin_family = AF_INET;
+    saddr4.sin_port = htons(22);
+    saddr4.sin_addr.s_addr = htonl(0x7F000001);
+    EXPECT_EQ(0, memcmp(&storage, &saddr4, sizeof(saddr4)));
+  }
+
+  // Parse IPv6 address.
+  memset(&storage, 0x5A, sizeof(storage));  // Fill garbled data.
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "::1", htons(22), AF_UNSPEC, false, &storage));
+  {
+    sockaddr_in6 saddr6 = {};
+    saddr6.sin6_family = AF_INET6;
+    saddr6.sin6_port = htons(22);
+    const in6_addr kExpectAddr = {{{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+    }}};
+    saddr6.sin6_addr = kExpectAddr;
+    EXPECT_EQ(0, memcmp(&storage, &saddr6, sizeof(saddr6)));
+  }
+
+  // allow_v4map is not effective for AF_UNSPEC.
+  memset(&storage, 0x5A, sizeof(storage));  // Fill garbled data.
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "127.0.0.1", htons(22), AF_UNSPEC, true, &storage));
+  {
+    sockaddr_in saddr4 = {};
+    saddr4.sin_family = AF_INET;
+    saddr4.sin_port = htons(22);
+    saddr4.sin_addr.s_addr = htonl(0x7F000001);
+    EXPECT_EQ(0, memcmp(&storage, &saddr4, sizeof(saddr4)));
+  }
+
+  // The address must form stringified IP.
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "www.google.com", htons(80), AF_UNSPEC, false, &storage));
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "localhost", htons(12345), AF_UNSPEC, false, &storage));
+}
+
+TEST(SocketUtilTest, StringToSockAddrStorageIPv4) {
+  sockaddr_storage storage;
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "127.0.0.1", htons(22), AF_INET, false, &storage));
+  {
+    sockaddr_in saddr4 = {};
+    saddr4.sin_family = AF_INET;
+    saddr4.sin_port = htons(22);
+    saddr4.sin_addr.s_addr = htonl(0x7F000001);
+    EXPECT_EQ(0, memcmp(&storage, &saddr4, sizeof(saddr4)));
+  }
+
+  // The address must form stringified IP.
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "www.google.com", htons(80), AF_INET, false, &storage));
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "localhost", htons(12345), AF_INET, false, &storage));
+
+  // IPv6 address is not accepted.
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "::1", htons(8080), AF_INET, false, &storage));
+}
+
+TEST(SocketUtilTest, StrAddrToSockAddrStorageIPv6) {
+  // Parse IPv6 address.
+  sockaddr_storage storage;
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "::1", htons(22), AF_UNSPEC, false, &storage));
+  {
+    sockaddr_in6 saddr6 = {};
+    saddr6.sin6_family = AF_INET6;
+    saddr6.sin6_port = htons(22);
+    const in6_addr kExpectAddr = {{{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+    }}};
+    saddr6.sin6_addr = kExpectAddr;
+    EXPECT_EQ(0, memcmp(&storage, &saddr6, sizeof(saddr6)));
+  }
+
+  // IPv4 address is not accepted, if allow_v4mapped is set false.
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "127.0.0.1", htons(22), AF_INET6, false, &storage));
+
+  // V4Mapped address is returend if allow_v4mapped is set true.
+  memset(&storage, 0x5A, sizeof(storage));  // Fill garbled data.
+  ASSERT_TRUE(StringToSockAddrStorage(
+      "127.0.0.1", htons(22), AF_INET6, true, &storage));
+  {
+    sockaddr_in6 saddr6 = {};
+    saddr6.sin6_family = AF_INET6;
+    saddr6.sin6_port = htons(22);
+    const in6_addr kExpectAddr = {{{
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0x7F, 0, 0, 1
+    }}};
+    saddr6.sin6_addr = kExpectAddr;
+    EXPECT_EQ(0, memcmp(&storage, &saddr6, sizeof(saddr6)));
+  }
+
+  // The address must form stringified IP.
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "ipv6.google.com", htons(80), AF_INET, false, &storage));
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "ipv6.google.com", htons(80), AF_INET, true, &storage));
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "localhost", htons(12345), AF_INET, false, &storage));
+  ASSERT_FALSE(StringToSockAddrStorage(
+      "localhost", htons(12345), AF_INET, true, &storage));
+}
+
+TEST(SocketUtilTest, ServiceNameToPort) {
+  // Common use cases.
+  EXPECT_EQ(HostToNetShort(0), ServiceNameToPort("0"));
+  EXPECT_EQ(HostToNetShort(22), ServiceNameToPort("22"));
+  EXPECT_EQ(HostToNetShort(80), ServiceNameToPort("80"));
+  EXPECT_EQ(HostToNetShort(443), ServiceNameToPort("443"));
+  EXPECT_EQ(HostToNetShort(8080), ServiceNameToPort("8080"));
+  EXPECT_EQ(HostToNetShort(65535), ServiceNameToPort("65535"));
+
+  // Returns 0 for NULL.
+  EXPECT_EQ(0, ServiceNameToPort(NULL));
+
+  // Out of range.
+  EXPECT_EQ(0, ServiceNameToPort("-1"));
+  EXPECT_EQ(0, ServiceNameToPort("65536"));
+  EXPECT_EQ(0, ServiceNameToPort("1000000"));
+
+  // Currently named-services are not supported.
+  EXPECT_EQ(0, ServiceNameToPort("http"));
+  EXPECT_EQ(0, ServiceNameToPort("https"));
+  EXPECT_EQ(0, ServiceNameToPort("ftp"));
+  EXPECT_EQ(0, ServiceNameToPort("ssh"));
+}
+
+TEST(SocketUtilTest, SockAddrStorageToAddrInfo) {
+  {
+    sockaddr_storage storage;
+    ASSERT_TRUE(StringToSockAddrStorage(
+        "127.0.0.1", htons(22), AF_UNSPEC, false, &storage));
+    scoped_ptr<addrinfo, AddrInfoDeleter> info(SockAddrStorageToAddrInfo(
+        storage, SOCK_STREAM, IPPROTO_IP, "localhost"));
+    ASSERT_TRUE(info.get());
+    EXPECT_EQ(0, info->ai_flags);
+    EXPECT_EQ(AF_INET, info->ai_family);
+    EXPECT_EQ(SOCK_STREAM, info->ai_socktype);
+    EXPECT_EQ(IPPROTO_IP, info->ai_protocol);
+    EXPECT_EQ(SIZEOF_AS_SOCKLEN(sockaddr_in), info->ai_addrlen);
+    EXPECT_EQ(0, memcmp(&storage, info->ai_addr, info->ai_addrlen));
+    EXPECT_STREQ("localhost", info->ai_canonname);
+    EXPECT_EQ(NULL, info->ai_next);
+  }
+
+  {
+    sockaddr_storage storage;
+    ASSERT_TRUE(StringToSockAddrStorage(
+        "::1", htons(22), AF_UNSPEC, false, &storage));
+    scoped_ptr<addrinfo, AddrInfoDeleter> info(SockAddrStorageToAddrInfo(
+        storage, SOCK_STREAM, IPPROTO_IP, "localhost"));
+    ASSERT_TRUE(info.get());
+    EXPECT_EQ(0, info->ai_flags);
+    EXPECT_EQ(AF_INET6, info->ai_family);
+    EXPECT_EQ(SOCK_STREAM, info->ai_socktype);
+    EXPECT_EQ(IPPROTO_IP, info->ai_protocol);
+    EXPECT_EQ(SIZEOF_AS_SOCKLEN(sockaddr_in6), info->ai_addrlen);
+    EXPECT_EQ(0, memcmp(&storage, info->ai_addr, info->ai_addrlen));
+    EXPECT_STREQ("localhost", info->ai_canonname);
+    EXPECT_EQ(NULL, info->ai_next);
+  }
+}
+
+
+TEST(SocketUtilTest, VerifyGetSocketOption) {
+  char optval[10];
+  socklen_t optlen = 10;
+
+  // Typical usage.
+  EXPECT_EQ(0, VerifyGetSocketOption(optval, &optlen));
+
+  // NULL check for optval
+  EXPECT_EQ(EFAULT, VerifyGetSocketOption(NULL, &optlen));
+
+  // optlen can be 0. In that case, optval can be NULL.
+  optlen = 0;
+  EXPECT_EQ(0, VerifyGetSocketOption(optval, &optlen));
+  EXPECT_EQ(0, VerifyGetSocketOption(NULL, &optlen));
+
+  // NULL check for optlen.
+  EXPECT_EQ(EFAULT, VerifyGetSocketOption(optval, NULL));
+  EXPECT_EQ(EFAULT, VerifyGetSocketOption(NULL, NULL));
+
+  optlen = -1;
+  EXPECT_EQ(EINVAL, VerifyGetSocketOption(optval, &optlen));
+  EXPECT_EQ(EINVAL, VerifyGetSocketOption(NULL, &optlen));
+}
+
+TEST(SocketUtilTest, VerifySetSocketOption) {
+  char optval[10];
+
+  // Typical usage.
+  EXPECT_EQ(0, VerifySetSocketOption(optval, 4, 4));
+  EXPECT_EQ(0, VerifySetSocketOption(optval, 8, 4));
+
+  // If the buffer size is smaller than expected value, EINVAL is expected.
+  EXPECT_EQ(EINVAL, VerifySetSocketOption(optval, 4, 8));
+
+  // If optval is NULL, EFAULT is expected.
+  EXPECT_EQ(EFAULT, VerifySetSocketOption(NULL, 4, 4));
+}
+
+TEST(SocketUtilTest, VerifyTimeoutSocketOption) {
+  timeval t;
+
+  // Typical usage.
+  t.tv_sec = 1;
+  t.tv_usec = 500;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+  t.tv_sec = 1;
+  t.tv_usec = 0;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+  t.tv_sec = 0;
+  t.tv_usec = 1000;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+  t.tv_sec = 0;
+  t.tv_usec = 0;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+
+  // Negative value is allowed for tv_sec.
+  t.tv_sec = -1;
+  t.tv_usec = 0;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+
+  // tv_usec must be in the range of [0, 1000000).
+  t.tv_sec = 0;
+  t.tv_usec = -1;
+  EXPECT_EQ(EDOM, VerifyTimeoutSocketOption(t));
+  t.tv_usec = 0;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+  t.tv_usec = 999999;
+  EXPECT_EQ(0, VerifyTimeoutSocketOption(t));
+  t.tv_usec = 1000000;
+  EXPECT_EQ(EDOM, VerifyTimeoutSocketOption(t));
+}
+
+TEST(SocketUtilTest, CopySocketOption) {
+  const char kStorage[] = {1, 2, 3, 4};
+  char optval[8] = {};
+  socklen_t optlen = 0;
+
+  memset(optval, 0x5A, sizeof(optval));
+  optlen = 4;
+  CopySocketOption(kStorage, sizeof(kStorage), optval, &optlen);
+  {
+    const char kExpectedOptval[] = {1, 2, 3, 4, 0x5A, 0x5A, 0x5A, 0x5A};
+    EXPECT_EQ(0, memcmp(optval, kExpectedOptval, sizeof(kExpectedOptval)));
+    EXPECT_EQ(4, static_cast<int>(optlen));
+  }
+
+  memset(optval, 0x5A, sizeof(optval));
+  optlen = 8;
+  CopySocketOption(kStorage, sizeof(kStorage), optval, &optlen);
+  {
+    const char kExpectedOptval[] = {1, 2, 3, 4, 0x5A, 0x5A, 0x5A, 0x5A};
+    EXPECT_EQ(0, memcmp(optval, kExpectedOptval, sizeof(kExpectedOptval)));
+    EXPECT_EQ(4, static_cast<int>(optlen));
+  }
+
+  memset(optval, 0x5A, sizeof(optval));
+  optlen = 2;
+  CopySocketOption(kStorage, sizeof(kStorage), optval, &optlen);
+  {
+    const char kExpectedOptval[] = {1, 2, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A};
+    EXPECT_EQ(0, memcmp(optval, kExpectedOptval, sizeof(kExpectedOptval)));
+    EXPECT_EQ(2, static_cast<int>(optlen));
+  }
+
+  // If optlen is 0, do nothing, espcially, optval can be null.
+  memset(optval, 0x5A, sizeof(optval));
+  optlen = 0;
+  CopySocketOption(kStorage, sizeof(kStorage), NULL, &optlen);
+  EXPECT_EQ(0, static_cast<int>(optlen));
+}
+
+}  // namespace internal
+}  // namespace posix_translation
diff --git a/src/posix_translation/sockets_wrap.cc b/src/posix_translation/sockets_wrap.cc
new file mode 100644
index 0000000..deaae11
--- /dev/null
+++ b/src/posix_translation/sockets_wrap.cc
@@ -0,0 +1,434 @@
+/* Copyright 2014 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.
+ *
+ * Simple wrappers for various socket calls.
+ */
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/epoll.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "common/arc_strace.h"
+#include "common/danger.h"
+#include "common/export.h"
+#include "posix_translation/virtual_file_system.h"
+
+extern "C" {
+ARC_EXPORT int __wrap_accept(
+    int sockfd, struct sockaddr* addr, socklen_t* addrlen);
+ARC_EXPORT int __wrap_bind(
+    int sockfd, const struct sockaddr* addr, socklen_t addrlen);
+ARC_EXPORT int __wrap_connect(
+    int sockfd, const struct sockaddr* addr,
+    socklen_t addrlen);
+ARC_EXPORT int __wrap_epoll_create(int size);
+ARC_EXPORT int __wrap_epoll_ctl(
+    int epfd, int op, int fd, struct epoll_event* event);
+ARC_EXPORT int __wrap_epoll_wait(
+    int epfd, struct epoll_event* events, int maxevents, int timeout);
+ARC_EXPORT void __wrap_freeaddrinfo(struct addrinfo* res);
+ARC_EXPORT const char* __wrap_gai_strerror(int errcode);
+ARC_EXPORT int __wrap_getaddrinfo(
+    const char* node, const char* service,
+    const struct addrinfo* hints, struct addrinfo** res);
+ARC_EXPORT struct hostent* __wrap_gethostbyaddr(
+    const void* addr, socklen_t len, int type);
+ARC_EXPORT struct hostent* __wrap_gethostbyname(const char* hostname);
+ARC_EXPORT struct hostent* __wrap_gethostbyname2(
+    const char* hostname, int family);
+ARC_EXPORT int __wrap_gethostbyname_r(
+    const char* hostname, struct hostent* ret, char* buf, size_t buflen,
+    struct hostent** result, int* h_errnop);
+ARC_EXPORT int __wrap_getnameinfo(
+    const struct sockaddr* sa, socklen_t salen,
+    char* host, size_t hostlen,
+    char* serv, size_t servlen, int flags);
+ARC_EXPORT int __wrap_getpeername(
+    int sockfd, struct sockaddr* addr, socklen_t* addrlen);
+ARC_EXPORT int __wrap_getsockname(
+    int sockfd, struct sockaddr* addr, socklen_t* addrlen);
+ARC_EXPORT int __wrap_getsockopt(
+    int sockfd, int level, int optname, void* optval, socklen_t* optlen);
+ARC_EXPORT int __wrap_listen(int sockfd, int backlog);
+ARC_EXPORT int __wrap_pipe(int pipefd[2]);
+ARC_EXPORT int __wrap_pipe2(int pipefd[2], int flags);
+ARC_EXPORT int __wrap_pselect(
+    int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
+    const struct timespec* timeout, const sigset_t* sigmask);
+ARC_EXPORT ssize_t __wrap_recv(int sockfd, void* buf, size_t len, int flags);
+ARC_EXPORT ssize_t __wrap_recvfrom(
+    int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr,
+    socklen_t* addrlen);
+ARC_EXPORT ssize_t __wrap_recvmsg(int sockfd, struct msghdr* msg, int flags);
+ARC_EXPORT int __wrap_select(
+    int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
+    struct timeval* timeout);
+ARC_EXPORT ssize_t __wrap_send(
+    int sockfd, const void* buf, size_t len, int flags);
+ARC_EXPORT ssize_t __wrap_sendto(
+    int sockfd, const void* buf, size_t len, int flags,
+    const struct sockaddr* dest_addr, socklen_t addrlen);
+ARC_EXPORT ssize_t __wrap_sendmsg(
+    int sockfd, const struct msghdr* msg, int flags);
+ARC_EXPORT int __wrap_setsockopt(
+    int sockfd, int level, int optname, const void* optval, socklen_t optlen);
+ARC_EXPORT int __wrap_shutdown(int sockfd, int how);
+ARC_EXPORT int __wrap_socket(int domain, int type, int protocol);
+ARC_EXPORT int __wrap_socketpair(int domain, int type, int protocol, int sv[2]);
+}  // extern "C"
+
+using posix_translation::VirtualFileSystem;
+
+int __wrap_accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
+  ARC_STRACE_ENTER_FD("accept", "%d, %p, %p", sockfd, addr, addrlen);
+  int fd = VirtualFileSystem::GetVirtualFileSystem()->accept(
+      sockfd, addr, addrlen);
+  ARC_STRACE_REGISTER_FD(fd, "accept");
+  ARC_STRACE_RETURN(fd);
+}
+
+int __wrap_bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen) {
+  ARC_STRACE_ENTER_FD("bind", "%d, %s, %u",
+                      sockfd, arc::GetSockaddrStr(addr).c_str(), addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->bind(
+      sockfd, addr, addrlen);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_connect(int sockfd, const struct sockaddr* addr,
+                   socklen_t addrlen) {
+  ARC_STRACE_ENTER_FD("connect", "%d, %s, %u",
+                      sockfd, arc::GetSockaddrStr(addr).c_str(), addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->connect(
+      sockfd, addr, addrlen);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_epoll_create(int size) {
+  ARC_STRACE_ENTER("epoll_create", "%d", size);
+  int fd = VirtualFileSystem::GetVirtualFileSystem()->epoll_create1(0);
+  ARC_STRACE_REGISTER_FD(fd, "epoll");
+  ARC_STRACE_RETURN(fd);
+}
+
+int __wrap_epoll_ctl(int epfd, int op, int fd, struct epoll_event* event) {
+  ARC_STRACE_ENTER_FD(
+      "epoll_ctl", "%d, %s, %d \"%s\", %s",
+      epfd, arc::GetEpollCtlOpStr(op).c_str(), fd,
+      arc::GetFdStr(fd).c_str(),
+      // Recent Linux kernel accepts NULL |event| when |op| is EPOLL_CTL_DEL.
+      event ? arc::GetEpollEventStr(event->events).c_str() : "(null)");
+  ARC_STRACE_ENTER_FD("epoll_ctl", "%d, %d, %d, %p", epfd, op, fd, event);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->epoll_ctl(
+      epfd, op, fd, event);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_epoll_wait(int epfd, struct epoll_event* events, int maxevents,
+                      int timeout) {
+  ARC_STRACE_ENTER_FD("epoll_wait", "%d, %p, %d, %d",
+                      epfd, events, maxevents, timeout);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->epoll_wait(
+      epfd, events, maxevents, timeout);
+  if (arc::StraceEnabled()) {
+    for (int i = 0; i < result; ++i) {
+      ARC_STRACE_REPORT("fd %d \"%s\" is ready for %s", events[i].data.fd,
+                        arc::GetFdStr(events[i].data.fd).c_str(),
+                        arc::GetEpollEventStr(events[i].events).c_str());
+    }
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+void __wrap_freeaddrinfo(struct addrinfo* res) {
+  ARC_STRACE_ENTER("freeaddrinfo", "%p", res);
+  VirtualFileSystem::GetVirtualFileSystem()->freeaddrinfo(res);
+  ARC_STRACE_RETURN_VOID();
+}
+
+int __wrap_getnameinfo(const struct sockaddr* sa, socklen_t salen,
+                       char* host, size_t hostlen,
+                       char* serv, size_t servlen, int flags) {
+  // TODO(igorc): Add GetNameInfoFlagStr() to src/common/arc_strace.[h,cc].
+  ARC_STRACE_ENTER("getnameinfo", "%p, %d, %p, %zu, %p, %zu, %d",
+                   sa, salen, host, hostlen, serv, servlen, flags);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->getnameinfo(
+      sa, salen, host, hostlen, serv, servlen, flags);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_getaddrinfo(const char* node, const char* service,
+                       const struct addrinfo* hints, struct addrinfo** res) {
+  ARC_STRACE_ENTER("getaddrinfo", "\"%s\", \"%s\", %p, %p",
+                   SAFE_CSTR(node), SAFE_CSTR(service), hints, res);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->getaddrinfo(
+      node, service, hints, res);
+  // TODO(crbug.com/241955): Show errno for EAI_SYSTEM?
+  ARC_STRACE_RETURN(result);
+}
+
+const char* __wrap_gai_strerror(int errcode) {
+  // This code duplicates bionic/libc/netbsd/net/getaddrinfo.c.
+  // TODO(crbug.com/356271): Use Bionic impl instead.
+  static const char* const kErrorList[] = {
+    "Success",
+    "Address family for hostname not supported",    /* EAI_ADDRFAMILY */
+    "Temporary failure in name resolution",         /* EAI_AGAIN      */
+    "Invalid value for ai_flags",                   /* EAI_BADFLAGS   */
+    "Non-recoverable failure in name resolution",   /* EAI_FAIL       */
+    "ai_family not supported",                      /* EAI_FAMILY     */
+    "Memory allocation failure",                    /* EAI_MEMORY     */
+    "No address associated with hostname",          /* EAI_NODATA     */
+    "hostname nor servname provided, or not known", /* EAI_NONAME     */
+    "servname not supported for ai_socktype",       /* EAI_SERVICE    */
+    "ai_socktype not supported",                    /* EAI_SOCKTYPE   */
+    "System error returned in errno",               /* EAI_SYSTEM     */
+    "Invalid value for hints",                      /* EAI_BADHINTS   */
+    "Resolved protocol is unknown",                 /* EAI_PROTOCOL   */
+    "Argument buffer overflow",                     /* EAI_OVERFLOW   */
+    "Unknown error",                                /* EAI_MAX        */
+  };
+
+  ALOG_ASSERT((sizeof(kErrorList) / sizeof(kErrorList[0])) == (EAI_MAX + 1));
+
+  if (errcode < 0 || errcode > EAI_MAX)
+    errcode = EAI_MAX;
+  return kErrorList[errcode];
+}
+
+struct hostent* __wrap_gethostbyaddr(
+    const void* addr, socklen_t len, int type) {
+  // TODO(igorc): Add GetNetFamilyStr() to src/common/arc_strace.[h,cc].
+  ARC_STRACE_ENTER("gethostbyaddr", "%p, %d, %d", addr, len, type);
+  struct hostent* result = VirtualFileSystem::GetVirtualFileSystem()
+      ->gethostbyaddr(addr, len, type);
+  if (result == NULL) {
+    ARC_STRACE_REPORT("h_errno=%d", h_errno);
+  }
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+struct hostent* __wrap_gethostbyname(const char* hostname) {
+  ARC_STRACE_ENTER("gethostbyname", "\"%s\"", SAFE_CSTR(hostname));
+  struct hostent* result = VirtualFileSystem::GetVirtualFileSystem()
+      ->gethostbyname(hostname);
+  if (result == NULL) {
+    ARC_STRACE_REPORT("h_errno=%d", h_errno);
+  }
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+int __wrap_gethostbyname_r(const char* hostname, struct hostent* ret,
+                           char* buf, size_t buflen,
+                           struct hostent** result, int* h_errnop) {
+  ARC_STRACE_ENTER("gethostbyname_r", "\"%s\"", SAFE_CSTR(hostname));
+  int res = VirtualFileSystem::GetVirtualFileSystem()
+      ->gethostbyname_r(hostname, ret, buf, buflen, result, h_errnop);
+  if (res != 0 && *h_errnop != 0) {
+    ARC_STRACE_REPORT("h_errno=%d", *h_errnop);
+  }
+  ARC_STRACE_RETURN(res);
+}
+
+struct hostent* __wrap_gethostbyname2(const char* hostname, int family) {
+  ARC_STRACE_ENTER("gethostbyname2", "\"%s\" %d",
+                   SAFE_CSTR(hostname), family);
+  struct hostent* result = VirtualFileSystem::GetVirtualFileSystem()
+      ->gethostbyname2(hostname, family);
+  if (result == NULL) {
+    ARC_STRACE_REPORT("h_errno=%d", h_errno);
+  }
+  ARC_STRACE_RETURN_PTR(result, false);
+}
+
+int __wrap_getpeername(int sockfd, struct sockaddr* addr,
+                       socklen_t* addrlen) {
+  ARC_STRACE_ENTER_FD("getpeername", "%d, %p, %p", sockfd, addr, addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->getpeername(
+      sockfd, addr, addrlen);
+  if (result == -1 && errno == EINVAL) {
+    DANGER();
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_getsockname(int sockfd, struct sockaddr* addr,
+                       socklen_t* addrlen) {
+  ARC_STRACE_ENTER_FD("getsockname", "%d, %p, %p", sockfd, addr, addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->getsockname(
+      sockfd, addr, addrlen);
+  if (result == -1 && errno == EINVAL) {
+    DANGER();
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_getsockopt(int sockfd, int level, int optname,
+                      void* optval, socklen_t* optlen) {
+  ARC_STRACE_ENTER_FD("getsockopt", "%d, %d, %d, %p, %p",
+                      sockfd, level, optname, optval, optlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->getsockopt(
+      sockfd, level, optname, optval, optlen);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_listen(int sockfd, int backlog) {
+  ARC_STRACE_ENTER_FD("listen", "%d, %d", sockfd, backlog);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->listen(
+      sockfd, backlog);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_pipe(int pipefd[2]) {
+  ARC_STRACE_ENTER("pipe", "%p", pipefd);
+  int result;
+  result = VirtualFileSystem::GetVirtualFileSystem()->pipe2(pipefd, 0);
+  if (result >= 0) {
+    ARC_STRACE_REGISTER_FD(pipefd[0], "pipe[0]");
+    ARC_STRACE_REGISTER_FD(pipefd[1], "pipe[1]");
+    ARC_STRACE_REPORT("pipe[0]=%d pipe[1]=%d", pipefd[0], pipefd[1]);
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_pipe2(int pipefd[2], int flags) {
+  ARC_STRACE_ENTER("pipe2", "%p, %d", pipefd, flags);
+
+  int result = VirtualFileSystem::GetVirtualFileSystem()->pipe2(pipefd, flags);
+  if (result >= 0) {
+    ARC_STRACE_REGISTER_FD(pipefd[0], "pipe2[0]");
+    ARC_STRACE_REGISTER_FD(pipefd[1], "pipe2[1]");
+    ARC_STRACE_REPORT("pipe[0]=%d pipe[1]=%d", pipefd[0], pipefd[1]);
+  }
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_pselect(int nfds, fd_set* readfds, fd_set* writefds,
+                   fd_set* exceptfds, const struct timespec* timeout,
+                   const sigset_t* sigmask) {
+  ALOG_ASSERT(false, "pselect is not supported");
+  errno = EAFNOSUPPORT;
+  return -1;
+}
+
+ssize_t __wrap_recv(int sockfd, void* buf, size_t len, int flags) {
+  ARC_STRACE_ENTER_FD("recv", "%d, %p, %zu, %d", sockfd, buf, len, flags);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->recv(
+      sockfd, buf, len, flags);
+  if (result >= 0)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_recvfrom(int sockfd, void* buf, size_t len, int flags,
+                        struct sockaddr* src_addr, socklen_t* addrlen) {
+  ARC_STRACE_ENTER_FD("recvfrom", "%d, %p, %zu, %d, %p, %p",
+                      sockfd, buf, len, flags, src_addr, addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->recvfrom(
+      sockfd, buf, len, flags, src_addr, addrlen);
+  if (result == -1 && errno == EINVAL) {
+    DANGER();
+  }
+  if (result >= 0)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_recvmsg(int sockfd, struct msghdr* msg, int flags) {
+  ARC_STRACE_ENTER_FD("recvmsg", "%d, %p, %d", sockfd, msg, flags);
+  ssize_t result = VirtualFileSystem::GetVirtualFileSystem()->recvmsg(
+      sockfd, msg, flags);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_select(int nfds, fd_set* readfds, fd_set* writefds,
+                  fd_set* exceptfds, struct timeval* timeout) {
+  // TODO(crbug.com/241955): Stringify *fds parameters.
+  ARC_STRACE_ENTER("select", "%d, %p, %p, %p, %p",
+                   nfds, readfds, writefds, exceptfds, timeout);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->select(
+      nfds, readfds, writefds,
+                                                     exceptfds, timeout);
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_send(int sockfd, const void* buf, size_t len, int flags) {
+  ARC_STRACE_ENTER_FD("send", "%d, %p, %zu, %d", sockfd, buf, len, flags);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->send(
+      sockfd, buf, len, flags);
+  if (errno != EFAULT)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_sendto(int sockfd, const void* buf, size_t len, int flags,
+                      const struct sockaddr* dest_addr, socklen_t addrlen) {
+  ARC_STRACE_ENTER_FD("sendto", "%d, %p, %zu, %d, %s, %u",
+                      sockfd, buf, len, flags,
+                      arc::GetSockaddrStr(dest_addr).c_str(), addrlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->sendto(
+      sockfd, buf, len, flags, dest_addr, addrlen);
+  if (result == -1 && errno == EINVAL) {
+    DANGER();
+  }
+  if (errno != EFAULT)
+    ARC_STRACE_REPORT("buf=%s", arc::GetRWBufStr(buf, result).c_str());
+  ARC_STRACE_RETURN(result);
+}
+
+ssize_t __wrap_sendmsg(int sockfd, const struct msghdr* msg, int flags) {
+  ARC_STRACE_ENTER_FD("sendmsg", "%d, %p, %d", sockfd, msg, flags);
+  ssize_t result = VirtualFileSystem::GetVirtualFileSystem()->sendmsg(
+      sockfd, msg, flags);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_setsockopt(int sockfd, int level, int optname,
+                      const void* optval, socklen_t optlen) {
+  ARC_STRACE_ENTER_FD("setsockopt", "%d, %d, %d, %p, %d",
+                      sockfd, level, optname, optval, optlen);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->setsockopt(
+      sockfd, level, optname, optval, optlen);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_shutdown(int sockfd, int how) {
+  ARC_STRACE_ENTER_FD("shutdown", "%d, %d", sockfd, how);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->shutdown(sockfd, how);
+  ARC_STRACE_RETURN(result);
+}
+
+int __wrap_socket(int domain, int type, int protocol) {
+  ARC_STRACE_ENTER("socket", "%s, %s, %s",
+                   arc::GetSocketDomainStr(domain).c_str(),
+                   arc::GetSocketTypeStr(type).c_str(),
+                   arc::GetSocketProtocolStr(protocol).c_str());
+  int fd = VirtualFileSystem::GetVirtualFileSystem()->socket(
+      domain, type, protocol);
+  ARC_STRACE_REGISTER_FD(fd, "socket");
+  ARC_STRACE_RETURN(fd);
+}
+
+int __wrap_socketpair(int domain, int type, int protocol, int sv[2]) {
+  ARC_STRACE_ENTER("socketpair", "%s, %s, %s, %p",
+                   arc::GetSocketDomainStr(domain).c_str(),
+                   arc::GetSocketTypeStr(type).c_str(),
+                   arc::GetSocketProtocolStr(protocol).c_str(),
+                   sv);
+  int result = VirtualFileSystem::GetVirtualFileSystem()->socketpair(
+      domain, type, protocol, sv);
+  if (result >= 0) {
+    ARC_STRACE_REGISTER_FD(sv[0], "socketpair[0]");
+    ARC_STRACE_REGISTER_FD(sv[1], "socketpair[1]");
+    ARC_STRACE_REPORT("sock[0]=%d sock[1]=%d", sv[0], sv[1]);
+  }
+  ARC_STRACE_RETURN(result);
+}
diff --git a/src/posix_translation/statfs.cc b/src/posix_translation/statfs.cc
new file mode 100644
index 0000000..1ac267e
--- /dev/null
+++ b/src/posix_translation/statfs.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 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 "posix_translation/statfs.h"
+
+#include <string.h>
+
+// Note: These values are obtained by running a small C program on a
+// real device using C4droid.
+
+// See man statfs(2).
+#define TMPFS_MAGIC           0x01021994
+#define PROC_SUPER_MAGIC      0x9fa0
+#define EXT2_SUPER_MAGIC      0xEF53
+#define SYSFS_MAGIC           0x62656572
+
+namespace posix_translation {
+
+int DoStatFsForDev(struct statfs* out) {
+  memset(out, 0, sizeof(struct statfs));
+  out->f_type = TMPFS_MAGIC;
+  out->f_bsize = 4096;
+  out->f_blocks = 88936;
+  out->f_bfree = 88928;
+  out->f_bavail = 88928;
+  out->f_files = 28368;
+  out->f_ffree = 28134;
+  out->f_namelen = 255;
+  out->f_frsize = 4096;
+  out->f_spare[0] = 4130;
+  return 0;
+}
+
+int DoStatFsForProc(struct statfs* out) {
+  memset(out, 0, sizeof(struct statfs));
+  out->f_type = PROC_SUPER_MAGIC;
+  out->f_bsize = 4096;
+  out->f_blocks = 88936;
+  out->f_bfree = 88928;
+  out->f_bavail = 88928;
+  out->f_files = 28368;
+  out->f_ffree = 28134;
+  out->f_namelen = 255;
+  out->f_frsize = 4096;
+  out->f_spare[0] = 4128;
+  return 0;
+}
+
+int DoStatFsForData(struct statfs* out) {
+  memset(out, 0, sizeof(struct statfs));
+  out->f_type = EXT2_SUPER_MAGIC;
+  out->f_bsize = 4096;
+  out->f_blocks = 2LL * 1024 * 1024 * 1024 / out->f_bsize;  // 2GB
+  out->f_bfree = out->f_blocks / 2;
+  out->f_bavail = out->f_bfree;
+  out->f_files = 887696;
+  out->f_ffree = 866497;
+  out->f_fsid.__val[0] = -748642328;
+  out->f_fsid.__val[1] = 77008235;
+  out->f_namelen = 255;
+  out->f_frsize = 4096;
+  out->f_spare[0] = 1062;
+  return 0;
+}
+
+int DoStatFsForSystem(struct statfs* out) {
+  memset(out, 0, sizeof(struct statfs));
+  out->f_type = EXT2_SUPER_MAGIC;
+  out->f_bsize = 4096;
+  out->f_blocks = 164788;
+  out->f_bfree = 93919;
+  out->f_bavail = 93919;
+  out->f_files = 41856;
+  out->f_ffree = 40924;
+  out->f_fsid.__val[0] = -748642328;
+  out->f_fsid.__val[1] = 77008235;
+  out->f_namelen = 255;
+  out->f_frsize = 4096;
+  out->f_spare[0] = 4129;
+  return 0;
+}
+
+int DoStatFsForSys(struct statfs* out) {
+  memset(out, 0, sizeof(struct statfs));
+  out->f_type = SYSFS_MAGIC;
+  out->f_bsize = 4096;
+  out->f_blocks = 0;
+  out->f_bfree = 0;
+  out->f_bavail = 0;
+  out->f_files = 0;
+  out->f_ffree = 0;
+  out->f_fsid.__val[0] = 0;
+  out->f_fsid.__val[1] = 0;
+  out->f_namelen = 255;
+  out->f_frsize = 4096;
+  out->f_spare[0] = 4128;
+  return 0;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/statfs.h b/src/posix_translation/statfs.h
new file mode 100644
index 0000000..84c346f
--- /dev/null
+++ b/src/posix_translation/statfs.h
@@ -0,0 +1,22 @@
+// Copyright 2014 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.
+//
+// Functions which fill fixed statfs values got from a real device.
+
+#ifndef POSIX_TRANSLATION_STATFS_H_
+#define POSIX_TRANSLATION_STATFS_H_
+
+#include <sys/vfs.h>
+
+namespace posix_translation {
+
+int DoStatFsForDev(struct statfs* out);
+int DoStatFsForProc(struct statfs* out);
+int DoStatFsForData(struct statfs* out);
+int DoStatFsForSystem(struct statfs* out);
+int DoStatFsForSys(struct statfs* out);
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_STATFS_H_
diff --git a/src/posix_translation/statfs_test.cc b/src/posix_translation/statfs_test.cc
new file mode 100644
index 0000000..df4540b
--- /dev/null
+++ b/src/posix_translation/statfs_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 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 "gtest/gtest.h"
+#include "posix_translation/statfs.h"
+
+namespace posix_translation {
+
+// Call all the functions to let Valgrind examine them.
+TEST(StatFsTest, TestDev) {
+  struct statfs sfs;
+  EXPECT_EQ(0, DoStatFsForDev(&sfs));
+  EXPECT_NE(0, static_cast<int>(sfs.f_bsize));
+}
+
+TEST(StatFsTest, TestProc) {
+  struct statfs sfs;
+  EXPECT_EQ(0, DoStatFsForProc(&sfs));
+  EXPECT_NE(0, static_cast<int>(sfs.f_bsize));
+}
+
+TEST(StatFsTest, TestData) {
+  struct statfs sfs;
+  EXPECT_EQ(0, DoStatFsForData(&sfs));
+  EXPECT_NE(0, static_cast<int>(sfs.f_bsize));
+}
+
+TEST(StatFsTest, TestSystem) {
+  struct statfs sfs;
+  EXPECT_EQ(0, DoStatFsForSystem(&sfs));
+  EXPECT_NE(0, static_cast<int>(sfs.f_bsize));
+}
+
+TEST(StatFsTest, TestSys) {
+  struct statfs sfs;
+  EXPECT_EQ(0, DoStatFsForSys(&sfs));
+  EXPECT_NE(0, static_cast<int>(sfs.f_bsize));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/syscall_wrap.cc b/src/posix_translation/syscall_wrap.cc
new file mode 100644
index 0000000..441163a
--- /dev/null
+++ b/src/posix_translation/syscall_wrap.cc
@@ -0,0 +1,56 @@
+/* Copyright 2014 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.
+ *
+ * Linux syscall wrapper.
+ * Both platforms expect definitions provided by <sys/syscall.h> of Bionic.
+ * Note that NaCl x86-64 also uses one for i686 as Bionic does not have
+ * sys/syscall.h for x86-64.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+
+#include "base/basictypes.h"
+#include "common/arc_strace.h"
+#include "common/export.h"
+
+namespace {
+
+int HandleSyscallGettid() {
+  ARC_STRACE_ENTER("syscall", "%s", "__NR_gettid");
+  const int result = gettid();
+  ARC_STRACE_RETURN(result);
+}
+
+int HandleSyscallDefault(int number) {
+  // TODO(crbug.com/241955): Stringify |number|.
+  ARC_STRACE_ENTER("syscall", "%d, ...", number);
+  errno = ENOSYS;
+  ARC_STRACE_RETURN(-1);
+}
+
+}  // namespace
+
+extern "C" ARC_EXPORT int __wrap_syscall(int number, ...) {
+  // Defining a function with variable argument without using va_start/va_end
+  // is not portable and may cause crash.
+  va_list ap;
+  va_start(ap, number);
+  int result;
+
+  // The number is based on not running Android platform, but ARC build
+  // target platform. NDK should not pass directly the number applications use.
+  switch (number) {
+    case __NR_gettid:
+      result = HandleSyscallGettid();
+      break;
+    default:
+      result = HandleSyscallDefault(number);
+      break;
+  }
+  va_end(ap);
+  return result;
+}
diff --git a/src/posix_translation/tcp_socket.cc b/src/posix_translation/tcp_socket.cc
new file mode 100644
index 0000000..2a2c9dd
--- /dev/null
+++ b/src/posix_translation/tcp_socket.cc
@@ -0,0 +1,907 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/tcp_socket.h"
+
+#include <arpa/inet.h>
+#include <netinet/tcp.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "posix_translation/socket_util.h"
+#include "posix_translation/time_util.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/net_address.h"
+#include "ppapi/cpp/tcp_socket.h"
+
+namespace posix_translation {
+
+// Thin wrapper of pp::TCPSocket to manage the lifetime of pp::TCPSocket.
+// Background: the problem is some blocking call (such as ::read()), and
+// ::close() for this class may have race condition.
+// Assuming ::read() is called on a thread and it is blocked, and ::close() is
+// called on another thread.
+// On current FileStream implementation, the final ::close() destructs the
+// stream instance. So, when ::read() is unblocked after the ::close(), it is
+// necessary to know if the socket is closed or not without touching the
+// TCPSocket instance (otherwise it may cause use-after-free problem).
+// This thin wrapper provides such a functionality.
+//
+// How to use:
+//
+//     :
+//   // Keep the reference to SocketWrapper locally.
+//   scoped_refptr<SocketWrapper> wrapper(socket_);
+//   VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+//   const base::TimeTicks time_limit = internal::TimeOutToTimeLimit(timeout);
+//   bool is_timedout = false;
+//   while (!is_timedout && ... condition ...) {
+//     is_timedout = sys->WaitUntil(time_limit);
+//     // Check close state before accessing any member variables since this
+//     // instance might be destroyed while this thread was waiting.
+//     if (wrapper->is_closed()) {
+//       errno = EBADF;
+//       return -1;
+//     }
+//   }
+//     :
+//
+// Note: this is very close to what WeakPtr does. However, we cannot use
+// WeakPtrFactory because the factory is bound to the thread where the first
+// WeakPtr is created, and then it is prohibited to destruct it on another
+// thread.
+//
+// This class will be touched from multi threads. To access is_closed() and
+// Close(), the caller has responsibility to lock the filesystem-wise giant
+// mutex in advance.
+class TCPSocket::SocketWrapper
+    : public base::RefCountedThreadSafe<SocketWrapper> {
+ public:
+  // Takes the ownership of socket.
+  explicit SocketWrapper(const pp::TCPSocket& socket)
+      : socket_(socket), closed_(false) {
+  }
+
+  bool is_closed() const {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    return closed_;
+  }
+
+  void Close() {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (is_closed())
+      return;
+    closed_ = true;
+    socket_.Close();
+  }
+
+  pp::TCPSocket* socket() {
+    return &socket_;
+  }
+
+ private:
+  // Do not allow to destruct this class manually from the client code
+  // to avoid to delete the object accidentally while there are still
+  // references to it.
+  friend class base::RefCountedThreadSafe<SocketWrapper>;
+  ~SocketWrapper() {
+  }
+
+  pp::TCPSocket socket_;
+  bool closed_;
+  DISALLOW_COPY_AND_ASSIGN(SocketWrapper);
+};
+
+TCPSocket::TCPSocket(int fd, int socket_family, int oflag)
+    : SocketStream(socket_family, oflag), fd_(fd), factory_(this),
+      socket_(new SocketWrapper(pp::TCPSocket(
+          VirtualFileSystem::GetVirtualFileSystem()->instance()))),
+      read_buf_(kBufSize), connect_state_(TCP_SOCKET_NEW), eof_(false),
+      read_sent_(false), write_sent_(false), connect_error_(0),
+      no_delay_(0) {
+  ALOG_ASSERT(socket_family == AF_INET || socket_family == AF_INET6);
+}
+
+TCPSocket::TCPSocket(const pp::TCPSocket& socket)
+    : SocketStream(kUnknownSocketFamily, O_RDWR), fd_(-1), factory_(this),
+      socket_(new SocketWrapper(socket)),
+      read_buf_(kBufSize), connect_state_(TCP_SOCKET_NEW), eof_(false),
+      read_sent_(false), write_sent_(false), connect_error_(0),
+      no_delay_(0) {
+}
+
+TCPSocket::~TCPSocket() {
+  if (!socket_->is_closed()) {
+    // Unlike UDPSocket, this happens when TCPSocket instance is created but
+    // discarded before it is registered to file system. For example, this
+    // happens on error case of accept().
+    CloseLocked();
+  }
+}
+
+void TCPSocket::MarkAsErrorLocked(int error) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  if (!IsTerminated()) {
+    if (connect_state_ == TCP_SOCKET_CONNECTING)
+      connect_error_ = error;
+    if (!is_block()) {
+      // getsockopt() does not seem to expose SO_ERROR for blocking sockets.
+      // This is likely because the main reason for SO_ERROR is to allow apps
+      // to query errors after a successful select() call, during which
+      // a non-blocking connect may have failed.
+      error_ = error;
+    }
+    connect_state_ = TCP_SOCKET_ERROR;
+    NotifyListeners();
+  }
+}
+
+int TCPSocket::bind(const sockaddr* addr, socklen_t addrlen) {
+  int error =
+      internal::VerifyInputSocketAddress(addr, addrlen, socket_family_);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  pp::NetAddress address =
+      internal::SockAddrToNetAddress(sys->instance(), addr);
+
+  ALOGI("TCPSocket::Bind: %s",
+        address.DescribeAsString(true).AsString().c_str());
+  scoped_refptr<SocketWrapper> wrapper(socket_);
+  int32_t result = PP_OK_COMPLETIONPENDING;
+  {
+    base::AutoUnlock unlock(sys->mutex());
+    result = wrapper->socket()->Bind(address, pp::BlockUntilComplete());
+  }
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  // Check close state before accessing any member variables since this
+  // instance might be destroyed while this thread was waiting.
+  if (wrapper->is_closed()) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (result != PP_OK) {
+    if (result == PP_ERROR_ADDRESS_IN_USE) {
+      errno = EADDRINUSE;
+    } else {
+      errno = EINVAL;
+    }
+    return -1;
+  }
+
+  return 0;
+}
+
+int TCPSocket::listen(int backlog) {
+  if (connect_state_ != TCP_SOCKET_NEW) {
+    // This could happen, for example, when a user writes as follows:
+    //   s = socket(AF_INET, SOCK_STREAM, 0);
+    //   connect(s, ... something peer ...);
+    //   listen(s, 5);
+    // There is no explicit documentation in the man page, but empirically
+    // under Linux, EINVAL is raised.
+    errno = EINVAL;
+    return -1;
+  }
+
+  connect_state_ = TCP_SOCKET_LISTENING;
+
+  scoped_refptr<SocketWrapper> wrapper(socket_);
+  int32_t result = PP_OK_COMPLETIONPENDING;
+  {
+    base::AutoUnlock unlock(VirtualFileSystem::GetVirtualFileSystem()->mutex());
+    result = wrapper->socket()->Listen(backlog, pp::BlockUntilComplete());
+  }
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  // Check close state before accessing any member variables since this
+  // instance might be destroyed while this thread was waiting.
+  if (wrapper->is_closed()) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (result != PP_OK) {
+    if (result == PP_ERROR_NOSPACE)
+      errno = EOPNOTSUPP;
+    else
+      errno = EADDRINUSE;
+    MarkAsErrorLocked(errno);
+    return -1;
+  }
+
+  // The listen() has actually been started. So, start "accept" as a background
+  // task to support non-blocking ::accept().
+  pp::Module::Get()->core()->CallOnMainThread(
+      0, factory_.NewCallback(&TCPSocket::Accept));
+  return 0;
+}
+
+int TCPSocket::accept(sockaddr* addr, socklen_t* addrlen) {
+  // accept(2) allows NULL/NULL to be passed for sockaddr.
+  if (addr) {
+    int error = internal::VerifyOutputSocketAddress(addr, addrlen);
+    if (error) {
+      errno = error;
+      return -1;
+    }
+  }
+
+  if (connect_state_ != TCP_SOCKET_LISTENING) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  if (is_block()) {
+    // Wait until some peer connects to the listening socket, or timed out.
+    const base::TimeTicks time_limit =
+        internal::TimeOutToTimeLimit(recv_timeout_);
+    bool is_timedout = false;
+    scoped_refptr<SocketWrapper> wrapper(socket_);
+    while (!is_timedout && accepted_socket_.is_null()) {
+      is_timedout = sys->WaitUntil(time_limit);
+      // Check close state before accessing any member variables since this
+      // instance might be destroyed while this thread was waiting.
+      if (wrapper->is_closed()) {
+        errno = EBADF;
+        return -1;
+      }
+    }
+  }
+
+  if (accepted_socket_.is_null()) {
+    errno = EAGAIN;
+    return -1;
+  }
+
+  pp::TCPSocket accepted_socket = accepted_socket_;
+  accepted_socket_ = pp::TCPSocket();
+  pp::Module::Get()->core()->CallOnMainThread(
+      0, factory_.NewCallback(&TCPSocket::Accept));
+
+  // Before creating TCPSocket instance, extract the address to check an error.
+  sockaddr_storage storage = {};
+  if (addr) {
+    if (!internal::NetAddressToSockAddrStorage(
+            accepted_socket.GetRemoteAddress(), AF_UNSPEC, false, &storage)) {
+      // According to man, there seems no appropriate error is defined for
+      // this case. So, use ENOBUF to let the client know that this is some
+      // internal error.
+      errno = ENOBUFS;
+      return -1;
+    }
+  }
+
+  scoped_refptr<TCPSocket> socket = new TCPSocket(accepted_socket);
+  int fd = sys->AddFileStreamLocked(socket);
+  if (fd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+
+  socket->fd_ = fd;
+  socket->connect_state_ = TCP_SOCKET_CONNECTED;
+  // Start reading on background.
+  socket->PostReadTaskLocked();
+
+  // Finally, copy the address data if necessary.
+  if (addr)
+    internal::CopySocketAddress(storage, addr, addrlen);
+  return fd;
+}
+
+int TCPSocket::connect(const sockaddr* serv_addr, socklen_t addrlen) {
+  int error =
+      internal::VerifyInputSocketAddress(serv_addr, addrlen, socket_family_);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  if (IsTerminated()) {
+    // TODO(crbug.com/358855): Allow new connect() calls after an unsuccessful
+    // connection attempt.
+    errno = EBADF;
+    return -1;
+  }
+
+  if (connect_state_ == TCP_SOCKET_CONNECTED ||
+      connect_state_ == TCP_SOCKET_LISTENING) {
+    errno = EISCONN;
+    return -1;
+  }
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  if (connect_state_ == TCP_SOCKET_NEW) {
+    pp::NetAddress address =
+        internal::SockAddrToNetAddress(sys->instance(), serv_addr);
+    ALOGI("TCPSocket::connect: %s",
+          address.DescribeAsString(true).AsString().c_str());
+
+    connect_state_ = TCP_SOCKET_CONNECTING;
+    pp::Module::Get()->core()->CallOnMainThread(
+        0, factory_.NewCallback(&TCPSocket::Connect, address));
+    if (!is_block()) {
+      errno = EINPROGRESS;
+      return -1;
+    }
+  } else {
+    ALOG_ASSERT(connect_state_ == TCP_SOCKET_CONNECTING);
+    if (!is_block()) {
+      errno = EALREADY;
+      return -1;
+    }
+    // Blocking connect should block, waiting for results of a pending connect.
+  }
+
+  scoped_refptr<SocketWrapper> wrapper(socket_);
+  while (connect_state_ == TCP_SOCKET_CONNECTING) {
+    sys->Wait();
+    // Check close state before accessing any member variables since this
+    // instance might be destroyed while this thread was waiting.
+    if (wrapper->is_closed()) {
+      errno = EBADF;
+      return -1;
+    }
+  }
+
+  if (connect_state_ == TCP_SOCKET_ERROR) {
+    errno = connect_error_;
+    return -1;
+  }
+
+  ALOG_ASSERT(connect_state_ == TCP_SOCKET_CONNECTED);
+  return 0;
+}
+
+off64_t TCPSocket::lseek(off64_t offset, int whence) {
+  errno = ESPIPE;
+  return -1;
+}
+
+ssize_t TCPSocket::read(void* buf, size_t count) {
+  if (connect_state_ == TCP_SOCKET_NEW ||
+      connect_state_ == TCP_SOCKET_LISTENING) {
+    errno = ENOTCONN;
+    return -1;
+  }
+
+  if (is_block()) {
+    scoped_refptr<SocketWrapper> wrapper(socket_);
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    const base::TimeTicks time_limit =
+        internal::TimeOutToTimeLimit(recv_timeout_);
+    bool is_timedout = false;
+    while (!is_timedout && !IsSelectReadReady() && !IsTerminated()) {
+      is_timedout = sys->WaitUntil(time_limit);
+      // Check close state before accessing any member variables since this
+      // instance might be destroyed while this thread was waiting.
+      if (wrapper->is_closed()) {
+        errno = EBADF;
+        return -1;
+      }
+    }
+  } else if (connect_state_ == TCP_SOCKET_CONNECTING) {
+    // Non-blocking and still connecting.
+    errno = EAGAIN;
+    return -1;
+  }
+
+  size_t nread = std::min(count, in_buf_.size());
+  if (nread) {
+    std::copy(in_buf_.begin(), in_buf_.begin() + nread,
+              reinterpret_cast<char*>(buf));
+    in_buf_.erase(in_buf_.begin(), in_buf_.begin() + nread);
+    PostReadTaskLocked();
+    return nread;
+  }
+
+  if (!is_connected() || eof_)
+    return 0;
+
+  errno = EAGAIN;
+  return -1;
+}
+
+ssize_t TCPSocket::recv(void *buf, size_t len, int flags) {
+  // TODO(crbug.com/242604): Handle flags such as MSG_DONTWAIT
+  return read(buf, len);
+}
+
+ssize_t TCPSocket::recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                            socklen_t* addrlen) {
+  // TODO(crbug.com/242604): Handle flags such as MSG_DONTWAIT
+  if (!addr && !addrlen)
+    return read(buf, len);
+  errno = EINVAL;
+  return -1;
+}
+
+ssize_t TCPSocket::write(const void* buf, size_t count) {
+  if (!is_connected()) {
+    errno = EPIPE;
+    return -1;
+  }
+
+  bool is_blocking = is_block();
+
+  if (is_blocking && out_buf_.size() >= kBufSize) {
+    scoped_refptr<SocketWrapper> wrapper(socket_);
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    const base::TimeTicks time_limit =
+        internal::TimeOutToTimeLimit(send_timeout_);
+    bool is_timedout = false;
+    while (!is_timedout && out_buf_.size() >= kBufSize && is_connected()) {
+      is_timedout = sys->WaitUntil(time_limit);
+      // Check close state before accessing any member variables since this
+      // instance might be destroyed while this thread was waiting.
+      if (wrapper->is_closed()) {
+        errno = EBADF;
+        return -1;
+      }
+    }
+    if (!is_connected()) {
+      errno = EIO;
+      return -1;
+    }
+  }
+
+  if (out_buf_.size() < kBufSize) {
+    out_buf_.insert(out_buf_.end(),
+                    reinterpret_cast<const char*>(buf),
+                    reinterpret_cast<const char*>(buf) + count);
+    if (!write_sent_) {
+      pp::Module::Get()->core()->CallOnMainThread(
+          0, factory_.NewCallback(&TCPSocket::Write));
+    }
+    return count;
+  }
+
+  ALOG_ASSERT(!is_blocking);
+
+  errno = EAGAIN;
+  return -1;
+}
+
+ssize_t TCPSocket::send(const void* buf, size_t len, int flags) {
+  // TODO(crbug.com/242604): Handle flags such as MSG_DONTWAIT
+  return write(buf, len);
+}
+
+ssize_t TCPSocket::sendto(const void* buf, size_t len, int flags,
+                          const sockaddr* dest_addr, socklen_t addrlen) {
+  // TODO(crbug.com/242604): Handle flags such as MSG_DONTWAIT
+  if (!dest_addr && !addrlen)
+    return write(buf, len);
+  errno = EINVAL;
+  return -1;
+}
+
+int TCPSocket::ioctl(int request, va_list ap) {
+  if (request == FIONREAD) {
+    int* out = va_arg(ap, int*);
+    *out = in_buf_.size();
+    return 0;
+  } else {
+    errno = EINVAL;
+    return -1;
+  }
+}
+
+bool TCPSocket::GetOptNameData(
+    int level, int optname, socklen_t* len, void** storage,
+    const void* user_data, socklen_t user_data_len) {
+  // We cannot use SIZEOF_AS_SOCKLEN(int) for this as the linter is
+  // confused by this and emits two warnings (readability/casting and
+  // readability/function).
+  static const socklen_t sizeof_int = sizeof(int);  // NOLINT(runtime/sizeof)
+  if (level == IPPROTO_TCP) {
+    switch (optname) {
+      case TCP_NODELAY:
+        *storage = &no_delay_;
+        *len = sizeof_int;
+        ALOG_ASSERT(*len == sizeof(no_delay_));
+        return true;
+    }
+  }
+
+  return SocketStream::GetOptNameData(level, optname, len, storage, user_data,
+                                      user_data_len);
+}
+
+int TCPSocket::setsockopt(int level, int optname, const void* optval,
+                          socklen_t optlen) {
+  if (level == SOL_IPV6 && optname == IPV6_V6ONLY) {
+    // Currently, IPV6_V6ONLY is not supported by pepper.
+    // This is just a work around until it is supported. The default value of
+    // IPV6_V6ONLY is 0 (false). Some applications try to set the 0
+    // explicitly and fail if it is not supported. So, here, we return
+    // 0 (success) only if *optval is 0.
+    // TODO(crbug.com/371334): Use pepper's IPV6_V6ONLY option when supported.
+    if (optlen < SIZEOF_AS_SOCKLEN(int) ||  // NOLINT(readability/casting)
+        *static_cast<const int*>(optval) != 0) {
+      errno = EINVAL;
+      return -1;
+    }
+    return 0;
+  }
+
+  int no_delay = no_delay_;
+  int result = SocketStream::setsockopt(level, optname, optval, optlen);
+  if (result != 0)
+    return result;
+
+  if (no_delay == no_delay_)
+    return 0;
+
+  scoped_refptr<SocketWrapper> wrapper(socket_);
+  int32_t pp_error = PP_OK_COMPLETIONPENDING;
+  {
+    base::AutoUnlock unlock(
+        VirtualFileSystem::GetVirtualFileSystem()->mutex());
+    pp_error = wrapper->socket()->SetOption(
+        PP_TCPSOCKET_OPTION_NO_DELAY, pp::Var(no_delay_ ? true : false),
+        pp::BlockUntilComplete());
+  }
+  ARC_STRACE_REPORT_PP_ERROR(pp_error);
+  // Check close state before accessing any member variables since this
+  // instance might be destroyed while this thread was waiting.
+  if (wrapper->is_closed()) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (pp_error != PP_OK) {
+    errno = ENOPROTOOPT;  // TODO(crbug.com/358932): Pick correct errno.
+    return -1;
+  }
+  return 0;
+}
+
+int TCPSocket::getpeername(sockaddr* name, socklen_t* namelen) {
+  int error = internal::VerifyOutputSocketAddress(name, namelen);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  sockaddr_storage storage;
+  if (!internal::NetAddressToSockAddrStorage(
+          socket_->socket()->GetRemoteAddress(), AF_UNSPEC, false, &storage)) {
+    memset(&storage, 0, sizeof(storage));
+    storage.ss_family = socket_family_;
+  }
+
+  internal::CopySocketAddress(storage, name, namelen);
+  return 0;
+}
+
+int TCPSocket::getsockname(sockaddr* name, socklen_t* namelen) {
+  int error = internal::VerifyOutputSocketAddress(name, namelen);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  sockaddr_storage storage;
+  if (!internal::NetAddressToSockAddrStorage(
+          socket_->socket()->GetLocalAddress(), AF_UNSPEC, false, &storage)) {
+    memset(&storage, 0, sizeof(storage));
+    storage.ss_family = socket_family_;
+  }
+
+  internal::CopySocketAddress(storage, name, namelen);
+  return 0;
+}
+
+bool TCPSocket::IsSelectReadReady() const {
+  // Closed socket should return an error without blocking.
+  if (socket_->is_closed())
+    return true;
+
+  switch (connect_state_) {
+    case TCP_SOCKET_NEW:
+      // If the socket is neither connected nor listening, the socket is
+      // considered read_ready, as the read() should return error without
+      // blocking.
+      return true;
+    case TCP_SOCKET_CONNECTING:
+      // If the socket is connecting, no readable data is available.
+      return false;
+    case TCP_SOCKET_CONNECTED:
+      // A connected socket is considered read_ready if there is data
+      // available for reading, or if EOF has been detected.
+      return !in_buf_.empty() || eof_;
+    case TCP_SOCKET_LISTENING:
+      // A listening socket is considered read_ready when there is a
+      // connection waiting to be accepted.
+      return !accepted_socket_.is_null();
+    case TCP_SOCKET_ERROR:
+      // On error, the read() should return error without blocking.
+      return true;
+    default:
+      // Should not reach here.
+      ALOG_ASSERT(false);
+  }
+  return false;
+}
+
+bool TCPSocket::IsSelectWriteReady() const {
+  // Closed socket should return an error without blocking.
+  if (socket_->is_closed())
+    return true;
+
+  switch (connect_state_) {
+    case TCP_SOCKET_NEW:
+      // If the socket is neither connected nor listening, the socket is
+      // considered write_ready, as the write() should return error without
+      // blocking.
+      return true;
+    case TCP_SOCKET_CONNECTING:
+      // If the socket is connecting, the socket is not yet writable.
+      return false;
+    case TCP_SOCKET_CONNECTED:
+      // A connected socket is considered write_ready if there is some space
+      // available in the internal buffer.
+      return out_buf_.size() < kBufSize;
+    case TCP_SOCKET_LISTENING:
+      // The listening socket is unwritable.
+      return false;
+    case TCP_SOCKET_ERROR:
+      // On error, the write() should return error without blocking.
+      return true;
+    default:
+      // Should not reach here.
+      ALOG_ASSERT(false);
+  }
+  return false;
+}
+
+bool TCPSocket::IsSelectExceptionReady() const {
+  return connect_state_ == TCP_SOCKET_ERROR;
+}
+
+int16_t TCPSocket::GetPollEvents() const {
+  // Currently we use IsSelect*Ready() family temporarily (and wrongly).
+  // TODO(crbug.com/359400): Fix the implementation.
+  return ((IsSelectReadReady() ? POLLIN : 0) |
+          (IsSelectWriteReady() ? POLLOUT : 0) |
+          (IsSelectExceptionReady() ? POLLERR : 0));
+}
+
+void TCPSocket::OnLastFileRef() {
+  ALOG_ASSERT(!socket_->is_closed());
+  CloseLocked();
+}
+
+bool TCPSocket::IsTerminated() const {
+  return socket_->is_closed() || connect_state_ == TCP_SOCKET_ERROR;
+}
+
+void TCPSocket::PostReadTaskLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (!is_connected() || read_sent_) {
+    return;  // No more async reads.
+  }
+  if (in_buf_.size() >= kBufSize / 2) {
+    return;  // Enough to read locally.
+  }
+  if (eof_) {
+    return;  // We already hit the EOF.
+  }
+  read_sent_ = true;
+  if (!pp::Module::Get()->core()->IsMainThread()) {
+    pp::Module::Get()->core()->CallOnMainThread(
+        0, factory_.NewCallback(&TCPSocket::Read));
+  } else {
+    // If on main Pepper thread call it directly.
+    ReadLocked();
+  }
+}
+
+void TCPSocket::Accept(int32_t result) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+
+  int32_t pp_error = socket_->socket()->Accept(
+      factory_.NewCallbackWithOutput(&TCPSocket::OnAccept));
+  ALOG_ASSERT(pp_error == PP_OK_COMPLETIONPENDING);
+}
+
+void TCPSocket::OnAccept(int32_t result, const pp::TCPSocket& accepted_socket) {
+  // TODO(crbug.com/364744): Handle error cases.
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  ALOG_ASSERT(accepted_socket_.is_null());
+  accepted_socket_ = accepted_socket;
+  sys->Broadcast();
+  NotifyListeners();
+}
+
+void TCPSocket::Connect(int32_t result, const pp::NetAddress& address) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  // A closed socket means we are in destructor. On the other hand,
+  // error should not happen in connect.
+  ALOG_ASSERT(connect_state_ == TCP_SOCKET_CONNECTING);
+  int32_t pp_error = socket_->socket()->Connect(
+      address, factory_.NewCallback(&TCPSocket::OnConnect));
+  ALOG_ASSERT(pp_error == PP_OK_COMPLETIONPENDING);
+}
+
+void TCPSocket::OnConnect(int32_t result) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  // A closed socket means we are in destructor. On the other hand,
+  // error should not happen in connect.
+  ALOG_ASSERT(connect_state_ == TCP_SOCKET_CONNECTING);
+  if (result == PP_OK) {
+    connect_state_ = TCP_SOCKET_CONNECTED;
+    PostReadTaskLocked();
+    NotifyListeners();
+  } else {
+    MarkAsErrorLocked(ECONNREFUSED);
+  }
+  sys->Broadcast();
+}
+
+void TCPSocket::Read(int32_t result) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  ReadLocked();
+}
+
+void TCPSocket::ReadLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (IsTerminated()) {
+    read_sent_ = false;
+    sys->Broadcast();
+    return;
+  }
+
+  int32_t pp_error = socket_->socket()->Read(
+      &read_buf_[0], read_buf_.size(),
+      factory_.NewCallback(&TCPSocket::OnRead));
+  ALOG_ASSERT(pp_error == PP_OK_COMPLETIONPENDING);
+}
+
+void TCPSocket::OnRead(int32_t result) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+
+  read_sent_ = false;
+  if (IsTerminated()) {
+    sys->Broadcast();
+    return;
+  }
+
+  if (result > 0) {
+    in_buf_.insert(in_buf_.end(),
+                   read_buf_.begin(), read_buf_.begin() + result);
+    PostReadTaskLocked();
+    NotifyListeners();
+  } else if (result == 0) {
+    eof_ = true;
+    NotifyListeners();
+  } else {
+    MarkAsErrorLocked(EIO);  // TODO(crbug.com/358932): Pick correct error.
+  }
+  sys->Broadcast();
+}
+
+void TCPSocket::Write(int32_t result) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  if (!write_sent_) {
+    WriteLocked();
+  }
+}
+
+void TCPSocket::WriteLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+  ALOG_ASSERT(!write_sent_);
+
+  if (IsTerminated()) {
+    sys->Broadcast();
+    return;
+  }
+  if (write_buf_.size() == 0) {
+    write_buf_.swap(out_buf_);
+  } else if (write_buf_.size() < kBufSize / 2) {
+    // Avoid to shift the content in out_buf_ too often by only allowing
+    // to move chunks either larger than kBufSize / 2 or coinciding with
+    // the whole out_buf_ buffer.
+    int size = std::min(kBufSize - write_buf_.size(), out_buf_.size());
+    write_buf_.insert(write_buf_.end(), out_buf_.begin(),
+                      out_buf_.begin() + size);
+    out_buf_.erase(out_buf_.begin(), out_buf_.begin() + size);
+  }
+
+  write_sent_ = true;
+  int32_t result = socket_->socket()->Write(
+      &write_buf_[0], write_buf_.size(),
+      factory_.NewCallback(&TCPSocket::OnWrite));
+  ALOG_ASSERT(result == PP_OK_COMPLETIONPENDING);
+}
+
+void TCPSocket::OnWrite(int32_t result) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+
+  write_sent_ = false;
+  if (IsTerminated()) {
+    sys->Broadcast();
+    return;
+  }
+
+  if (result < 0 || (size_t)result > write_buf_.size()) {
+    // Write error.
+    ALOGI("TCPSocket::OnWrite: close socket %d", fd_);
+    MarkAsErrorLocked(EIO);  // TODO(crbug.com/358932): Pick correct error.
+    sys->Broadcast();
+    return;
+  } else {
+    write_buf_.erase(write_buf_.begin(), write_buf_.begin() + (size_t)result);
+  }
+  if (!write_buf_.empty() || !out_buf_.empty()) {
+    WriteLocked();
+  }
+  sys->Broadcast();
+  NotifyListeners();
+}
+
+void TCPSocket::CloseLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  // Wait for write operations to complete
+  // TODO(crbug.com/351755): Refactor code so that close can't hang for ever.
+  while (write_sent_ && is_connected()) {
+    sys->Wait();
+  }
+
+  // Post task to the main thread, so that any pending tasks on main thread
+  // will be canceled.
+  int32_t result = PP_OK_COMPLETIONPENDING;
+  pp::Module::Get()->core()->CallOnMainThread(
+      0, factory_.NewCallback(&TCPSocket::Close, &result));
+  while (result == PP_OK_COMPLETIONPENDING)
+    sys->Wait();
+  ARC_STRACE_REPORT_PP_ERROR(result);
+}
+
+void TCPSocket::Close(int32_t result, int32_t* pres) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  factory_.CancelAll();
+  socket_->Close();
+  *pres = PP_OK;
+  // Don't access any member variable after sys->Browadcast() is called.
+  // It may make destructor have completed.
+  NotifyListeners();
+  sys->Broadcast();
+}
+
+const char* TCPSocket::GetStreamType() const {
+  return "tcp";
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/tcp_socket.h b/src/posix_translation/tcp_socket.h
new file mode 100644
index 0000000..1721563
--- /dev/null
+++ b/src/posix_translation/tcp_socket.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_TCP_SOCKET_H_
+#define POSIX_TRANSLATION_TCP_SOCKET_H_
+
+#include <fcntl.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "posix_translation/socket_stream.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/tcp_socket.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace pp {
+class NetAddress;
+}  // namespace pp
+
+namespace posix_translation {
+
+class TCPSocket : public SocketStream {
+ public:
+  TCPSocket(int fd, int socket_family, int oflag);
+
+  virtual int bind(const sockaddr* addr, socklen_t addrlen) OVERRIDE;
+  virtual int listen(int backlog) OVERRIDE;
+  virtual int accept(sockaddr* addr, socklen_t* addrlen) OVERRIDE;
+  virtual int connect(const sockaddr* addr, socklen_t addrlen) OVERRIDE;
+
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE;
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t recv(void* buf, size_t len, int flags) OVERRIDE;
+  virtual ssize_t recvfrom(void* buf, size_t len, int flags, sockaddr* addr,
+                           socklen_t* addrlen) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+  virtual ssize_t send(const void* buf, size_t len, int flags) OVERRIDE;
+  virtual ssize_t sendto(const void* buf, size_t len, int flags,
+                         const sockaddr* dest_addr, socklen_t addrlen) OVERRIDE;
+
+  virtual int ioctl(int request, va_list ap) OVERRIDE;
+
+  virtual int setsockopt(int level, int optname, const void* optval,
+                         socklen_t optlen) OVERRIDE;
+  virtual int getpeername(sockaddr* name, socklen_t* namelen) OVERRIDE;
+  virtual int getsockname(sockaddr* name, socklen_t* namelen) OVERRIDE;
+
+  virtual bool IsSelectReadReady() const OVERRIDE;
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+  virtual bool IsSelectExceptionReady() const OVERRIDE;
+  virtual int16_t GetPollEvents() const OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~TCPSocket();
+  virtual bool GetOptNameData(int level, int optname, socklen_t* len,
+                              void** storage, const void* user_data,
+                              socklen_t user_data_len) OVERRIDE;
+  virtual void OnLastFileRef() OVERRIDE;
+
+ private:
+  friend class PepperTCPSocketTest;
+
+  class SocketWrapper;
+
+  enum ConnectState {
+    TCP_SOCKET_NEW,
+    TCP_SOCKET_CONNECTING,
+    TCP_SOCKET_CONNECTED,
+    TCP_SOCKET_LISTENING,
+    TCP_SOCKET_ERROR,
+  };
+
+  // This is a constructor to create a TCPSocket for accepting a connection.
+  // TODO(hidehiko): Unify this overloaded constructor with the one declared
+  // as public above.
+  explicit TCPSocket(const pp::TCPSocket& socket);
+
+  bool is_block() const { return !(oflag() & O_NONBLOCK); }
+
+  bool is_connected() const {
+    return connect_state_ == TCP_SOCKET_CONNECTED;
+  }
+
+  // Returns true if the socket is already closed, or an error has occured
+  // before (or on background task).
+  bool IsTerminated() const;
+
+  void MarkAsErrorLocked(int error);
+
+  void PostReadTaskLocked();
+
+  void Accept(int32_t result);
+  void OnAccept(int32_t result, const pp::TCPSocket& socket);
+
+  void Connect(int32_t result, const pp::NetAddress& address);
+  void OnConnect(int32_t result);
+
+  void Read(int32_t result);
+  void ReadLocked();
+  void OnRead(int32_t result);
+
+  void Write(int32_t result);
+  void WriteLocked();
+  void OnWrite(int32_t result);
+
+  void CloseLocked();
+  void Close(int32_t result, int32_t* pres);
+
+  static const size_t kBufSize = 64 * 1024;
+
+  int fd_;
+  std::string hostname_;
+  pp::CompletionCallbackFactory<TCPSocket> factory_;
+  scoped_refptr<SocketWrapper> socket_;
+  std::vector<char> in_buf_;
+  std::vector<char> out_buf_;
+  std::vector<char> read_buf_;
+  std::vector<char> write_buf_;
+  ConnectState connect_state_;
+  bool eof_;
+  bool read_sent_;
+  bool write_sent_;
+  int connect_error_;
+
+  // The socket accepted on background, which will be returned when accept()
+  // is called.
+  pp::TCPSocket accepted_socket_;
+
+  // Storage for TCP_NODELAY's optval. This is int, rather than bool, to keep
+  // the value passed via setsockopt as is.
+  int no_delay_;
+
+  DISALLOW_COPY_AND_ASSIGN(TCPSocket);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TCP_SOCKET_H_
diff --git a/src/posix_translation/tcp_socket_test.cc b/src/posix_translation/tcp_socket_test.cc
new file mode 100644
index 0000000..3dfd838
--- /dev/null
+++ b/src/posix_translation/tcp_socket_test.cc
@@ -0,0 +1,364 @@
+// Copyright 2014 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.
+//
+// Unit tests for TCP sockets.
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "gtest/gtest.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/socket_util.h"
+#include "posix_translation/tcp_socket.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi_mocks/background_test.h"
+#include "ppapi_mocks/background_thread.h"
+#include "ppapi_mocks/ppb_net_address.h"
+#include "ppapi_mocks/ppb_tcp_socket.h"
+
+using ::testing::NiceMock;
+using ::testing::DoAll;
+
+namespace posix_translation {
+
+// Thin wrapper of base::WaitableEvent::Wait() to block the main thread.
+void WaitEvent(void* user_data, int32_t result) {
+  base::WaitableEvent* event =
+      reinterpret_cast<base::WaitableEvent*>(user_data);
+  event->Wait();
+}
+
+// Thin Wrapper of base::WaitableEvent::Signal() to run it on the main thread.
+void SignalEvent(void* user_data, int32_t result) {
+  base::WaitableEvent* event =
+      reinterpret_cast<base::WaitableEvent*>(user_data);
+  event->Signal();
+}
+
+#define EXPECT_ERROR(expected_error, result) do { \
+    EXPECT_EQ(-1, result); \
+    EXPECT_EQ(expected_error, errno); \
+    errno = 0; \
+  } while (0)
+
+class PepperTCPSocketTest
+    : public FileSystemBackgroundTestCommon<PepperTCPSocketTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(ConnectSuccess);
+  DECLARE_BACKGROUND_TEST(ConnectFail);
+  DECLARE_BACKGROUND_TEST(SetOptionSuccess);
+  DECLARE_BACKGROUND_TEST(SetOptionFail);
+  DECLARE_BACKGROUND_TEST(ConnectThenSetOption);
+  DECLARE_BACKGROUND_TEST(SetOptionThenConnect);
+// TODO(crbug.com/362175): qemu-arm cannot reliably emulate threading
+// functions so run them in a real ARM device.
+#if defined(__arm__)
+  DECLARE_BACKGROUND_TEST(QEMU_DISABLED_NonBlockingConnectSuccess);
+  DECLARE_BACKGROUND_TEST(QEMU_DISABLED_NonBlockingConnectFail);
+#else
+  DECLARE_BACKGROUND_TEST(NonBlockingConnectSuccess);
+  DECLARE_BACKGROUND_TEST(NonBlockingConnectFail);
+#endif
+
+ protected:
+  static const PP_Resource kTCPSocketResource = 74;
+
+  PepperTCPSocketTest()
+      : default_executor_(&bg_, PP_OK),
+        fail_executor_(&bg_, PP_ERROR_FAILED),
+        ppb_tcpsocket_(NULL) {
+  }
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemBackgroundTestCommon<PepperTCPSocketTest>::SetUp();
+    factory_.GetMock(&ppb_tcpsocket_);
+    factory_.GetMock(&ppb_netaddress_);
+
+    // We ignore DescribeAsString here, as it is used only for logging.
+    EXPECT_CALL(*ppb_netaddress_, DescribeAsString(_, _))
+        .WillRepeatedly(Return(ppapi_mocks::VarFromString("")));
+    pending_callbacks_.clear();
+  }
+
+  virtual void TearDown() OVERRIDE {
+    // Run all callbacks with abort error code.
+    for (size_t i = 0; i < pending_callbacks_.size(); ++i) {
+      PP_RunCompletionCallback(&pending_callbacks_[i], PP_ERROR_ABORTED);
+    }
+    FileSystemBackgroundTestCommon<PepperTCPSocketTest>::TearDown();
+  }
+
+  // Add callback which will be aborted later.
+  void AddPendingCallback(const PP_CompletionCallback& callback) {
+    pending_callbacks_.push_back(callback);
+  }
+
+  void ExpectTCPSocketInstance() {
+    // Create and release.
+    EXPECT_CALL(*ppb_tcpsocket_, Create(kInstanceNumber)).
+        WillOnce(Return(kTCPSocketResource));
+  }
+
+  void ExpectConnectSuccess() {
+    EXPECT_CALL(*ppb_tcpsocket_, Connect(kTCPSocketResource, _, _)).
+        WillOnce(WithArgs<2>(
+            Invoke(&default_executor_,
+                   &CompletionCallbackExecutor::ExecuteOnMainThread)));
+
+    // On success of TCPSocket::Connect(), pp::TCPSocket::Read() is called
+    // on the main thread. Also, keep the callback, which will be aborted in
+    // TearDown(), otherwise the resource will be leaked.
+    EXPECT_CALL(*ppb_tcpsocket_, Read(kTCPSocketResource, _, _, _)).
+        WillOnce(DoAll(
+            WithArgs<3>(
+                Invoke(this, &PepperTCPSocketTest::AddPendingCallback)),
+            Return(static_cast<int32_t>(PP_OK_COMPLETIONPENDING))));
+  }
+
+  void ExpectConnectFail() {
+    EXPECT_CALL(*ppb_tcpsocket_, Connect(kTCPSocketResource, _, _)).
+        WillOnce(WithArgs<2>(
+            Invoke(&fail_executor_,
+                   &CompletionCallbackExecutor::ExecuteOnMainThread)));
+  }
+
+  void ExpectSetOptionNoDelaySuccess() {
+    EXPECT_CALL(*ppb_tcpsocket_,
+                SetOption(kTCPSocketResource,
+                          PP_TCPSOCKET_OPTION_NO_DELAY, _, _)).
+        WillOnce(WithArgs<3>(
+            Invoke(&default_executor_,
+                   &CompletionCallbackExecutor::ExecuteOnMainThread)));
+  }
+
+  void ExpectSetOptionNoDelayFail() {
+    EXPECT_CALL(*ppb_tcpsocket_,
+                SetOption(kTCPSocketResource,
+                          PP_TCPSOCKET_OPTION_NO_DELAY, _, _)).
+        WillOnce(WithArgs<3>(
+            Invoke(&fail_executor_,
+                   &CompletionCallbackExecutor::ExecuteOnMainThread)));
+  }
+
+  int fcntl(int sockfd, int cmd, ...) {
+    int result;
+    va_list ap;
+    va_start(ap, cmd);
+    result = file_system_->fcntl(sockfd, cmd, ap);
+    va_end(ap);
+    return result;
+  }
+
+  int connect(int sockfd) {
+    sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_port = htons(2048);
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    return file_system_->connect(sockfd, reinterpret_cast<sockaddr*>(&addr),
+                                 sizeof(addr));
+  }
+
+  int set_nodelay_option(int sockfd) {
+    int one = 1;
+    return file_system_->setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one,
+                                    static_cast<socklen_t>(sizeof(one)));
+  }
+
+  int set_non_block(int sockfd) {
+    int opts = fcntl(sockfd, F_GETFL);
+    if (opts < 0) return opts;
+    return fcntl(sockfd, F_SETFL, opts | O_NONBLOCK);
+  }
+
+  void ExpectSoError(int sockfd, int expected) {
+    int optval;
+    socklen_t optlen = SIZEOF_AS_SOCKLEN(optval);
+    EXPECT_EQ(0, file_system_->getsockopt(sockfd, SOL_SOCKET, SO_ERROR,
+                                          &optval, &optlen));
+    EXPECT_EQ(expected, optval);
+  }
+
+  void ExpectPollEvent(int sockfd, int expected_events, int timeout) {
+    struct pollfd poller;
+    poller.fd = sockfd;
+    poller.events = POLLIN | POLLOUT;
+    poller.revents = 0;
+    EXPECT_EQ(expected_events != 0 ? 1 : 0,
+              file_system_->poll(&poller, 1, timeout));
+    EXPECT_EQ(expected_events, poller.revents);
+  }
+
+  CompletionCallbackExecutor default_executor_;
+  CompletionCallbackExecutor fail_executor_;
+  std::vector<PP_CompletionCallback> pending_callbacks_;
+  NiceMock<PPB_TCPSocket_Mock>* ppb_tcpsocket_;
+  NiceMock<PPB_NetAddress_Mock>* ppb_netaddress_;
+};
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, ConnectSuccess) {
+  ExpectTCPSocketInstance();
+  ExpectConnectSuccess();
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, connect(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, ConnectFail) {
+  ExpectTCPSocketInstance();
+  ExpectConnectFail();
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_ERROR(ECONNREFUSED, connect(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, SetOptionSuccess) {
+  ExpectTCPSocketInstance();
+  ExpectSetOptionNoDelaySuccess();
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, set_nodelay_option(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, SetOptionFail) {
+  ExpectTCPSocketInstance();
+  ExpectSetOptionNoDelayFail();
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_ERROR(ENOPROTOOPT, set_nodelay_option(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, ConnectThenSetOption) {
+  ExpectTCPSocketInstance();
+  ExpectConnectSuccess();
+  ExpectSetOptionNoDelaySuccess();
+
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, connect(sockfd));
+  EXPECT_EQ(0, set_nodelay_option(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+TEST_BACKGROUND_F(PepperTCPSocketTest, SetOptionThenConnect) {
+  ExpectTCPSocketInstance();
+  ExpectConnectSuccess();
+  ExpectSetOptionNoDelaySuccess();
+
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, set_nodelay_option(sockfd));
+  EXPECT_EQ(0, connect(sockfd));
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+// TODO(crbug.com/362175): qemu-arm cannot reliably emulate threading
+// functions so run them in a real ARM device.
+#if defined(__arm__)
+TEST_BACKGROUND_F(PepperTCPSocketTest, QEMU_DISABLED_NonBlockingConnectSuccess)
+#else
+TEST_BACKGROUND_F(PepperTCPSocketTest, NonBlockingConnectSuccess)
+#endif
+{
+  ExpectTCPSocketInstance();
+  ExpectConnectSuccess();
+
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, set_non_block(sockfd));
+
+  // Block the main thread so this code can run tests on the state of |sockfd|
+  // before the Pepper main thread callbacks have executed.
+  base::WaitableEvent event1(true, false);
+  bg_.CallOnMainThread(
+      0, PP_MakeCompletionCallback(&WaitEvent, &event1), 0);
+
+  // First time, no background connect() task runs so EINPROGRESS should be
+  // raised.
+  EXPECT_ERROR(EINPROGRESS, connect(sockfd));
+
+  // Second time, there is a background connect() (initiated by above connect),
+  // so EALREADY should be raised.
+  EXPECT_ERROR(EALREADY, connect(sockfd));
+
+  // Make sure no poll flag is on.
+  ExpectPollEvent(sockfd, 0, 0);
+
+  // Here a task to run TCPSocket::Connect is enqueued to the main thread.
+  // So, we unblock the thread and wait the pending task's completion.
+  base::WaitableEvent event2(true, false);
+  bg_.CallOnMainThread(
+      0, PP_MakeCompletionCallback(&SignalEvent, &event2), 0);
+  event1.Signal();
+  event2.Wait();
+
+  // Here, the connection is established. So, now it should be writable.
+  ExpectPollEvent(sockfd, POLLOUT, 0);
+
+  ExpectSoError(sockfd, 0);
+
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+// TODO(crbug.com/362175): qemu-arm cannot reliably emulate threading
+// functions so run them in a real ARM device.
+#if defined(__arm__)
+TEST_BACKGROUND_F(PepperTCPSocketTest, QEMU_DISABLED_NonBlockingConnectFail)
+#else
+TEST_BACKGROUND_F(PepperTCPSocketTest, NonBlockingConnectFail)
+#endif
+{
+  ExpectTCPSocketInstance();
+  ExpectConnectFail();
+
+  int sockfd = file_system_->socket(AF_INET, SOCK_STREAM, 0);
+  EXPECT_NE(0, sockfd);
+  EXPECT_EQ(0, set_non_block(sockfd));
+
+  // Block the main thread.
+  base::WaitableEvent event1(true, false);
+  bg_.CallOnMainThread(
+      0, PP_MakeCompletionCallback(&WaitEvent, &event1), 0);
+
+  // First time, no background connect() task runs so EINPROGRESS should be
+  // raised.
+  EXPECT_ERROR(EINPROGRESS, connect(sockfd));
+
+  // Second time, there is a background connect() (initiated by above connect),
+  // so EALREADY should be raised.
+  EXPECT_ERROR(EALREADY, connect(sockfd));
+
+  // Make sure no poll flag is on.
+  ExpectPollEvent(sockfd, 0, 0);
+
+  // Here a task to run TCPSocket::Connect is enqueued to the main thread.
+  // So, we unblock the thread and wait the pending task's completion.
+  base::WaitableEvent event2(true, false);
+  bg_.CallOnMainThread(
+      0, PP_MakeCompletionCallback(&SignalEvent, &event2), 0);
+  event1.Signal();
+  event2.Wait();
+
+  // On error, all POLLIN, POLLOUT and POLLERR are raised.
+  ExpectPollEvent(sockfd, POLLIN | POLLOUT | POLLERR, 0);
+
+  ExpectSoError(sockfd, ECONNREFUSED);
+
+  EXPECT_EQ(0, file_system_->close(sockfd));
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/test_util/DEPS b/src/posix_translation/test_util/DEPS
new file mode 100644
index 0000000..ef2095e
--- /dev/null
+++ b/src/posix_translation/test_util/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ppapi_mocks"
+]
diff --git a/src/posix_translation/test_util/file_system_background_test_common.h b/src/posix_translation/test_util/file_system_background_test_common.h
new file mode 100644
index 0000000..276ffcb
--- /dev/null
+++ b/src/posix_translation/test_util/file_system_background_test_common.h
@@ -0,0 +1,120 @@
+// Copyright 2014 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 POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_BACKGROUND_TEST_COMMON_H_
+#define POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_BACKGROUND_TEST_COMMON_H_
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "common/process_emulator.h"
+#include "posix_translation/fd_to_file_stream_map.h"
+#include "posix_translation/mount_point_manager.h"
+#include "posix_translation/readonly_file.h"
+#include "posix_translation/test_util/file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi_mocks/background_test.h"
+#include "ppapi_mocks/background_thread.h"
+
+namespace posix_translation {
+
+// A class template that allows gtest tests to run in a non-main thread.
+// This is useful when the method to test has a check like
+//   ALOG_ASSERT(!pp::Module::Get()->core()->IsMainThread());
+// For more details, see ppapi_mocks/background_*.h.
+template <typename Derived>
+class FileSystemBackgroundTestCommon : public BackgroundTest<Derived>,
+                                       public FileSystemTestCommon {
+ public:
+  FileSystemBackgroundTestCommon()
+    : cc_factory_(static_cast<Derived*>(this)),
+      bg_(this) {
+    set_is_background_test(true);
+  }
+
+ protected:
+  virtual void SetUp() OVERRIDE {
+    FileSystemTestCommon::SetUp();
+    bg_.SetUp();
+  }
+
+  ino_t GetInode(const char* path) {
+    return file_system_->GetInodeLocked(path);
+  }
+  void RemoveInode(const char* path) {
+    file_system_->RemoveInodeLocked(path);
+  }
+  void ReassignInode(const char* oldpath, const char* newpath) {
+    file_system_->ReassignInodeLocked(oldpath, newpath);
+  }
+  void AddMountPoint(const std::string& path, FileSystemHandler* handler) {
+    file_system_->mount_points_->Add(path, handler);
+  }
+  void ChangeMountPointOwner(const std::string& path, uid_t uid) {
+    file_system_->mount_points_->ChangeOwner(
+        path, arc::ProcessEmulator::GetUid());
+  }
+  void RemoveMountPoint(const std::string& path) {
+    uid_t uid = 0;
+    ALOG_ASSERT(file_system_->mount_points_->GetFileSystemHandler(path, &uid));
+    file_system_->mount_points_->Remove(path);
+    ALOG_ASSERT(!file_system_->mount_points_->GetFileSystemHandler(path, &uid));
+  }
+  void ClearMountPoints() {
+    file_system_->mount_points_->Clear();
+  }
+  FileSystemHandler* GetFileSystemHandlerLocked(const std::string& path) {
+    return file_system_->GetFileSystemHandlerLocked(path, NULL);
+  }
+  int GetFirstUnusedDescriptor() {
+    return file_system_->fd_to_stream_->GetFirstUnusedDescriptor();
+  }
+  void AddFileStream(int fd, scoped_refptr<FileStream> stream) {
+    file_system_->fd_to_stream_->AddFileStream(fd, stream);
+  }
+  void ReplaceFileStream(int fd, scoped_refptr<FileStream> stream) {
+    file_system_->fd_to_stream_->ReplaceFileStream(fd, stream);
+  }
+  void RemoveFileStream(int fd) {
+    file_system_->fd_to_stream_->RemoveFileStream(fd);
+  }
+  bool IsKnownDescriptor(int fd) {
+    return file_system_->fd_to_stream_->IsKnownDescriptor(fd);
+  }
+  scoped_refptr<FileStream> GetStream(int fd) {
+    return file_system_->fd_to_stream_->GetStream(fd);
+  }
+  std::string GetNormalizedPath(const std::string& s,
+                                VirtualFileSystem::NormalizeOption option) {
+    std::string tmp(s);
+    file_system_->GetNormalizedPathLocked(&tmp, option);
+    return tmp;
+  }
+  base::Lock& mutex() {
+    return file_system_->mutex();
+  }
+
+  // Overridden from BackgroundTest<Derived>:
+  virtual BackgroundThread* GetBackgroundThread() OVERRIDE {
+    return &bg_;
+  }
+  virtual pp::CompletionCallbackFactory<Derived>*
+      GetCompletionCallbackFactory() OVERRIDE {
+    return &cc_factory_;
+  }
+
+  pp::CompletionCallbackFactory<Derived> cc_factory_;
+  BackgroundThread bg_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileSystemBackgroundTestCommon);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_BACKGROUND_TEST_COMMON_H_
diff --git a/src/posix_translation/test_util/file_system_test_base.h b/src/posix_translation/test_util/file_system_test_base.h
new file mode 100644
index 0000000..1b7d305
--- /dev/null
+++ b/src/posix_translation/test_util/file_system_test_base.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2013 The Chromium OS 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 POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_BASE_H_
+#define POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_BASE_H_
+
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi_mocks/ppapi_test.h"
+#include "ppapi_mocks/ppb_file_system.h"
+#include "ppapi_mocks/ppb_ext_crx_file_system_private.h"
+
+namespace posix_translation {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AnyNumber;
+using ::testing::Gt;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::WithArgs;
+
+class FileSystemTestBase {
+ public:
+  static const PP_Resource kFileSystemResource = 73;
+  explicit FileSystemTestBase(PpapiTest* t)
+      : ppapi_test_(t), num_callbacks_to_run_(0) {
+  }
+
+  void SetUpPepperFileSystemConstructExpectations(PP_Resource instance) {
+    PpapiTest* t = ppapi_test_;
+    EXPECT_CALL(*t->ppb_file_system_,
+                Create(instance,
+                       PP_FILESYSTEMTYPE_LOCALPERSISTENT)).
+        WillRepeatedly(Return(kFileSystemResource));
+    EXPECT_CALL(*t->ppb_file_system_,
+                Open(kFileSystemResource,
+                     Gt(1024*1024),  // Should be at least 1MB
+                     _)).
+        WillOnce(WithArgs<2>(Invoke(this, &FileSystemTestBase::HandleOpen)));
+    ++num_callbacks_to_run_;
+  }
+
+  void SetUpCrxFileSystemConstructExpectations(PP_Resource instance) {
+    PpapiTest* t = ppapi_test_;
+    EXPECT_CALL(*t->ppb_crxfs_, Open(_, _, _)).
+        WillOnce(WithArgs<2>(Invoke(this, &FileSystemTestBase::HandleOpen)));
+    ++num_callbacks_to_run_;
+  }
+
+  int32_t HandleOpen(PP_CompletionCallback cb) {
+    ppapi_test_->PushCompletionCallback(cb);
+    return PP_OK_COMPLETIONPENDING;
+  }
+
+  void RunCompletionCallbacks() {
+    PP_CompletionCallback cb;
+    for (int i = 0; i < num_callbacks_to_run_; ++i) {
+      cb = ppapi_test_->PopPendingCompletionCallback();
+      PP_RunCompletionCallback(&cb, PP_OK);
+    }
+  }
+
+ protected:
+  PpapiTest* ppapi_test_;
+  int num_callbacks_to_run_;
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_BASE_H_
diff --git a/src/posix_translation/test_util/file_system_test_common.cc b/src/posix_translation/test_util/file_system_test_common.cc
new file mode 100644
index 0000000..ab40e16
--- /dev/null
+++ b/src/posix_translation/test_util/file_system_test_common.cc
@@ -0,0 +1,40 @@
+// Copyright 2014 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 "posix_translation/test_util/file_system_test_common.h"
+
+#include "posix_translation/pepper_file.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+// We use 1 here because many of our tests expect that 0 is not managed by us.
+const int FileSystemTestCommon::kMinFdForTesting = 1;
+const int FileSystemTestCommon::kMaxFdForTesting = 1023;
+
+FileSystemTestCommon::FileSystemTestCommon()
+    : FileSystemTestBase(this),
+      is_background_test_(false),
+      current_directory_("/"),
+      current_umask_(0) {
+}
+
+void FileSystemTestCommon::SetUp() {
+  PpapiTest::SetUp();
+  file_system_ = new VirtualFileSystem(
+      instance_.get(), this, kMinFdForTesting, kMaxFdForTesting);
+  file_system_->SetBrowserReady();
+  if (!is_background_test_)
+    file_system_->mutex().Acquire();
+  // Ownedship of VirtualFileSystem is transferred.
+  SetVirtualFileSystemInterface(file_system_);
+}
+
+void FileSystemTestCommon::TearDown() {
+  if (!is_background_test_)
+    file_system_->mutex().Release();
+  SetVirtualFileSystemInterface(NULL);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/test_util/file_system_test_common.h b/src/posix_translation/test_util/file_system_test_common.h
new file mode 100644
index 0000000..394a5b6
--- /dev/null
+++ b/src/posix_translation/test_util/file_system_test_common.h
@@ -0,0 +1,67 @@
+// Copyright 2014 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 POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_COMMON_H_
+#define POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_COMMON_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "posix_translation/test_util/file_system_test_base.h"
+#include "posix_translation/memory_region.h"
+#include "posix_translation/process_environment.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi_mocks/ppapi_test.h"
+
+namespace posix_translation {
+
+class FileSystemTestCommon : public FileSystemTestBase,
+                             public PpapiTest,
+                             public ProcessEnvironment {
+ public:
+  static const int kMinFdForTesting;
+  static const int kMaxFdForTesting;
+
+  FileSystemTestCommon();
+
+  virtual std::string GetCurrentDirectory() const OVERRIDE {
+    return current_directory_;
+  }
+  virtual void SetCurrentDirectory(const std::string& dir) OVERRIDE {
+    current_directory_ = dir;
+  }
+
+  virtual mode_t GetCurrentUmask() const OVERRIDE {
+    return current_umask_;
+  }
+
+  virtual void SetCurrentUmask(mode_t mask) OVERRIDE {
+    current_umask_ = mask;
+  }
+
+ protected:
+  void set_is_background_test(bool is_background_test) {
+    is_background_test_ = is_background_test;
+  }
+  void SetMemoryMapAbortEnableFlags(bool enable) {
+    file_system_->abort_on_unexpected_memory_maps_ = enable;
+    file_system_->memory_region_->abort_on_unexpected_memory_maps_ = enable;
+  }
+  virtual void SetUp() OVERRIDE;
+  virtual void TearDown() OVERRIDE;
+
+  VirtualFileSystem* file_system_;  // Not owned
+  bool is_background_test_;
+
+ private:
+  std::string current_directory_;
+  mode_t current_umask_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileSystemTestCommon);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_FILE_SYSTEM_TEST_COMMON_H_
diff --git a/src/posix_translation/test_util/mmap_util.cc b/src/posix_translation/test_util/mmap_util.cc
new file mode 100644
index 0000000..e18222e
--- /dev/null
+++ b/src/posix_translation/test_util/mmap_util.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2014 The Chromium OS 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 "posix_translation/test_util/mmap_util.h"
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+namespace posix_translation {
+
+MmappedFile::MmappedFile() : fd_(-1), size_(0), data_(MAP_FAILED) {
+}
+
+MmappedFile::~MmappedFile() {
+  if (data_ != MAP_FAILED)
+    munmap(data_, size_);
+  if (fd_ >= 0)
+    close(fd_);
+}
+
+bool MmappedFile::Init(const std::string& file_name) {
+  struct stat buf;
+  if (stat(file_name.c_str(), &buf) != 0)
+    return false;
+  size_ = buf.st_size;
+
+  fd_ = open(file_name.c_str(), O_RDONLY);
+  if (fd_ == -1)
+    return false;
+
+  data_ = mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
+  if (data_ == MAP_FAILED) {
+    close(fd_);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/test_util/mmap_util.h b/src/posix_translation/test_util/mmap_util.h
new file mode 100644
index 0000000..a8b48d4
--- /dev/null
+++ b/src/posix_translation/test_util/mmap_util.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2014 The Chromium OS 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 POSIX_TRANSLATION_TEST_UTIL_MMAP_UTIL_H_
+#define POSIX_TRANSLATION_TEST_UTIL_MMAP_UTIL_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace posix_translation {
+
+// Memory-map a file in the read-only mode. The file will be unmapped and
+// closed at destruction time.
+class MmappedFile {
+ public:
+  MmappedFile();
+  ~MmappedFile();
+
+  // Memory-map a file at |file_name|. Returns true on success.
+  bool Init(const std::string& file_name);
+
+  // Points to the beginning of the mapped file contents, once Init() is
+  // successful. Otherwise, returns MAP_FAILED.
+  const char* data() const { return reinterpret_cast<const char*>(data_); }
+
+  // Returns the size of the mapped file.
+  size_t size() const { return size_; }
+
+ private:
+  int fd_;
+  size_t size_;
+  void* data_;
+
+  DISALLOW_COPY_AND_ASSIGN(MmappedFile);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_MMAP_UTIL_H_
diff --git a/src/posix_translation/test_util/mock_virtual_file_system.cc b/src/posix_translation/test_util/mock_virtual_file_system.cc
new file mode 100644
index 0000000..adb278a
--- /dev/null
+++ b/src/posix_translation/test_util/mock_virtual_file_system.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 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 "posix_translation/test_util/mock_virtual_file_system.h"
+
+namespace posix_translation {
+
+MockVirtualFileSystem::MockVirtualFileSystem()
+    : add_to_cache_callcount_(0) {
+}
+
+MockVirtualFileSystem::~MockVirtualFileSystem() {
+}
+
+void MockVirtualFileSystem::Mount(const std::string& path,
+                                  FileSystemHandler* handler) {
+}
+
+void MockVirtualFileSystem::Unmount(const std::string& path) {
+}
+
+void MockVirtualFileSystem::ChangeMountPointOwner(const std::string& path,
+                                                  uid_t owner_uid) {
+}
+
+void MockVirtualFileSystem::SetBrowserReady() {
+}
+
+void MockVirtualFileSystem::InvalidateCache() {
+}
+
+void MockVirtualFileSystem::AddToCache(const std::string& path,
+                                       const PP_FileInfo& file_info,
+                                       bool exists) {
+  if (exists) {
+    existing_cached_paths_.push_back(make_pair(path, file_info));
+  } else {
+    non_existing_cached_paths_.push_back(path);
+  }
+  ++add_to_cache_callcount_;
+}
+
+bool MockVirtualFileSystem::RegisterFileStream(
+    int fd, scoped_refptr<FileStream> stream) {
+  return true;
+}
+
+bool MockVirtualFileSystem::IsWriteMapped(ino_t inode) {
+  return false;
+}
+
+bool MockVirtualFileSystem::IsCurrentlyMapped(ino_t inode) {
+  return false;
+}
+
+FileSystemHandler* MockVirtualFileSystem::GetFileSystemHandler(
+    const std::string& path) {
+  return NULL;
+}
+
+std::string MockVirtualFileSystem::GetMemoryMapAsString() {
+  return "";
+}
+
+std::string MockVirtualFileSystem::GetIPCStatsAsString() {
+  return "";
+}
+
+int MockVirtualFileSystem::StatForTesting(
+    const std::string& pathname, struct stat* out) {
+  return 0;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/test_util/mock_virtual_file_system.h b/src/posix_translation/test_util/mock_virtual_file_system.h
new file mode 100644
index 0000000..4aa6300
--- /dev/null
+++ b/src/posix_translation/test_util/mock_virtual_file_system.h
@@ -0,0 +1,68 @@
+// Copyright 2014 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 POSIX_TRANSLATION_TEST_UTIL_MOCK_VIRTUAL_FILE_SYSTEM_H_
+#define POSIX_TRANSLATION_TEST_UTIL_MOCK_VIRTUAL_FILE_SYSTEM_H_
+
+#include <sys/stat.h>  // ino_t
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "posix_translation/virtual_file_system_interface.h"
+#include "ppapi/c/pp_file_info.h"
+
+namespace posix_translation {
+
+// A mock implementation of VirtualFileSystemInterface.
+class MockVirtualFileSystem : public VirtualFileSystemInterface {
+ public:
+  MockVirtualFileSystem();
+  virtual ~MockVirtualFileSystem();
+
+  virtual void Mount(const std::string& path,
+                     FileSystemHandler* handler) OVERRIDE;
+  virtual void Unmount(const std::string& path) OVERRIDE;
+  virtual void ChangeMountPointOwner(const std::string& path,
+                                     uid_t owner_uid) OVERRIDE;
+  virtual void SetBrowserReady() OVERRIDE;
+  virtual void InvalidateCache() OVERRIDE;
+  virtual void AddToCache(const std::string& path,
+                          const PP_FileInfo& file_info,
+                          bool exists) OVERRIDE;
+  virtual bool RegisterFileStream(int fd,
+                                  scoped_refptr<FileStream> stream) OVERRIDE;
+  virtual FileSystemHandler* GetFileSystemHandler(
+      const std::string& path) OVERRIDE;
+  virtual bool IsWriteMapped(ino_t inode) OVERRIDE;
+  virtual bool IsCurrentlyMapped(ino_t inode) OVERRIDE;
+  virtual std::string GetMemoryMapAsString() OVERRIDE;
+  virtual std::string GetIPCStatsAsString() OVERRIDE;
+  virtual int StatForTesting(
+      const std::string& pathname, struct stat* out) OVERRIDE;
+
+  // The call count of each function.
+  uint32_t add_to_cache_callcount() const { return add_to_cache_callcount_; }
+
+  const std::vector<std::string>& non_existing_cached_paths() const {
+    return non_existing_cached_paths_; }
+
+  const std::vector<std::pair<std::string, PP_FileInfo> >&
+      existing_cached_paths() const {
+        return existing_cached_paths_;
+      }
+
+ private:
+  uint32_t add_to_cache_callcount_;
+  std::vector<std::string> non_existing_cached_paths_;
+  std::vector<std::pair<std::string, PP_FileInfo> > existing_cached_paths_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockVirtualFileSystem);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_TEST_UTIL_MOCK_VIRTUAL_FILE_SYSTEM_H_
diff --git a/src/posix_translation/test_util/sysconf_util.cc b/src/posix_translation/test_util/sysconf_util.cc
new file mode 100644
index 0000000..846bac8
--- /dev/null
+++ b/src/posix_translation/test_util/sysconf_util.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 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 "posix_translation/test_util/sysconf_util.h"
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+namespace posix_translation {
+
+namespace {
+
+int g_online = -1;
+int g_configured = -1;
+
+typedef int (*sysconf_t)(int name);
+sysconf_t g_libc_sysconf;
+
+void InitSysconfPtr() {
+  if (g_libc_sysconf)
+    return;
+  void* handle = dlopen("libc.so", RTLD_LAZY | RTLD_LOCAL);
+  assert(handle);
+  g_libc_sysconf = reinterpret_cast<sysconf_t>(dlsym(handle, "sysconf"));
+  assert(g_libc_sysconf);
+  // Leave the |handle| open, which is okay for unit testing.
+}
+
+}  // namespace
+
+ScopedNumProcessorsOnlineSetting::ScopedNumProcessorsOnlineSetting(
+    int num) {
+  g_online = num;
+}
+
+ScopedNumProcessorsOnlineSetting::~ScopedNumProcessorsOnlineSetting() {
+  g_online = -1;
+}
+
+ScopedNumProcessorsConfiguredSetting::ScopedNumProcessorsConfiguredSetting(
+    int num) {
+  g_configured = num;
+}
+
+ScopedNumProcessorsConfiguredSetting::~ScopedNumProcessorsConfiguredSetting() {
+  g_configured = -1;
+}
+
+// Overrides libc's sysconf().
+extern "C" int sysconf(int name) {
+  switch (name) {
+    case _SC_NPROCESSORS_ONLN:
+      if (g_online == -1)
+        break;
+      return g_online;
+    case _SC_NPROCESSORS_CONF:
+      if (g_configured == -1)
+        break;
+      return g_configured;
+    default:
+      break;
+  }
+  // Fall back to libc's. This is necessary since posix_translation calls
+  // sysconf to retrieve the page size.
+  InitSysconfPtr();
+  return g_libc_sysconf(name);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/test_util/sysconf_util.h b/src/posix_translation/test_util/sysconf_util.h
new file mode 100644
index 0000000..37225f1
--- /dev/null
+++ b/src/posix_translation/test_util/sysconf_util.h
@@ -0,0 +1,28 @@
+// Copyright 2014 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 POSIX_TRANSLATION_TEST_UTIL_SYSCONF_UTIL_H_
+#define POSIX_TRANSLATION_TEST_UTIL_SYSCONF_UTIL_H_
+
+#include "base/basictypes.h"
+
+namespace posix_translation {
+
+// A class for temporarily overriding sysconf(_SC_NPROCESSORS_ONLN) result.
+class ScopedNumProcessorsOnlineSetting {
+ public:
+  explicit ScopedNumProcessorsOnlineSetting(int num);
+  ~ScopedNumProcessorsOnlineSetting();
+};
+
+// A class for temporarily overriding sysconf(_SC_NPROCESSORS_CONF) result.
+class ScopedNumProcessorsConfiguredSetting {
+ public:
+  explicit ScopedNumProcessorsConfiguredSetting(int num);
+  ~ScopedNumProcessorsConfiguredSetting();
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_SYSCONF_UTIL_H_
diff --git a/src/posix_translation/test_util/virtual_file_system_test_common.h b/src/posix_translation/test_util/virtual_file_system_test_common.h
new file mode 100644
index 0000000..9a629dd
--- /dev/null
+++ b/src/posix_translation/test_util/virtual_file_system_test_common.h
@@ -0,0 +1,21 @@
+// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Constants and utility functions shared in virtual_file_system_*test.cc
+
+#ifndef POSIX_TRANSLATION_TEST_UTIL_VIRTUAL_FILE_SYSTEM_TEST_COMMON_H_
+#define POSIX_TRANSLATION_TEST_UTIL_VIRTUAL_FILE_SYSTEM_TEST_COMMON_H_
+
+#include <string>
+
+namespace posix_translation {
+
+#define EXPECT_ERROR(result, expected_error)     \
+  EXPECT_EQ(-1, result);                         \
+  EXPECT_EQ(expected_error, errno);              \
+  errno = 0;
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TEST_UTIL_VIRTUAL_FILE_SYSTEM_TEST_COMMON_H_
diff --git a/src/posix_translation/time_util.cc b/src/posix_translation/time_util.cc
new file mode 100644
index 0000000..e7c5e71
--- /dev/null
+++ b/src/posix_translation/time_util.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 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 "posix_translation/time_util.h"
+
+#include "base/synchronization/condition_variable.h"
+#include "common/arc_strace.h"
+#include "common/alog.h"
+
+namespace posix_translation {
+namespace internal {
+namespace {
+
+const int64_t kMicrosecondsPerSecond = 1000 * 1000;
+
+// Main implementation of WaitUntil().
+bool WaitUntilInternal(base::ConditionVariable* condition_variable,
+                       const base::TimeTicks& time_limit) {
+  ALOG_ASSERT(condition_variable);
+
+  // Wait without timeout.
+  if (time_limit.is_null()) {
+    condition_variable->Wait();
+    return false;
+  }
+
+  base::TimeTicks start_time = base::TimeTicks::Now();
+  if (time_limit <= start_time) {
+    // The time limit was already expired.
+    return true;
+  }
+
+  condition_variable->TimedWait(time_limit - start_time);
+  base::TimeTicks end_time = base::TimeTicks::Now();
+  return time_limit <= end_time;
+}
+
+}  // namespace
+
+base::TimeDelta TimeValToTimeDelta(const timeval& time) {
+  return base::TimeDelta::FromMicroseconds(
+      time.tv_sec * kMicrosecondsPerSecond + time.tv_usec);
+}
+
+timeval TimeDeltaToTimeVal(const base::TimeDelta& time) {
+  int64_t usec = time.InMicroseconds();
+  time_t tv_sec = usec / kMicrosecondsPerSecond;
+  suseconds_t tv_usec = usec % kMicrosecondsPerSecond;
+  if (tv_usec < 0) {
+    // If tv_usec is out of range [0, 1000000) (this happens usec is negative
+    // and in such as case tv_usec is in the range of (-1000000, 0)),
+    // borrow from tv_sec. Note that it is ok for tv_sec to be negative.
+    tv_sec -= 1;
+    tv_usec += kMicrosecondsPerSecond;
+  }
+
+  timeval result = {};
+  result.tv_sec = tv_sec;
+  result.tv_usec = tv_usec;
+  return result;
+}
+
+base::TimeTicks TimeOutToTimeLimit(const base::TimeDelta& timeout_period) {
+  if (timeout_period == base::TimeDelta())
+    return base::TimeTicks();
+  return base::TimeTicks::Now() + timeout_period;
+}
+
+bool WaitUntil(base::ConditionVariable* condition_variable,
+               const base::TimeTicks& time_limit) {
+  bool result = WaitUntilInternal(condition_variable, time_limit);
+  ARC_STRACE_REPORT(
+      "WaitUntil: result=%s, time_limit=%lld",
+      (result ? "timedout" : "signaled"), time_limit.ToInternalValue());
+  return result;
+}
+
+}  // namespace internal
+}  // namespace posix_translation
diff --git a/src/posix_translation/time_util.h b/src/posix_translation/time_util.h
new file mode 100644
index 0000000..0a724f3
--- /dev/null
+++ b/src/posix_translation/time_util.h
@@ -0,0 +1,48 @@
+// Copyright 2014 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 POSIX_TRANSLATION_TIME_UTIL_H_
+#define POSIX_TRANSLATION_TIME_UTIL_H_
+
+#include <sys/time.h>
+
+#include "base/time/time.h"
+
+namespace base {
+class ConditionVariable;
+}  // namespace base
+
+namespace posix_translation {
+namespace internal {
+
+// Converts timeval structure to TimeDelta.
+base::TimeDelta TimeValToTimeDelta(const timeval& time);
+
+// Converts TimeDelta to timeval.
+timeval TimeDeltaToTimeVal(const base::TimeDelta& time);
+
+// Returns the time limit (in absolute time) since *now*, from the
+// timeout period. If timeout period is 0, it means blocking without timeout,
+// so returns null TimeTicks (i.e. is_null() returns true). The convention
+// should be consistent with using WaitUntil() declared below.
+// Note that if timeout_period is negative, it returns non-null TimeTicks
+// instance, which will have WaitUntil() timed out immediately.
+base::TimeTicks TimeOutToTimeLimit(const base::TimeDelta& timeout_period);
+
+// Blocks the current thread until the given condition_variable is signaled
+// with time limit. Returns whether it is timed out.
+// Note that if time_limit is not set (i.e. time_limit.is_null() is true),
+// it means there is not time limit. Then, this function waits forever until
+// condition_variable is actually signaled.
+// Also, note that there is a small chance that this function returns true
+// even if condition_variable is signaled. So, if the predicate is still
+// false *and* the return value is true, it is actually a time out.
+// condition_variable must not be NULL.
+bool WaitUntil(base::ConditionVariable* condition_variable,
+               const base::TimeTicks& time_limit);
+
+}  // namespace internal
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_TIME_UTIL_H_
diff --git a/src/posix_translation/time_util_test.cc b/src/posix_translation/time_util_test.cc
new file mode 100644
index 0000000..851f7e8
--- /dev/null
+++ b/src/posix_translation/time_util_test.cc
@@ -0,0 +1,173 @@
+// Copyright 2014 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 "posix_translation/time_util.h"
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "gtest/gtest.h"
+
+namespace posix_translation {
+namespace internal {
+namespace {
+
+struct TestData {
+  timeval tv;
+  int64_t microseconds;
+};
+
+const TestData kTestData[] = {
+  { {0, 0}, 0 },
+  { {0, 500}, 500 },
+  { {0, 999999}, 999999 },
+  { {1, 0}, 1000000 },
+  { {10, 0}, 10000000 },
+  { {1, 500000}, 1500000 },
+  { {-1, 0}, -1000000 },
+  { {-1, 500000}, -500000 },
+  { {-2, 500000}, -1500000 },
+  { {2148, 0}, 2148000000LL },  // Signed 32bit (= 31bit) boundary.
+  { {4295, 0}, 4295000000LL },  // Unsigned 32bit boundary.
+};
+
+// Simple helper thread to signal condition variable while it is being waited on
+// the testing thread.
+class SignalCondThread : public base::SimpleThread {
+ public:
+  SignalCondThread(base::Lock* mutex,
+                   base::ConditionVariable* cond,
+                   base::WaitableEvent* completion_event)
+      : base::SimpleThread("SignalCondThread"),
+        mutex_(mutex), cond_(cond), completion_event_(completion_event) {
+  }
+  virtual ~SignalCondThread() {
+  }
+
+  virtual void Run() OVERRIDE {
+    base::AutoLock lock(*mutex_);
+    cond_->Signal();
+
+    // Let the original thread know that this thread is being terminated.
+    if (completion_event_)
+      completion_event_->Signal();
+  }
+
+ private:
+  base::Lock* mutex_;
+  base::ConditionVariable* cond_;
+  base::WaitableEvent* completion_event_;
+  DISALLOW_COPY_AND_ASSIGN(SignalCondThread);
+};
+
+}  // namespace
+
+TEST(TimeUtilTest, TimeValToTimeDelta) {
+  for (size_t i = 0; i < arraysize(kTestData); ++i) {
+    SCOPED_TRACE(testing::Message() << "Case: " << i);
+    EXPECT_EQ(
+        base::TimeDelta::FromMicroseconds(kTestData[i].microseconds),
+        TimeValToTimeDelta(kTestData[i].tv));
+  }
+}
+
+TEST(TimeUtilTest, TimeDeltaToTimeVal) {
+  for (size_t i = 0; i < arraysize(kTestData); ++i) {
+    SCOPED_TRACE(testing::Message() << "Case: " << i);
+    timeval tv = TimeDeltaToTimeVal(
+        base::TimeDelta::FromMicroseconds(kTestData[i].microseconds));
+    EXPECT_EQ(kTestData[i].tv.tv_sec, tv.tv_sec);
+    EXPECT_EQ(kTestData[i].tv.tv_usec, tv.tv_usec);
+  }
+}
+
+TEST(TimeUtilTest, TimeOutToTimeLimit) {
+  // If 0 TimeDelta is given, null-TimeTicks should be returned.
+  EXPECT_TRUE(TimeOutToTimeLimit(base::TimeDelta()).is_null());
+
+  const base::TimeDelta kTimeOut = base::TimeDelta::FromMilliseconds(500);
+  // Unfortunately, the API depends on the "current" time, so check exact
+  // value would cause a flakiness. Instead, we sandwich the value.
+  const base::TimeTicks before = base::TimeTicks::Now();
+  const base::TimeTicks time_limit = TimeOutToTimeLimit(kTimeOut);
+  const base::TimeTicks after = base::TimeTicks::Now();
+
+  EXPECT_LE(before + kTimeOut, time_limit);
+  EXPECT_LE(time_limit, after + kTimeOut);
+}
+
+TEST(TimeUtilTest, WaitUntilImmediateReturn) {
+  base::Lock mutex;
+  base::ConditionVariable cond(&mutex);
+  base::AutoLock lock(mutex);
+
+  EXPECT_TRUE(WaitUntil(&cond, base::TimeTicks::Now()));
+  EXPECT_TRUE(
+      WaitUntil(&cond,
+                base::TimeTicks::Now() - base::TimeDelta::FromSeconds(1)));
+}
+
+TEST(TimeUtilTest, WaitUntilTimeOut) {
+  base::Lock mutex;
+  base::ConditionVariable cond(&mutex);
+  base::AutoLock lock(mutex);
+
+  // Wait for 123ms, and make sure that the time is actually passed.
+  // Note that we expect 123ms is long enough that
+  // base::ConditionVariable::TimedWait() is actually called internally in
+  // most cases. However, regardless of whether it is actually called or not,
+  // WaitUntil must be timed out after 123ms. It means, this test must be
+  // stable.
+  const base::TimeTicks kTimeLimit =
+      base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(123);
+  EXPECT_TRUE(WaitUntil(&cond, kTimeLimit));
+  const base::TimeTicks after = base::TimeTicks::Now();
+  EXPECT_LE(kTimeLimit, after);
+}
+
+TEST(TimeUtilTest, WaitUntilSignal) {
+  base::Lock mutex;
+  base::ConditionVariable cond(&mutex);
+  base::AutoLock lock(mutex);
+
+  SignalCondThread thread(&mutex, &cond, NULL);
+  thread.Start();
+  EXPECT_FALSE(WaitUntil(&cond, base::TimeTicks()));  // Wait without timeout.
+  thread.Join();
+}
+
+TEST(TimeUtilTest, WaitUntilSignalWithTimeout) {
+  base::Lock mutex;
+  base::ConditionVariable cond(&mutex);
+  base::AutoLock lock(mutex);
+
+  // Here we set the time out. It means, WaitUntil *could* be timed out.
+  // However, we set the value to very long time, so we do not expect
+  // it is actually timed out.
+  const base::TimeDelta kTimeOut = base::TimeDelta::FromSeconds(60);
+
+  // We use completion event just in case the test gets flaky.
+  // See below comment for details.
+  base::WaitableEvent completion_event(true, false);
+  SignalCondThread thread(&mutex, &cond, &completion_event);
+  thread.Start();
+  EXPECT_FALSE(WaitUntil(&cond, base::TimeTicks::Now() + kTimeOut));
+
+  // If something goes bad, base::ConditionVariable::TimedWait() may not be
+  // invoked. It means, the mutex may not be taken on the created thread,
+  // which would cause a dead lock on SignalCondThread::Join() below.
+  // To avoid such a situation, here once we unlock the mutex and wait the
+  // completion event, just in case.
+  {
+    base::AutoUnlock unlock(mutex);
+    completion_event.Wait();
+  }
+  thread.Join();
+}
+
+}  // namespace internal
+}  // namespace posix_translation
diff --git a/src/posix_translation/udp_socket.cc b/src/posix_translation/udp_socket.cc
new file mode 100644
index 0000000..f65002c
--- /dev/null
+++ b/src/posix_translation/udp_socket.cc
@@ -0,0 +1,623 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/udp_socket.h"
+
+#include <fcntl.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <string.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "posix_translation/socket_util.h"
+#include "posix_translation/time_util.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/net_address.h"
+#include "ppapi/cpp/udp_socket.h"
+#include "ppapi/cpp/var.h"
+
+namespace posix_translation {
+namespace {
+
+// The minimum address length for AF_UNSPEC.
+const socklen_t kUnspecMinAddrLen =
+    offsetof(sockaddr, sa_family) + sizeof(sa_family_t);
+
+// Sets socket option to the given socket. Returns whether it succeeds.
+bool SetSocketOption(pp::UDPSocket* socket,
+                     PP_UDPSocket_Option name,
+                     const pp::Var& value) {
+  int32_t pp_error ALLOW_UNUSED =
+      socket->SetOption(name, value, pp::BlockUntilComplete());
+  ARC_STRACE_REPORT_PP_ERROR(pp_error);
+
+  // We should handle errors as follows:
+  // if (pp_error != PP_OK) {
+  //   errno = ENOPROTOOPT;  // TODO(crbug.com/358932): Pick correct errno.
+  //   return false;
+  // }
+  // However, failing of some options causes JDWP (Java Debug Wired Protocol)
+  // to fail during setup of listening socket. So, here now we just ignore
+  // errors.
+  // TODO(crbug.com/233914): Fix this problem.
+  // TODO(crbug.com/362763): One of the typical case that PPAPI call fails is
+  // invoking SO_REUSEADDR after bind(). PPAPI should support this case, too.
+
+  return true;
+}
+
+// Common implementation of setsockopt with boolean value for UDP socket, such
+// as SO_REUSEADDR or SO_BROADCAST.
+// Note that the type of storage is int rather than bool, because it stores
+// the given value as is.
+int SetSocketBooleanOptionInternal(
+    const void* optval, socklen_t optlen, int* storage,
+    pp::UDPSocket* socket, PP_UDPSocket_Option name) {
+  int error =
+      internal::VerifySetSocketOption(optval, optlen, sizeof(*storage));
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  int new_value = *static_cast<const int*>(optval);
+  // Compare as boolean values and call PPAPI only when the new value is
+  // different from the old one as boolean.
+  // For example, assuming setsockopt(SO_REUSEADDR, 1) is already called,
+  // then setsockopt(SO_REUSEADDR, 2) would not need to call PPAPI, because
+  // "REUSEADDR" is already true in PPAPI layer. In this case, *storage == 1
+  // and value == 2, and both are evaluated to true.
+  if ((new_value != 0) != (*storage != 0)) {
+    if (!SetSocketOption(socket, name, pp::Var(new_value ? true : false)))
+      return -1;
+  }
+  // PPAPI call successfully done. Update the value.
+  *storage = new_value;
+  return 0;
+}
+
+}  // namespace
+
+// A message unit which is sent to or received from the peer.
+struct UDPSocket::Message {
+  // The address where this message is being sent to or where the message
+  // comes from.
+  sockaddr_storage addr;
+
+  // Sent or received data.
+  std::vector<char> data;
+};
+
+// Thin wrapper of pp::UDPSocket. This is introduced to manage the lifetime of
+// pp::UDPSocket instance correctly, and resolve race condition.
+// The concept of this class is as same as TCPSocket::SocketWrapper. Please
+// see also its comment for more details.
+class UDPSocket::SocketWrapper
+    : public base::RefCountedThreadSafe<SocketWrapper> {
+ public:
+  explicit SocketWrapper(const pp::UDPSocket& socket)
+      : socket_(socket),
+        closed_(false) {
+  }
+
+  bool is_closed() const {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    return closed_;
+  }
+
+  void Close() {
+    VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
+    if (is_closed())
+      return;
+    closed_ = true;
+    socket_.Close();
+  }
+
+  pp::UDPSocket* socket() {
+    return &socket_;
+  }
+
+ private:
+  // Do not allow to destruct this class manually from the client code
+  // to avoid to delete the object accidentally while there are still
+  // references to it.
+  friend class base::RefCountedThreadSafe<SocketWrapper>;
+  ~SocketWrapper() {
+  }
+
+  pp::UDPSocket socket_;
+  bool closed_;
+
+  DISALLOW_COPY_AND_ASSIGN(SocketWrapper);
+};
+
+UDPSocket::UDPSocket(int fd, int socket_family, int oflag)
+    : SocketStream(socket_family, oflag), fd_(fd), factory_(this),
+      socket_(new SocketWrapper(pp::UDPSocket(
+          VirtualFileSystem::GetVirtualFileSystem()->instance()))),
+      state_(UDP_SOCKET_NEW), read_buf_(kBufSize),
+      read_sent_(false), write_sent_(false) {
+  ALOG_ASSERT(socket_family == AF_INET || socket_family == AF_INET6);
+  memset(&connected_addr_, 0, sizeof(connected_addr_));
+  connected_addr_.ss_family = AF_UNSPEC;
+}
+
+UDPSocket::~UDPSocket() {
+  ALOG_ASSERT(socket_->is_closed());
+}
+
+int UDPSocket::bind(const sockaddr* saddr, socklen_t addrlen) {
+  int error =
+      internal::VerifyInputSocketAddress(saddr, addrlen, socket_family_);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  if (state_ != UDP_SOCKET_NEW) {
+    errno = EISCONN;
+    return -1;
+  }
+
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  pp::NetAddress addr = internal::SockAddrToNetAddress(sys->instance(), saddr);
+
+  ALOGI("UDPSocket::Bind: %d %s\n",
+        fd_, addr.DescribeAsString(true).AsString().c_str());
+  scoped_refptr<SocketWrapper> wrapper(socket_);
+  int32_t result;
+  state_ = UDP_SOCKET_BINDING;
+  {
+    base::AutoUnlock unlock(sys->mutex());
+    result = wrapper->socket()->Bind(addr, pp::BlockUntilComplete());
+  }
+  ARC_STRACE_REPORT_PP_ERROR(result);
+  // Check close state before accessing any member variables since this
+  // instance might be destroyed while this thread was waiting.
+  if (wrapper->is_closed()) {
+    errno = EBADF;
+    return -1;
+  }
+
+  if (result != PP_OK) {
+    state_ = UDP_SOCKET_NEW;
+    if (result == PP_ERROR_ADDRESS_IN_USE) {
+      errno = EADDRINUSE;
+    } else {
+      // We expect PP_ERROR_NOACCESS, but it may be different (unknown) value.
+      // In either case, we return EACCES error.
+      errno = EACCES;
+    }
+    return -1;
+  }
+
+  // Exception state is (wrongly) changed, so notify listeners about it.
+  sys->Broadcast();
+  NotifyListeners();
+
+  state_ = UDP_SOCKET_BOUND;
+  PostReadTaskLocked();
+  return 0;
+}
+
+int UDPSocket::connect(const sockaddr* addr, socklen_t addrlen) {
+  int error =
+      internal::VerifyInputSocketAddress(addr, addrlen, socket_family_);
+  if (error) {
+    // There is an exception for connect() of UDP socket.
+    // If the addr is AF_UNSPEC, it means we should clear the connect state.
+    if (!addr || addrlen < kUnspecMinAddrLen || addr->sa_family != AF_UNSPEC) {
+      errno = error;
+      return -1;
+    }
+
+    // Reset the connected state.
+    memset(&connected_addr_, 0, sizeof(connected_addr_));
+    connected_addr_.ss_family = AF_UNSPEC;
+    return 0;
+  }
+
+  memset(&connected_addr_, 0, sizeof(connected_addr_));
+  // It is ensured that addr can be copied into sockaddr_storage, by
+  // VerifyInputSocketAddress above.
+  memcpy(&connected_addr_, addr, addrlen);
+  return 0;
+}
+
+int UDPSocket::setsockopt(
+    int level, int optname, const void* optval, socklen_t optlen) {
+  // For SO_REUSEADDR and SO_BROADCAST, it is necessary to communicate with
+  // PPAPI.
+  if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
+    return SetSocketBooleanOptionInternal(
+        optval, optlen, &reuse_addr_,
+        socket_->socket(), PP_UDPSOCKET_OPTION_ADDRESS_REUSE);
+  }
+
+  if (level == SOL_SOCKET && optname == SO_BROADCAST) {
+    return SetSocketBooleanOptionInternal(
+        optval, optlen, &broadcast_,
+        socket_->socket(), PP_UDPSOCKET_OPTION_BROADCAST);
+  }
+
+  return SocketStream::setsockopt(level, optname, optval, optlen);
+}
+
+int UDPSocket::getpeername(sockaddr* name, socklen_t* namelen) {
+  int error = internal::VerifyOutputSocketAddress(name, namelen);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+  if (connected_addr_.ss_family == AF_UNSPEC) {
+    errno = ENOTCONN;
+    return -1;
+  }
+  internal::CopySocketAddress(connected_addr_, name, namelen);
+  return 0;
+}
+
+int UDPSocket::getsockname(sockaddr* name, socklen_t* namelen) {
+  int error = internal::VerifyOutputSocketAddress(name, namelen);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  sockaddr_storage storage;
+  if (!internal::NetAddressToSockAddrStorage(
+          socket_->socket()->GetBoundAddress(), AF_UNSPEC, false, &storage)) {
+    memset(&storage, 0, sizeof(storage));
+    storage.ss_family = socket_family_;
+  }
+
+  internal::CopySocketAddress(storage, name, namelen);
+  return 0;
+}
+
+ssize_t UDPSocket::send(const void* buf, size_t len, int flags) {
+  return sendto(buf, len, flags, NULL, 0);
+}
+
+ssize_t UDPSocket::sendto(const void* buf, size_t len, int flags,
+                          const sockaddr* dest_addr, socklen_t addrlen) {
+  if (dest_addr == NULL) {
+    // Callers are allowed to pass a NULL dest_addr if the socket is connected,
+    // using the previously-connected address as the destination. However,
+    // trying this when not connected is an error.
+    if (connected_addr_.ss_family == AF_UNSPEC) {
+      errno = EDESTADDRREQ;
+      return -1;
+    }
+
+    dest_addr = reinterpret_cast<const sockaddr*>(&connected_addr_);
+    addrlen = sizeof(connected_addr_);
+  }
+
+  int error =
+      internal::VerifyInputSocketAddress(dest_addr, addrlen, socket_family_);
+  if (error) {
+    errno = error;
+    return -1;
+  }
+
+  if (state_ == UDP_SOCKET_NEW) {
+    // UDP sockets allow to send data without bind but Pepper requires bind
+    // before send/receive so bind it to any address now.
+    sockaddr_storage saddr = {};
+    saddr.ss_family = socket_family_;
+    if (this->bind(reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr))) {
+      // On error, errno is set in bind.
+      return -1;
+    }
+  }
+
+  // IPv4 packet has 16-bit packet length field. So, the max UDP packet size
+  // which can be represented is:
+  //   (64K - 1) - 8 (UDP packet header) - 20 (IPv4 packet header).
+  const size_t kMaxIPv4UDPPacketSize =
+      std::numeric_limits<uint16_t>::max() - sizeof(udphdr) - sizeof(iphdr);
+
+  // IPv6 packet has 16-bit payload length field, which do not include the size
+  // of IP packet header unlike IPv4 packet. So, the max size which can be
+  // represented is to UDP packet's size field, which is 16-bit:
+  //   (64K - 1) - 8 (UDP packet header).
+  const size_t kMaxIPv6UDPPacketSize =
+      std::numeric_limits<uint16_t>::max() - sizeof(udphdr);
+
+  const size_t kMaxUDPPacketSize =
+      socket_family_ == AF_INET ? kMaxIPv4UDPPacketSize : kMaxIPv6UDPPacketSize;
+  if (len > kMaxUDPPacketSize) {
+    errno = EMSGSIZE;
+    return -1;
+  }
+
+  out_queue_.push_back(Message());
+  Message* message = &out_queue_.back();
+  memcpy(&message->addr, dest_addr, addrlen);
+  message->data.assign(static_cast<const char*>(buf),
+                       static_cast<const char*>(buf) + len);
+  PostWriteTaskLocked();
+
+  if (is_block()) {
+    scoped_refptr<SocketWrapper> wrapper(socket_);
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    while (!out_queue_.empty()) {
+      sys->Wait();
+      // Check close state before accessing any member variables since this
+      // instance might be destroyed while this thread was waiting.
+      if (wrapper->is_closed()) {
+        errno = EBADF;
+        return -1;
+      }
+    }
+  }
+
+  // We should handle errors here properly, at least we should handle
+  // EMSGSIZE. Otherwise callers have no way to know if the packet is
+  // too large or not.
+  // TODO(crbug.com/364744): Handle errors.
+  return static_cast<ssize_t>(len);
+}
+
+ssize_t UDPSocket::recv(void* buffer, size_t len, int flags) {
+  if (connected_addr_.ss_family == AF_UNSPEC) {
+    errno = ENOTCONN;
+    return -1;
+  }
+  return recvfrom(buffer, len, flags, NULL, NULL);
+}
+
+ssize_t UDPSocket::recvfrom(void* buffer, size_t len, int flags,
+                            sockaddr* addr, socklen_t* addrlen) {
+  if (is_block()) {
+    scoped_refptr<SocketWrapper> wrapper(socket_);
+    VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+    const base::TimeTicks time_limit =
+        internal::TimeOutToTimeLimit(recv_timeout_);
+    bool is_timedout = false;
+    while (!is_timedout && in_queue_.empty()) {
+      is_timedout = sys->WaitUntil(time_limit);
+      // Check close state before accessing any member variables since this
+      // instance might be destroyed while this thread was waiting.
+      if (wrapper->is_closed()) {
+        errno = EBADF;
+        return -1;
+      }
+    }
+  }
+
+  if (in_queue_.empty()) {
+    errno = EAGAIN;
+    return -1;
+  }
+
+  // Message may be discarded below, so limit the scope in order to avoid
+  // illegal access.
+  {
+    const Message& message = in_queue_.front();
+    if (addrlen != NULL && addr != NULL)
+      internal::CopySocketAddress(message.addr, addr, addrlen);
+    len = std::min(len, message.data.size());
+    memcpy(buffer, &message.data[0], len);
+  }
+  if ((flags & MSG_PEEK) == 0)
+    in_queue_.pop_front();
+
+  PostReadTaskLocked();
+  return len;
+}
+
+ssize_t UDPSocket::read(void* buf, size_t count) {
+  return recv(buf, count, 0);
+}
+
+ssize_t UDPSocket::write(const void* buf, size_t count) {
+  return send(buf, count, 0);
+}
+
+bool UDPSocket::IsSelectReadReady() const {
+  return socket_->is_closed() || !in_queue_.empty();
+}
+
+bool UDPSocket::IsSelectWriteReady() const {
+  return true;
+}
+
+bool UDPSocket::IsSelectExceptionReady() const {
+  // TODO(crbug.com:359400): Fix the select() and poll() implementaiton.
+  // See the bug for details.
+  return socket_->is_closed();
+}
+
+int16_t UDPSocket::GetPollEvents() const {
+  // Currently we use IsSelect*Ready() family temporarily (and wrongly).
+  // TODO(crbug.com/359400): Fix the implementation.
+  return ((IsSelectReadReady() ? POLLIN : 0) |
+          (IsSelectWriteReady() ? POLLOUT : 0) |
+          (IsSelectExceptionReady() ? POLLERR : 0));
+}
+
+void UDPSocket::OnLastFileRef() {
+  ALOG_ASSERT(!socket_->is_closed());
+  CloseLocked();
+}
+
+void UDPSocket::CloseLocked() {
+  int32_t result = PP_OK_COMPLETIONPENDING;
+  pp::Module::Get()->core()->CallOnMainThread(
+      0, factory_.NewCallback(&UDPSocket::Close, &result));
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  while (result == PP_OK_COMPLETIONPENDING)
+    sys->Wait();
+  ARC_STRACE_REPORT_PP_ERROR(result);
+}
+
+void UDPSocket::Close(int32_t result, int32_t* pres) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+
+  factory_.CancelAll();
+  socket_->Close();
+  *pres = PP_OK;
+  // Don't access any member variable after sys->Broadcast() is called.
+  // It may make destructor have completed.
+  NotifyListeners();
+  sys->Broadcast();
+}
+
+void UDPSocket::Read(int32_t result) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  ReadLocked();
+}
+
+void UDPSocket::ReadLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  int32_t result = socket_->socket()->RecvFrom(
+      &read_buf_[0], read_buf_.size(),
+      factory_.NewCallbackWithOutput(&UDPSocket::OnRead));
+  ALOG_ASSERT(result == PP_OK_COMPLETIONPENDING);
+}
+
+void UDPSocket::OnRead(int32_t result, const pp::NetAddress& addr) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  read_sent_ = false;
+
+  if (result < 0) {
+    return;
+  }
+
+  ALOGI("UDPSocket::OnRead: %d %s",
+        fd_, addr.DescribeAsString(true).AsString().c_str());
+
+  sockaddr_storage src_addr;
+  internal::NetAddressToSockAddrStorage(addr, AF_UNSPEC, false, &src_addr);
+  if (connected_addr_.ss_family != AF_UNSPEC &&
+      !internal::SocketAddressEqual(connected_addr_, src_addr)) {
+    // Packet from address other than our connected address. So we
+    // merely drop the packet on the floor.
+    PostReadTaskLocked();
+    return;
+  }
+
+  in_queue_.push_back(Message());
+  Message* message = &in_queue_.back();
+  memcpy(&message->addr, &src_addr, sizeof(src_addr));
+  message->data.assign(read_buf_.begin(), read_buf_.begin() + result);
+
+  PostReadTaskLocked();
+
+  sys->Broadcast();
+  NotifyListeners();
+}
+
+void UDPSocket::Write(int32_t result) {
+  ALOG_ASSERT(result == PP_OK);
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+  WriteLocked();
+}
+
+void UDPSocket::WriteLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  ALOG_ASSERT(!out_queue_.empty());
+
+  pp::NetAddress addr = internal::SockAddrToNetAddress(
+      sys->instance(),
+      reinterpret_cast<const sockaddr*>(&out_queue_.front().addr));
+  ALOGI("UDPSocket::Write: %d %s",
+        fd_, addr.DescribeAsString(true).AsString().c_str());
+
+  const Message& message = out_queue_.front();
+  int32_t result = socket_->socket()->SendTo(
+      &message.data[0], message.data.size(), addr,
+      factory_.NewCallback(&UDPSocket::OnWrite));
+  ALOG_ASSERT(result == PP_OK_COMPLETIONPENDING);
+}
+
+void UDPSocket::OnWrite(int32_t result) {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  base::AutoLock lock(sys->mutex());
+
+  write_sent_ = false;
+  if (result < 0) {
+    // Write error.
+    ALOGI("TCPSocket::OnWrite: close socket %d", fd_);
+  } else {
+    // We do not expect partial write. Sent data may be truncated in PPAPI
+    // layer if it is too large, but the limit size is currently much bigger
+    // than the common MTU (Maximum Transmission Unit). In lower layer,
+    // UDP socket communication will fail if the size is bigger than MTU,
+    // rather than partial write.
+    // Thus, partial write will not happen here.
+    ALOG_ASSERT(static_cast<size_t>(result) == out_queue_.front().data.size());
+  }
+
+  out_queue_.pop_front();
+  sys->Broadcast();
+  NotifyListeners();
+
+  // Always try to send more if there are some pending items.
+  PostWriteTaskLocked();
+}
+
+void UDPSocket::PostReadTaskLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (read_sent_ || in_queue_.size() >= kQueueSize) {
+    // If there is in-flight Read() task, or the reading queue is already
+    // full, do not send the Read() task.
+    return;
+  }
+
+  read_sent_ = true;
+  if (!pp::Module::Get()->core()->IsMainThread()) {
+    pp::Module::Get()->core()->CallOnMainThread(
+        0, factory_.NewCallback(&UDPSocket::Read));
+  } else {
+    // If on main Pepper thread and delay is not required call it directly.
+    ReadLocked();
+  }
+}
+
+void UDPSocket::PostWriteTaskLocked() {
+  VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
+  sys->mutex().AssertAcquired();
+
+  if (write_sent_ || out_queue_.empty()) {
+    // If there is in-flight Write() task, or there is no write message,
+    // do not send the Write() task.
+    return;
+  }
+
+  write_sent_ = true;
+  if (!pp::Module::Get()->core()->IsMainThread()) {
+    pp::Module::Get()->core()->CallOnMainThread(
+        0, factory_.NewCallback(&UDPSocket::Write));
+  } else {
+    // If on main Pepper thread and delay is not required call it directly.
+    WriteLocked();
+  }
+}
+
+const char* UDPSocket::GetStreamType() const {
+  return "udp";
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/udp_socket.h b/src/posix_translation/udp_socket.h
new file mode 100644
index 0000000..6ef55ab
--- /dev/null
+++ b/src/posix_translation/udp_socket.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_UDP_SOCKET_H_
+#define POSIX_TRANSLATION_UDP_SOCKET_H_
+
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <deque>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "posix_translation/socket_stream.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace pp {
+class NetAddress;
+}  // namespace pp
+
+namespace posix_translation {
+
+class UDPSocket : public SocketStream {
+ public:
+  UDPSocket(int fd, int socket_family, int oflag);
+
+  virtual int bind(const sockaddr* saddr, socklen_t addrlen) OVERRIDE;
+  virtual int connect(const sockaddr* addr, socklen_t addrlen) OVERRIDE;
+  virtual int setsockopt(
+      int level, int optname, const void* optval, socklen_t optlen) OVERRIDE;
+  virtual int getpeername(sockaddr* name, socklen_t* namelen) OVERRIDE;
+  virtual int getsockname(sockaddr* name, socklen_t* namelen) OVERRIDE;
+  virtual ssize_t send(const void* buf, size_t len, int flags) OVERRIDE;
+  virtual ssize_t sendto(const void* buf, size_t len, int flags,
+                         const sockaddr* dest_addr, socklen_t addrlen) OVERRIDE;
+  virtual ssize_t recv(void* buffer, size_t len, int flags) OVERRIDE;
+  virtual ssize_t recvfrom(void* buffer, size_t len, int flags,
+                           sockaddr* addr, socklen_t* addrlen) OVERRIDE;
+
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE;
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE;
+
+  virtual bool IsSelectReadReady() const OVERRIDE;
+  virtual bool IsSelectWriteReady() const OVERRIDE;
+  virtual bool IsSelectExceptionReady() const OVERRIDE;
+  virtual int16_t GetPollEvents() const OVERRIDE;
+
+  virtual const char* GetStreamType() const OVERRIDE;
+
+ protected:
+  virtual ~UDPSocket();
+  virtual void OnLastFileRef() OVERRIDE;
+
+ private:
+  struct Message;
+  typedef std::deque<Message> MessageQueue;
+  class SocketWrapper;
+
+  enum State {
+    UDP_SOCKET_NEW,
+    UDP_SOCKET_BINDING,
+    UDP_SOCKET_BOUND,
+  };
+
+  bool is_block() { return !(oflag() & O_NONBLOCK); }
+
+  void CloseLocked();
+  void Close(int32_t result, int32_t* pres);
+
+  void Read(int32_t result);
+  void ReadLocked();
+  void OnRead(int32_t result, const pp::NetAddress& addr);
+
+  void Write(int32_t result);
+  void WriteLocked();
+  void OnWrite(int32_t result);
+
+  void PostReadTaskLocked();
+  void PostWriteTaskLocked();
+
+  // Number of messages in incoming queue that we can read ahead.
+  static const size_t kQueueSize = 16;
+
+  // Read buffer size for incoming message.
+  static const size_t kBufSize = 64 * 1024;
+
+  int fd_;
+  pp::CompletionCallbackFactory<UDPSocket> factory_;
+  scoped_refptr<SocketWrapper> socket_;
+  State state_;
+  MessageQueue in_queue_;
+  MessageQueue out_queue_;
+  std::vector<char> read_buf_;
+  bool read_sent_;
+  bool write_sent_;
+  struct sockaddr_storage connected_addr_;
+
+  DISALLOW_COPY_AND_ASSIGN(UDPSocket);
+};
+
+}  // namespace posix_translation
+
+#endif  // POSIX_TRANSLATION_UDP_SOCKET_H_
diff --git a/src/posix_translation/virtual_file_system.cc b/src/posix_translation/virtual_file_system.cc
new file mode 100644
index 0000000..4b7a3ea
--- /dev/null
+++ b/src/posix_translation/virtual_file_system.cc
@@ -0,0 +1,2004 @@
+// Copyright (c) 2012 The Chromium OS 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 "posix_translation/virtual_file_system.h"
+
+#include <arpa/inet.h>
+#include <limits.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/statvfs.h>
+#include <sys/time.h>
+#include <sys/vfs.h>
+#include <unistd.h>  // for access
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#if defined(_STLP_USE_STATIC_LIB)
+#include <iostream>  // NOLINT
+#endif
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"  // for base::TimeDelta
+#include "common/arc_strace.h"
+#include "common/alog.h"
+#include "common/file_util.h"
+#include "common/process_emulator.h"
+#include "common/trace_event.h"
+#include "posix_translation/address_util.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/directory_file_stream.h"
+#include "posix_translation/epoll_stream.h"
+#include "posix_translation/fd_to_file_stream_map.h"
+#include "posix_translation/local_socket.h"
+#include "posix_translation/memory_region.h"
+#include "posix_translation/mount_point_manager.h"
+#include "posix_translation/passthrough.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/process_environment.h"
+#include "posix_translation/tcp_socket.h"
+#include "posix_translation/time_util.h"
+#include "posix_translation/udp_socket.h"
+
+namespace posix_translation {
+
+#if defined(DEBUG_POSIX_TRANSLATION)
+namespace ipc_stats {
+// The implementation is in pepper_file.cc. Do not include pepper_file.h here
+// to prevent VirtualFileSystem from depending on a concrete filesytem.
+std::string GetIPCStatsAsStringLocked();
+}  // namespace ipc_stats
+#endif
+
+namespace {
+
+const char kVirtualFileSystemHandlerStr[] ALLOW_UNUSED = "VirtualFileSystem";
+
+void FillPermissionInfoToStat(const PermissionInfo& permission,
+                              struct stat* out) {
+  // Files created by apps should not allow other users to read them.
+  // This is checked by a CTS suite (FileSystemPermissionTest).
+  static const mode_t kDefaultUserFilePermission = 0600;
+  static const mode_t kDefaultUserDirPermission = 0700;
+  static const mode_t kDefaultSystemFilePermission = 0644;
+  static const mode_t kDefaultSystemDirPermission = 0755;
+
+  ALOG_ASSERT(permission.IsValid());
+  out->st_uid = permission.file_uid();
+  out->st_gid = arc::kRootGid;
+  mode_t file_type = out->st_mode & S_IFMT;
+  ALOG_ASSERT(file_type);
+  mode_t perm = out->st_mode & 0777;
+  // If the permission is not set by FileSystemHandler, fill it based on the
+  // file type and the owner.
+  if (file_type && !perm) {
+    // This function must not be used for special files.
+    ALOG_ASSERT((file_type == S_IFDIR) || (file_type == S_IFREG));
+    const bool is_dir = (file_type == S_IFDIR);
+    if (arc::IsAppUid(out->st_uid))
+      perm = is_dir ? kDefaultUserDirPermission : kDefaultUserFilePermission;
+    else
+      perm = (is_dir ? kDefaultSystemDirPermission :
+              kDefaultSystemFilePermission);
+  } else {
+    ARC_STRACE_REPORT("Permission already set %o", static_cast<int>(perm));
+  }
+  out->st_mode = file_type | perm;
+}
+
+// The current VirtualFileSystemInterface exposed to plugins via
+// GetVirtualFileSystemInterface().
+VirtualFileSystemInterface* g_current_file_system = NULL;
+
+}  // namespace
+
+VirtualFileSystemInterface* GetVirtualFileSystemInterface() {
+  // A mutex lock is not necessary here since |SetVirtualFileSystemInterface|
+  // must be called by the main thread before the first pthread_create() call
+  // is made. It is ensured that a non-main thread can see correct
+  // |g_current_file_system| because pthread_create() call to create the thread
+  // itself is a memory barrier.
+  ALOG_ASSERT(g_current_file_system);
+  return g_current_file_system;
+}
+
+void SetVirtualFileSystemInterface(VirtualFileSystemInterface* vfs) {
+  ALOG_ASSERT(!arc::ProcessEmulator::IsMultiThreaded());
+  delete g_current_file_system;
+  g_current_file_system = vfs;
+}
+
+// The VirtualFileSystem instance to be returned by GetVirtualFileSystem().
+// Set in the VFS constructor and unset in the VFS destructor.
+// Usually this should be the same as g_current_file_system, but this can be
+// NULL while g_current_file_system is non-NULL when a mock
+// VirtualFileSystemInterface implementation is set as current in unit tests
+// (e.g. FileSystemManagerTest).
+VirtualFileSystem* VirtualFileSystem::file_system_ = NULL;
+
+VirtualFileSystem::VirtualFileSystem(
+    pp::Instance* instance,
+    ProcessEnvironment* process_environment,
+    int min_fd,
+    int max_fd)
+    : browser_ready_(false),
+      instance_(instance),
+      process_environment_(process_environment),
+      cond_(&mutex_),
+      fd_to_stream_(new FdToFileStreamMap(min_fd, max_fd)),
+      memory_region_(new MemoryRegion),
+      // Some file systems do not use zero and very small numbers as inode
+      // number. For example, ext4 reserves 0 to 10 (see linux/fs/ext4/ext4.h)
+      // for special purposes. Do not use such numbers to emulate the behavior.
+      next_inode_(128),
+      mount_points_(new MountPointManager),
+      host_resolver_(instance),
+      abort_on_unexpected_memory_maps_(true) {
+#if defined(_STLP_USE_STATIC_LIB)
+  // See mods/android/external/stlport/src/locale_impl.cpp. Initialize cin,
+  // cout, and cerr here just in case. Using these streams inside the |mutex_|
+  // lock may cause deadlock, but it is probably better than a random crash.
+  // Leaking the object is intentional. The destructor of the object calls
+  // fflush which is not allowed in POSIX translation.
+  // TODO(crbug.com/417401): Migrate to libcxx and remove this.
+  new std::ios_base::Init;
+#endif
+  ALOG_ASSERT(!file_system_);
+  file_system_ = this;
+}
+
+VirtualFileSystem::~VirtualFileSystem() {
+  file_system_ = NULL;
+}
+
+VirtualFileSystem* VirtualFileSystem::GetVirtualFileSystem() {
+  ALOG_ASSERT(file_system_);
+  // We require this condition so that there is always at most one "current"
+  // VirtualFileSystem instance at any time.
+  ALOG_ASSERT(GetVirtualFileSystemInterface() == file_system_);
+  return file_system_;
+}
+
+FileSystemHandler* VirtualFileSystem::GetFileSystemHandler(
+    const std::string& path) {
+  base::AutoLock lock(mutex_);
+  return GetFileSystemHandlerLocked(path, NULL /* permission */);
+}
+
+FileSystemHandler* VirtualFileSystem::GetFileSystemHandlerLocked(
+    const std::string& path, PermissionInfo* out_permission) {
+  mutex_.AssertAcquired();
+
+  uid_t file_uid = 0;
+  FileSystemHandler* handler = mount_points_->GetFileSystemHandler(path,
+                                                                   &file_uid);
+  if (!handler) {
+    ARC_STRACE_REPORT("No handler is found for '%s'", path.c_str());
+    return NULL;
+  }
+  // Call REPORT_HANDLER() so that the current function call is categrized as
+  // |handler->name()| rather than |kVirtualFileSystemHandlerStr|.
+  ARC_STRACE_REPORT_HANDLER(handler->name().c_str());
+
+  if (!handler->IsInitialized())
+    handler->Initialize();
+  ALOG_ASSERT(handler->IsInitialized());
+
+  if (out_permission) {
+    // Check if |path| is writable. First, compare the current UID with the
+    // owner's of the file. Then, check if |path| is writable to the world.
+    const uid_t uid = arc::ProcessEmulator::GetUid();
+    const bool is_writable = !arc::IsAppUid(uid) || (file_uid == uid) ||
+        handler->IsWorldWritable(path);
+    *out_permission = PermissionInfo(file_uid, is_writable);
+  }
+
+  // Disallow path handlers being used on main thread since at lease one of the
+  // handlers (PepperFileHandler) might call pp::BlockUntilComplete() which is
+  // not allowed on the thread.
+  LOG_ALWAYS_FATAL_IF(pp::Module::Get()->core()->IsMainThread());
+  return handler;
+}
+
+ino_t VirtualFileSystem::GetInodeLocked(const std::string& path) {
+  ALOG_ASSERT(!path.empty());
+  ALOG_ASSERT(IsNormalizedPathLocked(path), "%s", path.c_str());
+  return GetInodeUncheckedLocked(path);
+}
+
+ino_t VirtualFileSystem::GetInodeUncheckedLocked(const std::string& path) {
+  // DO NOT CALL THIS FUNCTION DIRECTLY. This is only for VFS::lstat(),
+  // VFS::GetInodeLocked(), and DirImpl::GetNext().
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(!path.empty());
+
+  InodeMap::const_iterator it = inodes_.find(path);
+  if (it != inodes_.end())
+    return it->second;
+
+  ARC_STRACE_REPORT("Assigning inode %lld for %s",
+                      static_cast<int64_t>(next_inode_), path.c_str());
+  inodes_[path] = next_inode_;
+  // Note: Do not try to reuse returned inode numbers. Doing this would
+  // break MemoryRegion::IsWriteMapped().
+  return next_inode_++;
+}
+
+void VirtualFileSystem::RemoveInodeLocked(const std::string& path) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(IsNormalizedPathLocked(path), "%s", path.c_str());
+  inodes_.erase(path);
+}
+
+void VirtualFileSystem::ReassignInodeLocked(const std::string& oldpath,
+                                            const std::string& newpath) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(IsNormalizedPathLocked(oldpath), "%s", oldpath.c_str());
+  ALOG_ASSERT(IsNormalizedPathLocked(newpath), "%s", newpath.c_str());
+
+  InodeMap::iterator it = inodes_.find(oldpath);
+  if (it == inodes_.end()) {
+    // stat() has not been called for |oldpath|. Removing the inode for
+    // |newpath| is for handling the following case:
+    //   open("/a.txt", O_CREAT);  // this may not assign an inode yet.
+    //   open("/b.txt", O_CREAT);  // ditto.
+    //   stat("/b.txt");  // a new inode is assigned to b.txt.
+    //   rename("/a.txt", "/b.txt");  // the inode for b.txt should be removed.
+    inodes_.erase(newpath);
+  } else {
+    inodes_[newpath] = it->second;
+    inodes_.erase(it);
+  }
+}
+
+bool VirtualFileSystem::IsWriteMapped(ino_t inode) {
+  mutex_.AssertAcquired();
+  return memory_region_->IsWriteMapped(inode);
+}
+
+bool VirtualFileSystem::IsCurrentlyMapped(ino_t inode) {
+  mutex_.AssertAcquired();
+  return memory_region_->IsCurrentlyMapped(inode);
+}
+
+std::string VirtualFileSystem::GetMemoryMapAsString() {
+  base::AutoLock lock(mutex_);
+  return GetMemoryMapAsStringLocked();
+}
+
+std::string VirtualFileSystem::GetMemoryMapAsStringLocked() {
+  mutex_.AssertAcquired();
+  return memory_region_->GetMemoryMapAsString();
+}
+
+std::string VirtualFileSystem::GetIPCStatsAsString() {
+#if defined(DEBUG_POSIX_TRANSLATION)
+  base::AutoLock lock(mutex_);
+  return ipc_stats::GetIPCStatsAsStringLocked();
+#else
+  return "unknown";
+#endif
+}
+
+int VirtualFileSystem::StatForTesting(
+    const std::string& pathname, struct stat* out) {
+  return stat(pathname, out);
+}
+
+bool VirtualFileSystem::IsMemoryRangeAvailableLocked(void* addr,
+                                                     size_t length) {
+  mutex_.AssertAcquired();
+  if (!memory_region_->AddFileStreamByAddr(
+          addr, length, kBadInode, PROT_NONE, 0, NULL)) {
+    return false;
+  }
+  const int result =
+      memory_region_->RemoveFileStreamsByAddr(addr, length, true);
+  ALOG_ASSERT(!result);
+  return true;
+}
+
+int VirtualFileSystem::AddFileStreamLocked(scoped_refptr<FileStream> stream) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(stream->permission().IsValid(), "pathname=%s stream=%s",
+              stream->pathname().c_str(), stream->GetStreamType());
+  int fd = GetFirstUnusedDescriptorLocked();
+  if (fd >= 0)
+    fd_to_stream_->AddFileStream(fd, stream);
+  return fd;
+}
+
+int VirtualFileSystem::open(const std::string& pathname, int oflag,
+                            mode_t cmode) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  // Linux kernel also accepts 'O_RDONLY|O_TRUNC' and truncates the file. Even
+  // though pp::FileIO seems to refuse 'O_RDONLY|O_TRUNC', show a warning here.
+  if (((oflag & O_ACCMODE) == O_RDONLY) && (oflag & O_TRUNC))
+    ALOGW("O_RDONLY|O_TRUNC is specified for %s", pathname.c_str());
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  ALOG_ASSERT(permission.IsValid(), "pathname=%s handler=%s",
+              pathname.c_str(), handler->name().c_str());
+  // Linux kernel accepts both 'O_RDONLY|O_CREAT' and 'O_RDONLY|O_TRUNC'.
+  // If the directory is not writable, the request should be denied.
+  if (((oflag & O_ACCMODE) != O_RDONLY || (oflag & (O_CREAT | O_TRUNC))) &&
+      !permission.is_writable()) {
+    if (oflag & O_CREAT) {
+      if (oflag & O_EXCL) {
+        // When O_CREAT|O_EXCL is specified, Linux kernel prefers EEXIST
+        // over EACCES. Emulate the behavior.
+        struct stat st;
+        if (!handler->stat(resolved, &st)) {
+          errno = EEXIST;
+          return -1;
+        }
+      }
+      return DenyAccessForCreateLocked(&resolved, handler);
+    } else {
+      return DenyAccessForModifyLocked(resolved, handler);
+    }
+  }
+  int fd = GetFirstUnusedDescriptorLocked();
+  if (fd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  scoped_refptr<FileStream> stream = handler->open(fd, resolved, oflag, cmode);
+  if (!stream) {
+    ALOG_ASSERT(errno > 0, "pathname=%s, handler=%s",
+                pathname.c_str(), handler->name().c_str());
+    fd_to_stream_->RemoveFileStream(fd);
+    return -1;
+  }
+  stream->set_permission(permission);
+  fd_to_stream_->AddFileStream(fd, stream);
+  return fd;
+}
+
+// Android uses madvise to hint to the kernel about what Ashmem regions can be
+// deleted, and TcMalloc uses it to hint about returned system memory.
+int VirtualFileSystem::madvise(void* addr, size_t length, int advice) {
+  if (!util::IsPageAligned(addr)) {
+    errno = EINVAL;
+    return -1;
+  }
+  base::AutoLock lock(mutex_);
+  return memory_region_->SetAdviceByAddr(
+      addr, util::RoundToPageSize(length), advice);
+}
+
+void* VirtualFileSystem::mmap(void* addr, size_t length, int prot, int flags,
+                              int fd, off_t offset) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (!util::IsPageAligned(addr) || !length) {
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+  if (util::RoundToPageSize(offset) != static_cast<size_t>(offset)) {
+    // |offset| is not a multiple of the page size.
+    errno = EINVAL;
+    return MAP_FAILED;
+  }
+
+  scoped_refptr<FileStream> stream;
+  // dlmalloc() in Bionic never calls mmap with MAP_ANONYMOUS | MAP_FIXED.
+  // Also, note that calls from Bionic can not be captured by posix_translation,
+  // and MemoryRegion can not track such memory regions.
+  if (flags & (MAP_ANON | MAP_ANONYMOUS)) {
+    stream = new PassthroughStream;
+    ARC_STRACE_REPORT_HANDLER(stream->GetStreamType());
+  } else {
+    stream = fd_to_stream_->GetStream(fd);
+  }
+  if (!stream) {
+    errno = EBADF;
+    return MAP_FAILED;
+  }
+
+  length = util::RoundToPageSize(length);
+  void* new_addr = stream->mmap(addr, length, prot, flags, offset);
+  if (new_addr == MAP_FAILED)
+    return new_addr;
+
+  ALOG_ASSERT(util::IsPageAligned(new_addr));
+
+  // If MAP_FIXED is specified, we should remove old FileStream bound to
+  // the region [addr, addr+length), but should not call underlying munmap()
+  // implementation because the region has already been unmapped by the mmap
+  // call above.
+  if (flags & MAP_FIXED)
+    memory_region_->RemoveFileStreamsByAddr(addr, length, false);
+
+  bool result = memory_region_->AddFileStreamByAddr(
+      new_addr, length, offset /* for printing debug info */, prot, flags,
+      stream);
+  if (!result) {
+    if (flags & MAP_FIXED) {
+      ALOG_ASSERT(!abort_on_unexpected_memory_maps_,
+                  "\n%s\nThis memory region does not support mmap with "
+                  "MAP_FIXED because the region is backed by a POSIX "
+                  "incompatible stream. address: %p, size: 0x%zx, stream: %s",
+                  GetMemoryMapAsStringLocked().c_str(), new_addr, length,
+                  stream->GetStreamType());
+    } else {
+      ALOG_ASSERT(!abort_on_unexpected_memory_maps_,
+                  "\n%s\nUnexpected address: %p, size: 0x%zx, stream: %s",
+                  GetMemoryMapAsStringLocked().c_str(), new_addr, length,
+                  stream->GetStreamType());
+    }
+    // It should happen because of a bug or the restriction of MemoryFile
+    // incompatibility.
+    errno = ENODEV;
+    new_addr = MAP_FAILED;
+  }
+  return new_addr;
+}
+
+int VirtualFileSystem::mprotect(void* addr, size_t length, int prot) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  // Note: Do not check if |length| is zero here. See the comment in
+  // ChangeProtectionModeByAddr.
+  if (!util::IsPageAligned(addr)) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  length = util::RoundToPageSize(length);
+  // ChangeProtectionModeByAddr may call FileStream::mprotect() for each stream
+  // in [addr, addr+length).
+  return memory_region_->ChangeProtectionModeByAddr(addr, length, prot);
+}
+
+int VirtualFileSystem::munmap(void* addr, size_t length) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (!util::IsPageAligned(addr) || !length) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  length = util::RoundToPageSize(length);
+  // RemoveFileStreamsByAddr may call FileStream::munmap() for each stream in
+  // [addr, addr+length).
+  return memory_region_->RemoveFileStreamsByAddr(addr, length, true);
+}
+
+int VirtualFileSystem::close(int fd) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (!CloseLocked(fd)) {
+    errno = EBADF;
+    return -1;
+  }
+  return 0;
+}
+
+bool VirtualFileSystem::CloseLocked(int fd) {
+  mutex_.AssertAcquired();
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (!stream)
+    return false;
+  fd_to_stream_->RemoveFileStream(fd);
+  return true;
+}
+
+void VirtualFileSystem::InvalidateCache() {
+  base::AutoLock lock(mutex_);
+  std::vector<FileSystemHandler*> handlers;
+  mount_points_->GetAllFileSystemHandlers(&handlers);
+  for (size_t i = 0; i < handlers.size(); ++i) {
+    handlers[i]->InvalidateCache();
+  }
+}
+
+void VirtualFileSystem::AddToCache(const std::string& path,
+                                   const PP_FileInfo& file_info,
+                                   bool exists) {
+  base::AutoLock lock(mutex_);
+  std::string resolved(path);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  uid_t dummy = 0;
+  // Use |mount_points_| directly instead of GetFileSystemHandlerLocked so
+  // that the main thread can call this method.
+  FileSystemHandler* handler =
+      mount_points_->GetFileSystemHandler(path, &dummy);
+  if (handler)
+    handler->AddToCache(path, file_info, exists);
+  else
+    ALOGW("AddToCache: handler for %s not found", path.c_str());
+}
+
+bool VirtualFileSystem::RegisterFileStream(
+    int fd, scoped_refptr<FileStream> stream) {
+  base::AutoLock lock(mutex_);
+  if (fd_to_stream_->IsKnownDescriptor(fd))
+    return false;
+  ALOG_ASSERT(stream->permission().IsValid());
+  fd_to_stream_->AddFileStream(fd, stream);
+  return true;
+}
+
+bool VirtualFileSystem::IsKnownDescriptor(int fd) {
+  base::AutoLock lock(mutex_);
+  return fd_to_stream_->IsKnownDescriptor(fd);
+}
+
+ssize_t VirtualFileSystem::read(int fd, void* buf, size_t count) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->read(buf, count);
+  errno = EBADF;
+  return -1;
+}
+
+ssize_t VirtualFileSystem::write(int fd, const void* buf, size_t count) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->write(buf, count);
+  errno = EBADF;
+  return -1;
+}
+
+ssize_t VirtualFileSystem::readv(int fd, const struct iovec* iov, int count) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->readv(iov, count);
+  errno = EBADF;
+  return -1;
+}
+
+char* VirtualFileSystem::realpath(const char* path,
+                                  char* resolved_path) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (!path) {
+    errno = EINVAL;
+    return NULL;
+  }
+  // Return NULL when |path| does not exist.
+  struct stat st;
+  if (StatLocked(path, &st))
+    return NULL;  // errno is set in StatLocked.
+
+  std::string resolved(path);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  if (resolved.length() >= PATH_MAX) {
+    errno = ENAMETOOLONG;
+    return NULL;
+  }
+
+  // Note: resolved_path == NULL means we need to allocate a buffer.
+  char* output = resolved_path;
+  if (!output)
+    output = static_cast<char*>(malloc(PATH_MAX));
+
+  snprintf(output, PATH_MAX, "%s", resolved.c_str());
+  ARC_STRACE_REPORT("result=\"%s\"", output);
+  return output;
+}
+
+ssize_t VirtualFileSystem::writev(int fd, const struct iovec* iov, int count) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->writev(iov, count);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::chdir(const std::string& path) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (path.empty()) {
+    errno = ENOENT;
+    return -1;
+  }
+  size_t length = path.length();
+  while (length > 0 && path.at(length - 1) == '/') {
+    // Remove trailing slashes if exist. This is because chdir("foo/") should
+    // succeed if the directory "foo" exists, but stat("foo/", &st), or
+    // StatLocked("foo/", &st) fails with ENOENT.
+    length--;
+  }
+  std::string new_path = path.substr(0, length);
+  if (length)
+    GetNormalizedPathLocked(&new_path, kResolveSymlinks);
+
+  // We do not check if the root directory exist here.
+  if (!new_path.empty()) {
+    struct stat st;
+    int result = StatLocked(new_path, &st);
+    if (result)
+      return result;
+    if (!S_ISDIR(st.st_mode)) {
+      errno = ENOTDIR;
+      return -1;
+    }
+  }
+
+  // Keep the last character always being "/".
+  process_environment_->SetCurrentDirectory(new_path + "/");
+  return 0;
+}
+
+char* VirtualFileSystem::getcwd(char* buf, size_t size) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  char* result = NULL;
+  const std::string& current_working_directory =
+      process_environment_->GetCurrentDirectory();
+  size_t path_length = current_working_directory.length();
+  // |current_working_directory| contains "/" at the end of the path, and
+  // |result| should not contain the last "/" if the path is not root("/").
+  ALOG_ASSERT(util::EndsWithSlash(current_working_directory));
+  if (path_length > 1)
+    path_length--;
+
+  if (buf && !size) {
+    errno = EINVAL;
+    return NULL;
+  } else if (size <= path_length && (buf || size)) {
+    errno = ERANGE;
+    return NULL;
+  } else if (!buf) {
+    if (!size)
+      size = path_length + 1;
+    result = static_cast<char*>(malloc(size));
+    if (!result) {
+      errno = ENOMEM;
+      return NULL;
+    }
+  } else {
+    result = buf;
+  }
+  // Copy |current_working_directory| without the last "/".
+  strncpy(result, current_working_directory.c_str(), path_length);
+  result[path_length] = 0;
+  return result;
+}
+
+int VirtualFileSystem::IsPollReadyLocked(
+    struct pollfd* fds, nfds_t nfds, bool apply) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(fds);
+
+  int result = 0;
+  for (nfds_t i = 0; i < nfds; ++i) {
+    const int16_t events_mask = fds[i].events | POLLHUP | POLLERR | POLLNVAL;
+    scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fds[i].fd);
+    int16_t events =
+        (stream ? stream->GetPollEvents() : POLLNVAL) & events_mask;
+    if (events)
+      ++result;
+
+    if (apply)
+      fds[i].revents = events;
+  }
+
+  return result;
+}
+
+int VirtualFileSystem::poll(struct pollfd* fds, nfds_t nfds, int timeout) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (timeout != 0) {
+    const base::TimeTicks time_limit =  internal::TimeOutToTimeLimit(
+        base::TimeDelta::FromMilliseconds(std::max(0, timeout)));
+    while (!IsPollReadyLocked(fds, nfds, false)) {
+      if (WaitUntil(time_limit)) {
+        // timedout, or spurious wakeup, or real wakeup. Either way, we can
+        // just break since |timeout| has expired.
+        break;
+      }
+    }
+  }
+
+  return IsPollReadyLocked(fds, nfds, true);
+}
+
+ssize_t VirtualFileSystem::pread(int fd, void* buf, size_t count,
+                                 off64_t offset) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->pread(buf, count, offset);
+  errno = EBADF;
+  return -1;
+}
+
+ssize_t VirtualFileSystem::pwrite(int fd, const void* buf, size_t count,
+                                  off64_t offset) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->pwrite(buf, count, offset);
+  errno = EBADF;
+  return -1;
+}
+
+off64_t VirtualFileSystem::lseek(int fd, off64_t offset, int whence) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->lseek(offset, whence);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::dup(int fd) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return DupLocked(fd, -1);
+}
+
+int VirtualFileSystem::dup2(int fd, int newfd) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return DupLocked(fd, newfd);
+}
+
+int VirtualFileSystem::DupLocked(int fd, int newfd) {
+  mutex_.AssertAcquired();
+
+  if (newfd < 0)
+    newfd = GetFirstUnusedDescriptorLocked();
+  if (newfd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  ARC_STRACE_DUP_FD(fd, newfd);
+  if (fd == newfd)
+    return newfd;  // NB: Do not reuse this code for dup3().
+  CloseLocked(newfd);
+  fd_to_stream_->AddFileStream(newfd, stream);
+  return newfd;
+}
+
+scoped_refptr<FileStream> VirtualFileSystem::GetStreamLocked(int fd) {
+  mutex_.AssertAcquired();
+  return fd_to_stream_->GetStream(fd);
+}
+
+int VirtualFileSystem::epoll_create1(int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  int fd = GetFirstUnusedDescriptorLocked();
+  if (fd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  scoped_refptr<EPollStream> stream = new EPollStream(fd, flags);
+  fd_to_stream_->AddFileStream(fd, stream);
+  // Since this function does not call GetFileSystemHandlerLocked(), call
+  // REPORT_HANDLER() explicitly to make STATS in arc_strace.txt easier
+  // to read.
+  ARC_STRACE_REPORT_HANDLER(stream->GetStreamType());
+  return fd;
+}
+
+int VirtualFileSystem::epoll_ctl(int epfd, int op, int fd,
+                                 struct epoll_event* event) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> epoll_stream = fd_to_stream_->GetStream(epfd);
+  scoped_refptr<FileStream> target_stream = fd_to_stream_->GetStream(fd);
+  if (!epoll_stream || !target_stream) {
+    errno = EBADF;
+    return -1;
+  }
+  if (epfd == fd) {
+    errno = EINVAL;
+    return -1;
+  }
+  return epoll_stream->epoll_ctl(op, target_stream, event);
+}
+
+int VirtualFileSystem::epoll_wait(int epfd, struct epoll_event* events,
+                                  int maxevents, int timeout) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(epfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->epoll_wait(events, maxevents, timeout);
+}
+
+int VirtualFileSystem::fstat(int fd, struct stat* out) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  const int result = stream->fstat(out);
+  if (!result) {
+    ALOG_ASSERT(stream->permission().IsValid(), "fd=%d pathname=%s stream=%s",
+                fd, stream->pathname().c_str(), stream->GetStreamType());
+    FillPermissionInfoToStat(stream->permission(), out);
+  }
+  return result;
+}
+
+int VirtualFileSystem::lstat(const std::string& pathname, struct stat* out) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  // Get an absolute path with parent symlinks resolved.
+  std::string normalized(pathname);
+  GetNormalizedPathLocked(&normalized, kResolveParentSymlinks);
+  uid_t dummy = 0;
+  FileSystemHandler* handler = mount_points_->GetFileSystemHandler(normalized,
+                                                                   &dummy);
+  // Resolve the symlink to get the length of the symlink for st_size.
+  // TODO(crbug.com/335418): The resolved path is always an absolute
+  // path. That means symlinks of relative paths are not handled correctly.
+  std::string resolved;
+  const int old_errono = errno;
+  if (handler->readlink(normalized, &resolved) < 0) {
+    errno = old_errono;
+    return StatLocked(normalized, out);
+  }
+
+  memset(out, 0, sizeof(*out));
+  // Use the private function GetInodeUncheckedLocked to bypass the
+  // IsNormalizedPathLocked() check in the public version. Passing a path name
+  // which is a symlink to a file (i.e. not normalized) to
+  // GetInodeUncheckedLocked() is valid since lstat() is for stat'ing the link
+  // itself.
+  out->st_ino = GetInodeUncheckedLocked(normalized);
+  out->st_uid = arc::kRootUid;
+  out->st_gid = arc::kRootGid;
+  out->st_mode = S_IFLNK | 0777;
+  out->st_nlink = 1;
+  out->st_size = resolved.size();
+  out->st_blksize = 4096;
+  return 0;
+}
+
+int VirtualFileSystem::stat(const std::string& pathname, struct stat* out) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return StatLocked(pathname, out);
+}
+
+int VirtualFileSystem::StatLocked(const std::string& pathname,
+                                  struct stat* out) {
+  mutex_.AssertAcquired();
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  ALOG_ASSERT(permission.IsValid(), "pathname=%s handler=%s",
+              pathname.c_str(), handler->name().c_str());
+  const int result = handler->stat(resolved, out);
+  if (!result)
+    FillPermissionInfoToStat(permission, out);
+  return result;
+}
+
+ssize_t VirtualFileSystem::readlink(const std::string& pathname, char* buf,
+                                    size_t bufsiz) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  // Get an absolute path with parent symlinks resolved.
+  std::string normalized(pathname);
+  GetNormalizedPathLocked(&normalized, kResolveParentSymlinks);
+  uid_t dummy = 0;
+  FileSystemHandler* handler = mount_points_->GetFileSystemHandler(normalized,
+                                                                   &dummy);
+  // TODO(crbug.com/335418): The resolved path is always an absolute
+  // path. That means symlinks of relative paths are not handled correctly.
+  std::string resolved;
+  if (handler->readlink(normalized, &resolved) >= 0) {
+    // Truncate if resolved path is too long.
+    if (resolved.size() > bufsiz) {
+      resolved.resize(bufsiz);
+    }
+    // readlink does not append a NULL byte to |buf|.
+    memcpy(buf, resolved.data(), resolved.size());
+    return resolved.size();
+  }
+
+  struct stat st;
+  if (handler->stat(normalized, &st))
+    errno = ENOENT;
+  else
+    errno = EINVAL;
+  return -1;
+}
+
+int VirtualFileSystem::statfs(const std::string& pathname, struct statfs* out) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved, NULL);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  return handler->statfs(resolved, out);
+}
+
+int VirtualFileSystem::statvfs(const std::string& pathname,
+                               struct statvfs* out) {
+  struct statfs tmp;
+  int result = this->statfs(pathname, &tmp);
+  if (result != 0)
+    return result;
+  out->f_bsize = tmp.f_bsize;
+  out->f_frsize = tmp.f_bsize;
+  out->f_blocks = tmp.f_blocks;
+  out->f_bfree = tmp.f_bfree;
+  out->f_bavail = tmp.f_bavail;
+  out->f_files = tmp.f_files;
+  out->f_ffree = tmp.f_ffree;
+  out->f_favail = tmp.f_ffree;
+  out->f_fsid = tmp.f_fsid.__val[0];
+  out->f_flag = 0;
+  out->f_namemax = tmp.f_namelen;
+
+  return 0;
+}
+
+int VirtualFileSystem::ftruncate(int fd, off64_t length) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (length < 0) {
+    errno = EINVAL;
+    return -1;
+  }
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->ftruncate(length);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::getdents(int fd, dirent* buf, size_t count) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->getdents(buf, count);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::fcntl(int fd, int cmd, va_list ap) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream) {
+    return stream->fcntl(cmd, ap);
+  } else if (fd_to_stream_->IsKnownDescriptor(fd)) {
+    // Socket with reserved FD but not allocated yet, for now just ignore.
+    ALOGW("Ignoring fcntl() on file %d", fd);
+    return 0;
+  } else {
+    errno = EBADF;
+    return -1;
+  }
+}
+
+int VirtualFileSystem::fdatasync(int fd) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->fdatasync();
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::fsync(int fd) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream)
+    return stream->fsync();
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::ioctl(int fd, int request, va_list ap) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream) {
+    return stream->ioctl(request, ap);
+  } else {
+    errno = EBADF;
+    return -1;
+  }
+}
+
+int VirtualFileSystem::GetFirstUnusedDescriptorLocked() {
+  mutex_.AssertAcquired();
+  return fd_to_stream_->GetFirstUnusedDescriptor();
+}
+
+int VirtualFileSystem::IsSelectReadyLocked(int nfds, fd_set* fds,
+                                           SelectReadyEvent event,
+                                           bool apply) {
+  mutex_.AssertAcquired();
+  if (!fds)
+    return 0;
+
+  int nset = 0;
+  for (int i = 0; i < nfds; i++) {
+    if (!FD_ISSET(i, fds))
+      continue;
+
+    scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(i);
+    if (!stream)
+      continue;
+
+    bool is_ready;
+    if (event == SELECT_READY_READ) {
+      is_ready = stream->IsSelectReadReady();
+    } else if (event == SELECT_READY_WRITE) {
+      is_ready = stream->IsSelectWriteReady();
+    } else {  // SELECT_READY_EXCEPTION
+      is_ready = stream->IsSelectExceptionReady();
+    }
+
+    if (is_ready) {
+      if (!apply)
+        return 1;
+
+      ARC_STRACE_REPORT("select ready: fd=%d, event=%s", i,
+                          event == SELECT_READY_READ ? "read" :
+                          event == SELECT_READY_WRITE ? "write" :
+                          "exception");
+      nset++;
+    } else {
+      if (apply)
+        FD_CLR(i, fds);
+    }
+  }
+  return nset;
+}
+
+int VirtualFileSystem::select(int nfds, fd_set* readfds, fd_set* writefds,
+                              fd_set* exceptfds, struct timeval* timeout) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  // If timeout is set and it's 0, it means just a polling.
+  const bool is_polling =
+      (timeout && timeout->tv_sec == 0 && timeout->tv_usec == 0);
+  if (!is_polling) {
+    // If timeout is NULL, use base::TimeTicks(), which lets WaitUntil block
+    // indefinitely.
+    const base::TimeTicks time_limit = timeout ?
+        internal::TimeOutToTimeLimit(internal::TimeValToTimeDelta(*timeout)) :
+        base::TimeTicks();
+    while (!(IsSelectReadyLocked(
+                 nfds, readfds, SELECT_READY_READ, false) ||
+             IsSelectReadyLocked(
+                 nfds, writefds, SELECT_READY_WRITE, false) ||
+             IsSelectReadyLocked(
+                 nfds, exceptfds, SELECT_READY_EXCEPTION, false))) {
+      if (WaitUntil(time_limit)) {
+        // timedout, or spurious wakeup, or real wakeup. Either way, we can
+        // just break since |timeout| has expired.
+        break;
+      }
+    }
+
+    // Linux always updates |timeout| while POSIX does not require it. Emulate
+    // the behavior.
+    if (timeout) {
+      const base::TimeTicks end_time = base::TimeTicks::Now();
+      const base::TimeDelta remaining_time =
+          time_limit <= end_time ? base::TimeDelta() : time_limit - end_time;
+      ARC_STRACE_REPORT(
+          "new_timeout={ %lld ms }, original_timeout={ %lld s, %lld us }",
+          static_cast<int64_t>(remaining_time.InMilliseconds()),
+          static_cast<int64_t>(timeout->tv_sec),
+          static_cast<int64_t>(timeout->tv_usec));
+      *timeout = internal::TimeDeltaToTimeVal(remaining_time);
+    }
+  }
+
+  int nread =
+      IsSelectReadyLocked(nfds, readfds, SELECT_READY_READ, true);
+  int nwrite =
+      IsSelectReadyLocked(nfds, writefds, SELECT_READY_WRITE, true);
+  int nexcpt =
+      IsSelectReadyLocked(nfds, exceptfds, SELECT_READY_EXCEPTION, true);
+  if (nread < 0 || nwrite < 0 || nexcpt < 0) {
+    errno = EBADF;
+    return -1;
+  }
+  return nread + nwrite + nexcpt;
+}
+
+
+int VirtualFileSystem::getaddrinfo(const char* hostname, const char* servname,
+                                   const addrinfo* hints, addrinfo** res) {
+  TRACE_EVENT1(ARC_TRACE_CATEGORY, "VirtualFileSystem::getaddrinfo",
+               "hostname", std::string(hostname));
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return host_resolver_.getaddrinfo(hostname, servname, hints, res);
+}
+
+void VirtualFileSystem::freeaddrinfo(addrinfo* ai) {
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return host_resolver_.freeaddrinfo(ai);
+}
+
+struct hostent* VirtualFileSystem::gethostbyname(const char* host) {
+  return host_resolver_.gethostbyname(host);
+}
+
+struct hostent* VirtualFileSystem::gethostbyname2(
+    const char* host, int family) {
+  return host_resolver_.gethostbyname2(host, family);
+}
+
+int VirtualFileSystem::gethostbyname_r(
+    const char* host, struct hostent* ret, char* buf, size_t buflen,
+    struct hostent** result, int* h_errnop) {
+  return host_resolver_.gethostbyname_r(
+      host, ret, buf, buflen, result, h_errnop);
+}
+
+int VirtualFileSystem::gethostbyname2_r(
+    const char* host, int family, struct hostent* ret, char* buf, size_t buflen,
+    struct hostent** result, int* h_errnop) {
+  return host_resolver_.gethostbyname2_r(
+      host, family, ret, buf, buflen, result, h_errnop);
+}
+
+struct hostent* VirtualFileSystem::gethostbyaddr(
+    const void* addr, socklen_t len, int type) {
+  return host_resolver_.gethostbyaddr(addr, len, type);
+}
+
+int VirtualFileSystem::getnameinfo(const sockaddr* sa, socklen_t salen,
+                                   char* host, size_t hostlen,
+                                   char* serv, size_t servlen, int flags) {
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+  return host_resolver_.getnameinfo(
+      sa, salen, host, hostlen, serv, servlen, flags);
+}
+
+int VirtualFileSystem::socket(int socket_family, int socket_type,
+                              int protocol) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  int fd = GetFirstUnusedDescriptorLocked();
+  if (fd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  bool is_inet = (socket_family == AF_INET || socket_family == AF_INET6);
+  scoped_refptr<FileStream> socket;
+  if (is_inet && socket_type == SOCK_DGRAM) {
+    socket = new UDPSocket(fd, socket_family, 0);
+  } else if (is_inet && socket_type == SOCK_STREAM) {
+    socket = new TCPSocket(fd, socket_family, O_RDWR);
+  } else {
+    // Only supporting SOCK_DGRAM and SOCK_STREAM right now. Fail otherwise.
+    ALOGE("Request for unknown socket type %d, family=%d, protocol=%d",
+          socket_type, socket_family, protocol);
+    errno = EAFNOSUPPORT;
+    return -1;
+  }
+  fd_to_stream_->AddFileStream(fd, socket);
+  // Since this function does not call GetFileSystemHandlerLocked(), call
+  // REPORT_HANDLER() explicitly to make STATS in arc_strace.txt easier
+  // to read.
+  ARC_STRACE_REPORT_HANDLER(socket->GetStreamType());
+  return fd;
+}
+
+int VirtualFileSystem::socketpair(int socket_family, int socket_type,
+                                  int protocol, int sv[2]) {
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (socket_family != AF_UNIX) {
+    errno = EAFNOSUPPORT;
+    return -1;
+  }
+  if (protocol != 0) {
+    errno = EOPNOTSUPP;
+    return -1;
+  }
+  if (socket_type != SOCK_SEQPACKET && socket_type != SOCK_STREAM &&
+      socket_type != SOCK_DGRAM) {
+    errno = EOPNOTSUPP;
+    return -1;
+  }
+  if (sv == NULL) {
+    errno = EFAULT;
+    return -1;
+  }
+  base::AutoLock lock(mutex_);
+  int fd1 = GetFirstUnusedDescriptorLocked();
+  if (fd1 < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  int fd2 = GetFirstUnusedDescriptorLocked();
+  if (fd2 < 0) {
+    fd_to_stream_->RemoveFileStream(fd1);
+    errno = EMFILE;
+    return -1;
+  }
+  scoped_refptr<LocalSocket> sock1 = new LocalSocket(
+      0, socket_type, LocalSocket::READ_WRITE);
+  scoped_refptr<LocalSocket> sock2 = new LocalSocket(
+      0, socket_type, LocalSocket::READ_WRITE);
+  sock1->set_peer(sock2);
+  sock2->set_peer(sock1);
+  fd_to_stream_->AddFileStream(fd1, sock1);
+  fd_to_stream_->AddFileStream(fd2, sock2);
+  sv[0] = fd1;
+  sv[1] = fd2;
+  ARC_STRACE_REPORT_HANDLER(sock1->GetStreamType());
+  return 0;
+}
+
+int VirtualFileSystem::connect(int fd, const sockaddr* serv_addr,
+                               socklen_t addrlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->connect(serv_addr, addrlen);
+}
+
+int VirtualFileSystem::shutdown(int fd, int how) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (stream) {
+    // TODO(http://crbug.com/318921): Actually shutdown should be something
+    // more complicated but for now it works.
+    return 0;
+  } else {
+    errno = EBADF;
+    return -1;
+  }
+}
+
+int VirtualFileSystem::bind(int fd, const sockaddr* addr, socklen_t addrlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(fd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->bind(addr, addrlen);
+}
+
+int VirtualFileSystem::chown(const std::string& path, uid_t owner,
+                             gid_t group) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  if (arc::IsAppUid(arc::ProcessEmulator::GetUid())) {
+    errno = EPERM;
+    return -1;
+  }
+  std::string resolved(path);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+
+  struct stat st;
+  if (StatLocked(path, &st) != 0) {
+    // All errno values except this one are valid as the errno of chown.
+    ALOG_ASSERT(errno != EOVERFLOW);
+    return -1;
+  }
+
+  if (S_ISDIR(st.st_mode) && !util::EndsWithSlash(path))
+    mount_points_->ChangeOwner(path + '/', owner);
+  else
+    mount_points_->ChangeOwner(path, owner);
+
+  return 0;
+}
+
+int VirtualFileSystem::listen(int sockfd, int backlog) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->listen(backlog);
+}
+
+int VirtualFileSystem::accept(int sockfd, sockaddr* addr, socklen_t* addrlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->accept(addr, addrlen);
+}
+
+int VirtualFileSystem::getpeername(int sockfd, sockaddr* name,
+                                   socklen_t* namelen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->getpeername(name, namelen);
+}
+
+int VirtualFileSystem::getsockname(int sockfd, sockaddr* name,
+                                   socklen_t* namelen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->getsockname(name, namelen);
+}
+
+ssize_t VirtualFileSystem::send(int sockfd, const void* buf, size_t len,
+                                int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->send(buf, len, flags);
+}
+
+ssize_t VirtualFileSystem::sendto(
+    int sockfd, const void* buf, size_t len, int flags,
+    const sockaddr* dest_addr, socklen_t addrlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->sendto(buf, len, flags, dest_addr, addrlen);
+}
+
+ssize_t VirtualFileSystem::sendmsg(
+    int sockfd, const struct msghdr* msg, int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->sendmsg(msg, flags);
+}
+
+ssize_t VirtualFileSystem::recv(int sockfd, void* buf, size_t len, int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->recv(buf, len, flags);
+}
+
+ssize_t VirtualFileSystem::recvfrom(
+    int sockfd, void* buffer, size_t len, int flags, sockaddr* addr,
+    socklen_t* addrlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->recvfrom(buffer, len, flags, addr, addrlen);
+}
+
+ssize_t VirtualFileSystem::recvmsg(
+    int sockfd, struct msghdr* msg, int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (!stream) {
+    errno = EBADF;
+    return -1;
+  }
+  return stream->recvmsg(msg, flags);
+}
+
+int VirtualFileSystem::getsockopt(int sockfd, int level, int optname,
+                                  void* optval, socklen_t* optlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (stream)
+    return stream->getsockopt(level, optname, optval, optlen);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::setsockopt(int sockfd, int level, int optname,
+                                  const void* optval, socklen_t optlen) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  scoped_refptr<FileStream> stream = fd_to_stream_->GetStream(sockfd);
+  if (stream)
+    return stream->setsockopt(level, optname, optval, optlen);
+  errno = EBADF;
+  return -1;
+}
+
+int VirtualFileSystem::pipe2(int pipefd[2], int flags) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  int read_fd = GetFirstUnusedDescriptorLocked();
+  if (read_fd < 0) {
+    errno = EMFILE;
+    return -1;
+  }
+  int write_fd = GetFirstUnusedDescriptorLocked();
+  if (write_fd < 0) {
+    fd_to_stream_->RemoveFileStream(read_fd);
+    errno = EMFILE;
+    return -1;
+  }
+  scoped_refptr<LocalSocket> read_sock = new LocalSocket(
+      flags, SOCK_STREAM, LocalSocket::READ_ONLY);
+  scoped_refptr<LocalSocket> write_sock = new LocalSocket(
+      flags, SOCK_STREAM, LocalSocket::WRITE_ONLY);
+  read_sock->set_peer(write_sock);
+  write_sock->set_peer(read_sock);
+  fd_to_stream_->AddFileStream(read_fd, read_sock);
+  fd_to_stream_->AddFileStream(write_fd, write_sock);
+  pipefd[0] = read_fd;
+  pipefd[1] = write_fd;
+  // Since this function does not call GetFileSystemHandlerLocked(), call
+  // REPORT_HANDLER() explicitly to make STATS in arc_strace.txt easier
+  // to read.
+  ARC_STRACE_REPORT_HANDLER(read_sock->GetStreamType());
+  return 0;
+}
+
+int VirtualFileSystem::mkdir(const std::string& pathname, mode_t mode) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable()) {
+    struct stat st;
+    if (!handler->stat(resolved, &st)) {
+      errno = EEXIST;
+      return -1;
+    }
+    return DenyAccessForCreateLocked(&resolved, handler);
+  }
+  return handler->mkdir(resolved, mode);
+}
+
+int VirtualFileSystem::access(const std::string& pathname, int mode) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  struct stat st;
+  int result = StatLocked(pathname, &st);
+  if (result) {
+    // All other errno from stat is compatible with access.
+    ALOG_ASSERT(errno != EOVERFLOW);
+    return -1;
+  }
+
+  // Apps cannot modify files owned by system unless it is explicitly
+  // allowed.
+  if ((mode & W_OK) && !(st.st_mode & S_IWOTH) &&
+      arc::IsAppUid(arc::ProcessEmulator::GetUid()) &&
+      !arc::IsAppUid(st.st_uid)) {
+    errno = EACCES;
+    return -1;
+  }
+  // Check for the exec bit.
+  if (mode & X_OK) {
+    if (!(st.st_mode & S_IXUSR)) {
+      errno = EACCES;
+      return -1;
+    }
+    // If exec bit for the owner is set, the file must be owned by the
+    // user (perm=07?? UID=10000) or everyone can execute it (perm=0??5).
+    ALOG_ASSERT(arc::IsAppUid(st.st_uid) || (st.st_mode & S_IXOTH));
+  }
+  // There are no restrictions for read access in ARC.
+  // We also assume that S_IWUSR is always set.
+  ALOG_ASSERT(st.st_mode & S_IWUSR);
+  return 0;
+}
+
+int VirtualFileSystem::remove(const std::string& pathname) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  return handler->remove(resolved);
+}
+
+int VirtualFileSystem::rename(const std::string& oldpath,
+                              const std::string& newpath) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved_oldpath(oldpath);
+  GetNormalizedPathLocked(&resolved_oldpath, kResolveSymlinks);
+  PermissionInfo permission_old;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(
+      resolved_oldpath, &permission_old);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  std::string resolved_newpath(newpath);
+  GetNormalizedPathLocked(&resolved_newpath, kResolveSymlinks);
+  PermissionInfo permission_new;
+  FileSystemHandler* another_handler = GetFileSystemHandlerLocked(
+      resolved_newpath, &permission_new);
+  if (!another_handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (handler != another_handler) {
+    errno = EXDEV;
+    return -1;
+  }
+
+  if (resolved_newpath == resolved_oldpath) {
+    // Renaming to the same path should be successfully done, if it exists.
+    // To check its existence, call stat here. Note that this operation should
+    // succeed, even if it is readonly.
+    struct stat st;
+    int result = StatLocked(resolved_newpath, &st);
+    ALOG_ASSERT(errno != EOVERFLOW);
+    return result;
+  }
+
+  if (!permission_old.is_writable() || !permission_new.is_writable()) {
+    DenyAccessForModifyLocked(resolved_oldpath, handler);
+    const int oldpath_errno = errno;
+    ALOG_ASSERT(oldpath_errno == ENOENT || oldpath_errno == ENOTDIR ||
+                oldpath_errno == EACCES);
+    DenyAccessForCreateLocked(&resolved_newpath, handler);
+    const int newpath_errno = errno;
+    ALOG_ASSERT(newpath_errno == ENOENT || newpath_errno == ENOTDIR ||
+                newpath_errno == EACCES);
+    // This behavior is compatible with ext4. ENOTDIR is preferred to
+    // ENOENT, which is preferred to EACCES.
+    if (oldpath_errno == ENOTDIR || newpath_errno == ENOTDIR) {
+      errno = ENOTDIR;
+      return -1;
+    }
+    if (oldpath_errno == ENOENT || newpath_errno == ENOENT) {
+      errno = ENOENT;
+      return -1;
+    }
+    errno = EACCES;
+    return -1;
+  }
+
+  return handler->rename(resolved_oldpath, resolved_newpath);
+}
+
+int VirtualFileSystem::rmdir(const std::string& pathname) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  return handler->rmdir(resolved);
+}
+
+int VirtualFileSystem::symlink(const std::string& oldpath,
+                               const std::string& newpath) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved_newpath(newpath);
+  GetNormalizedPathLocked(&resolved_newpath, kResolveSymlinks);
+
+  const std::string parent = util::GetDirName(resolved_newpath);
+  PermissionInfo permission_new;
+  FileSystemHandler* newpath_handler = GetFileSystemHandlerLocked(
+      parent, &permission_new);
+  struct stat st;
+  if (!newpath_handler || newpath_handler->stat(parent, &st) < 0) {
+    errno = ENOENT;
+    return -1;
+  }
+
+  if (!permission_new.is_writable()) {
+    if (!newpath_handler->stat(resolved_newpath, &st)) {
+      errno = EEXIST;
+      return -1;
+    }
+    return DenyAccessForModifyLocked(parent, newpath_handler);
+  }
+  return newpath_handler->symlink(oldpath, resolved_newpath);
+}
+
+int VirtualFileSystem::truncate(const std::string& pathname, off64_t length) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  return handler->truncate(resolved, length);
+}
+
+mode_t VirtualFileSystem::umask(mode_t mask) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  mode_t result_umask = process_environment_->GetCurrentUmask();
+  process_environment_->SetCurrentUmask(mask);
+  return result_umask;
+}
+
+int VirtualFileSystem::unlink(const std::string& pathname) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  return handler->unlink(resolved);
+}
+
+int VirtualFileSystem::utime(const std::string& pathname,
+                             const struct utimbuf* times) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  struct timeval t[2];
+  t[0].tv_sec = times->actime;
+  t[0].tv_usec = 0;
+  t[1].tv_sec = times->modtime;
+  t[1].tv_usec = 0;
+  return handler->utimes(resolved, t);
+}
+
+int VirtualFileSystem::utimes(const std::string& pathname,
+                              const struct timeval times[2]) {
+  base::AutoLock lock(mutex_);
+  ARC_STRACE_REPORT_HANDLER(kVirtualFileSystemHandlerStr);
+
+  std::string resolved(pathname);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  PermissionInfo permission;
+  FileSystemHandler* handler = GetFileSystemHandlerLocked(resolved,
+                                                          &permission);
+  if (!handler) {
+    errno = ENOENT;
+    return -1;
+  }
+  if (!permission.is_writable())
+    return DenyAccessForModifyLocked(resolved, handler);
+  return handler->utimes(resolved, times);
+}
+
+void VirtualFileSystem::Wait() {
+  // Calling cond_.Wait() on main thread results in deadlock.
+  ALOG_ASSERT(!pp::Module::Get()->core()->IsMainThread());
+  // Chromium's Wait() automatically checks if mutex_ is locked.
+  cond_.Wait();
+}
+
+bool VirtualFileSystem::WaitUntil(const base::TimeTicks& time_limit) {
+  return internal::WaitUntil(&cond_, time_limit);
+}
+
+void VirtualFileSystem::Signal() {
+  mutex_.AssertAcquired();
+  cond_.Signal();
+}
+
+void VirtualFileSystem::Broadcast() {
+  mutex_.AssertAcquired();
+  cond_.Broadcast();
+}
+
+void VirtualFileSystem::SetBrowserReady() {
+  base::AutoLock lock(mutex_);
+  ALOG_ASSERT(!browser_ready_);
+  browser_ready_ = true;
+  cond_.Broadcast();
+}
+
+bool VirtualFileSystem::IsBrowserReadyLocked() const {
+  mutex_.AssertAcquired();
+  return browser_ready_;
+}
+
+void VirtualFileSystem::Mount(const std::string& path,
+                              FileSystemHandler* handler) {
+  base::AutoLock lock(mutex_);
+  mount_points_->Add(path, handler);
+}
+
+void VirtualFileSystem::Unmount(const std::string& path) {
+  base::AutoLock lock(mutex_);
+  mount_points_->Remove(path);
+}
+
+void VirtualFileSystem::ChangeMountPointOwner(const std::string& path,
+                                              uid_t owner_uid) {
+  base::AutoLock lock(mutex_);
+  mount_points_->ChangeOwner(path, owner_uid);
+}
+
+bool VirtualFileSystem::IsNormalizedPathLocked(const std::string& path) {
+  std::string resolved(path);
+  GetNormalizedPathLocked(&resolved, kResolveSymlinks);
+  if (path != "/" && util::EndsWithSlash(path))
+    resolved += '/';
+  return path == resolved;
+}
+
+void VirtualFileSystem::GetNormalizedPathLocked(std::string* in_out_path,
+                                                NormalizeOption option) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(in_out_path);
+
+  // Handle lstat("/path/to/symlink_to_dir/.") and readdir() for "." after
+  // opendir("/path/to/symlink_to_dir") cases properly.
+  util::RemoveTrailingSlashes(in_out_path);
+  if (option == kResolveParentSymlinks && EndsWith(*in_out_path, "/.", true))
+    option = kResolveSymlinks;
+
+  // Remove . and //.
+  util::RemoveSingleDotsAndRedundantSlashes(in_out_path);
+  if (in_out_path->empty())
+    return;
+
+  // If the path is relative, prepend CWD.
+  if (*in_out_path == ".") {
+    *in_out_path = process_environment_->GetCurrentDirectory();
+    util::RemoveTrailingSlashes(in_out_path);
+  } else if ((*in_out_path)[0] != '/') {
+    in_out_path->insert(0, process_environment_->GetCurrentDirectory());
+  }
+  ALOG_ASSERT(*in_out_path == "/" || !util::EndsWithSlash(*in_out_path));
+
+  // Resolve .. and symlinks.
+  std::vector<std::string> directories;
+  base::SplitString(*in_out_path, '/', &directories);
+  in_out_path->clear();
+  for (size_t i = 0; i < directories.size(); i++) {
+    if (directories[i].empty()) {
+      // Splitting "/" and "/foo" results in ["", ""] and ["", "foo"],
+      // respectively.
+      continue;
+    }
+    ALOG_ASSERT(!util::EndsWithSlash(*in_out_path), "%s", in_out_path->c_str());
+    if (directories[i] == "..") {
+      if (!in_out_path->empty()) {  // to properly handle "/.."
+        // TODO(crbug.com/287721): Check if |*in_out_path| is a directory.
+        const size_t pos = in_out_path->rfind('/');
+        ALOG_ASSERT(pos != std::string::npos);
+        in_out_path->resize(pos);
+      }
+    } else {
+      in_out_path->append("/" + directories[i]);
+      if (option == kResolveSymlinks ||
+          (option == kResolveParentSymlinks && i != directories.size() - 1)) {
+        ResolveSymlinks(in_out_path);
+      }
+    }
+  }
+  // Handles cases like "/.." and "/../".
+  if (in_out_path->empty())
+    in_out_path->assign("/");
+
+  ARC_STRACE_REPORT(
+      "Normalized to: %s%s", in_out_path->c_str(),
+      (option == kResolveParentSymlinks ? " (parent only)" : ""));
+}
+
+int VirtualFileSystem::DenyAccessForCreateLocked(std::string* path,
+                                                 FileSystemHandler* handler) {
+  mutex_.AssertAcquired();
+  ALOG_ASSERT(path);
+  util::GetDirNameInPlace(path);
+  return DenyAccessForModifyLocked(*path, handler);
+}
+
+int VirtualFileSystem::DenyAccessForModifyLocked(const std::string& path,
+                                                 FileSystemHandler* handler) {
+  mutex_.AssertAcquired();
+  // Linux checks the existence of a file before it checks the
+  // permission of it. To emulate this behavior, we will prefer errno
+  // set by access to EACCES.
+  struct stat st;
+  if (!handler->stat(path, &st))
+    errno = EACCES;
+  ALOG_ASSERT(errno == ENOENT || errno == ENOTDIR || errno == EACCES);
+  ARC_STRACE_REPORT("DenyAccess: path=%s errno=%d", path.c_str(), errno);
+  return -1;
+}
+
+void VirtualFileSystem::ResolveSymlinks(std::string* in_out_path) {
+  // Check if |in_out_path| is a symlink.
+  uid_t dummy = 0;
+  FileSystemHandler* handler =
+    mount_points_->GetFileSystemHandler(*in_out_path, &dummy);
+  if (!handler)
+    return;
+  std::string resolved;
+  const int old_errono = errno;
+  if (handler->readlink(*in_out_path, &resolved) >= 0) {
+    ALOG_ASSERT(*in_out_path != resolved);
+    in_out_path->replace(0, in_out_path->length(), resolved);
+    // TODO(crbug.com/226346): There are no protection against
+    // infinite symbolic link loop.
+    return ResolveSymlinks(in_out_path);
+  }
+  errno = old_errono;
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/virtual_file_system.h b/src/posix_translation/virtual_file_system.h
new file mode 100644
index 0000000..8685082
--- /dev/null
+++ b/src/posix_translation/virtual_file_system.h
@@ -0,0 +1,369 @@
+// Copyright (c) 2012 The Chromium OS 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 POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_H_
+#define POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_H_
+
+#include <dirent.h>
+#include <errno.h>
+#include <memory.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/vfs.h>
+#include <termios.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/containers/hash_tables.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "common/export.h"
+#include "posix_translation/file_system_handler.h"
+#include "posix_translation/host_resolver.h"
+#include "posix_translation/virtual_file_system_interface.h"
+#include "ppapi/cpp/file_system.h"
+#include "ppapi/utility/completion_callback_factory.h"
+
+namespace posix_translation {
+
+class FdToFileStreamMap;
+class FileStream;
+class MemoryRegion;
+class MountPointManager;
+class PepperFileHandler;
+class PermissionInfo;
+class ProcessEnvironment;
+class ReadonlyFileHandler;
+
+const ino_t kBadInode = -1;
+
+// Returns the current VirtualFileSystemInterface instance used by
+// libposix_translation.
+ARC_EXPORT VirtualFileSystemInterface* GetVirtualFileSystemInterface();
+
+// Replaces the current VirtualFileSystemInterface instace used by
+// libposix_translation. The function takes ownership of |vfs|.
+ARC_EXPORT void SetVirtualFileSystemInterface(VirtualFileSystemInterface* vfs);
+
+// This class is an abstraction layer on top of multiple concrete file
+// systems.
+class VirtualFileSystem : public VirtualFileSystemInterface {
+ public:
+  // |min_fd| is the minimum file number used in the file system.
+  // |max_fd| is the maximum.
+  ARC_EXPORT VirtualFileSystem(pp::Instance* instance,
+                               ProcessEnvironment* process_environment,
+                               int min_fd,
+                               int max_fd);
+  virtual ~VirtualFileSystem();
+
+  // Returns the current VirtualFileSystem instance used by
+  // libposix_translation.
+  // The returned instance is idential to
+  //   static_cast<VirtualFileSystem*>(GetVirtualFileSystemInterface())
+  // when it is actually VirtualFileSystem. Otherwise it aborts.
+  // This function is not exported because it's intended to be called only
+  // inside posix_translation.
+  static VirtualFileSystem* GetVirtualFileSystem();
+
+  // Implements file system functions.
+  int accept(int sockfd, sockaddr* addr, socklen_t* addrlen);
+  int access(const std::string& pathname, int mode);
+  int bind(int sockfd, const sockaddr* serv_addr,
+           socklen_t addrlen);
+  int chdir(const std::string& path);
+  int chown(const std::string& path, uid_t owner, gid_t group);
+  int close(int fd);
+  int connect(int sockfd, const sockaddr* serv_addr,
+              socklen_t addrlen);
+  int dup(int fd);
+  int dup2(int fd, int newfd);
+  int epoll_create1(int flags);
+  int epoll_ctl(int epfd, int op, int fd,
+                struct epoll_event* event);
+  int epoll_wait(int epfd, struct epoll_event* events, int maxevents,
+                 int timeout);
+  int fcntl(int fd, int cmd, va_list ap);
+  int fdatasync(int fd);
+  void freeaddrinfo(addrinfo* ai);
+  int fstat(int fd, struct stat* out);
+  int fsync(int fd);
+  int ftruncate(int fd, off64_t length);
+  int getaddrinfo(const char* hostname, const char* servname,
+                  const addrinfo* hints, addrinfo** res);
+  char* getcwd(char* buf, size_t size);
+  int getdents(int fd, dirent*, size_t count);
+  struct hostent* gethostbyaddr(
+      const void* addr, socklen_t len, int type);
+  struct hostent* gethostbyname(const char* hostname);
+  int gethostbyname_r(
+      const char* hostname, struct hostent* ret,
+      char* buf, size_t buflen,
+      struct hostent** result, int* h_errnop);
+  struct hostent* gethostbyname2(const char* hostname,
+                                 int family);
+  int gethostbyname2_r(
+      const char* hostname, int family, struct hostent* ret,
+      char* buf, size_t buflen,
+      struct hostent** result, int* h_errnop);
+  int getnameinfo(const sockaddr* sa, socklen_t salen,
+                  char* host, size_t hostlen,
+                  char* serv, size_t servlen, int flags);
+  int getpeername(int s, sockaddr* name, socklen_t* namelen);
+  int getsockname(int s, sockaddr* name, socklen_t* namelen);
+  int getsockopt(int sockfd, int level, int optname, void* optval,
+                 socklen_t* optlen);
+  int ioctl(int fd, int request, va_list ap);
+  int listen(int sockfd, int backlog);
+  off64_t lseek(int fd, off64_t offset, int whence);
+  int lstat(const std::string& pathname, struct stat* out);
+  int madvise(void* addr, size_t length, int advice);
+  int mkdir(const std::string& pathname, mode_t mode);
+  void* mmap(void* addr, size_t length, int prot, int flags, int fd,
+             off_t offset);
+  int mprotect(void* addr, size_t length, int prot);
+  int munmap(void* addr, size_t length);
+  int open(const std::string& pathname, int oflag,
+           mode_t cmode);
+  int pipe2(int pipefd[2], int flags);
+  int poll(struct pollfd* fds, nfds_t nfds, int timeout);
+  ssize_t pread(int fd, void* buf, size_t count,
+                off64_t offset);
+  ssize_t pwrite(int fd, const void* buf, size_t count,
+                 off64_t offset);
+  ssize_t read(int fd, void* buf, size_t count);
+  ssize_t readlink(const std::string& pathname, char* buf,
+                   size_t bufsiz);
+  ssize_t readv(int fd, const struct iovec* iovec, int count);
+  char* realpath(const char* path, char* resolved_path);
+  ssize_t recv(int sockfd, void* buf, size_t len, int flags);
+  ssize_t recvfrom(int socket, void* buffer, size_t len, int flags,
+                   sockaddr* addr, socklen_t* addrlen);
+  ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
+  int remove(const std::string& pathname);
+  int rename(const std::string& oldpath,
+             const std::string& newpath);
+  int rmdir(const std::string& pathname);
+  int select(int nfds, fd_set* readfds, fd_set* writefds,
+             fd_set* exceptfds, struct timeval* timeout);
+  ssize_t send(int sockfd, const void* buf, size_t len,
+               int flags);
+  ssize_t sendto(int sockfd, const void* buf, size_t len, int flags,
+                 const sockaddr* dest_addr, socklen_t addrlen);
+  ssize_t sendmsg(int sockfd, const struct msghdr* msg,
+                  int flags);
+  int setsockopt(int sockfd, int level, int optname, const void* optval,
+                 socklen_t optlen);
+  int shutdown(int sockfd, int how);
+  int socket(int socket_family, int socket_type, int protocol);
+  int socketpair(int socket_family, int socket_type, int protocol,
+                 int sv[2]);
+  int stat(const std::string& pathname, struct stat* out);
+  int statfs(const std::string& pathname, struct statfs* out);
+  int statvfs(const std::string& pathname,
+              struct statvfs* out);
+  int symlink(const std::string& oldpath,
+              const std::string& newpath);
+  int truncate(const std::string& pathname, off64_t length);
+  mode_t umask(mode_t mask);
+  int unlink(const std::string& pathname);
+  int utime(const std::string& pathname,
+            const struct utimbuf* times);
+  int utimes(const std::string& pathname,
+             const struct timeval times[2]);
+  ssize_t write(int fd, const void* buf, size_t count);
+  ssize_t writev(int fd, const struct iovec* iov, int count);
+
+  // VirtualFileSystemInterface overrides.
+  virtual void Mount(const std::string& path,
+                     FileSystemHandler* handler) OVERRIDE;
+  virtual void Unmount(const std::string& path) OVERRIDE;
+  virtual void ChangeMountPointOwner(const std::string& path,
+                                     uid_t owner_uid) OVERRIDE;
+
+  virtual void SetBrowserReady() OVERRIDE;
+  virtual void InvalidateCache() OVERRIDE;
+  virtual void AddToCache(const std::string& path, const PP_FileInfo& file_info,
+                          bool exists) OVERRIDE;
+  virtual bool RegisterFileStream(int fd,
+                                  scoped_refptr<FileStream> stream) OVERRIDE;
+  virtual FileSystemHandler* GetFileSystemHandler(
+      const std::string& path) OVERRIDE;
+  virtual bool IsWriteMapped(ino_t inode) OVERRIDE;
+  virtual bool IsCurrentlyMapped(ino_t inode) OVERRIDE;
+  virtual std::string GetMemoryMapAsString() OVERRIDE;
+  virtual std::string GetIPCStatsAsString() OVERRIDE;
+  virtual int StatForTesting(
+      const std::string& pathname, struct stat* out) OVERRIDE;
+
+  // TODO(crbug.com/245003): Get rid of the getter.
+  base::Lock& mutex() { return mutex_; }
+
+  pp::Instance* instance() { return instance_; }
+
+  // Condition variable operations.
+  // Blocks current thread and waits for the condition variable is signaled.
+  void Wait();
+
+  // Blocks current thread and waits for the condition variable to be signaled
+  // until |time_limit|. Returns true if it is timed out.
+  // If |time_limit| is null (i.e. is_null() returns true), this blocks the
+  // current thread forever until the condition variable is signaled.
+  // See WaitUntil() in time_util.{h,cc} for more details.
+  bool WaitUntil(const base::TimeTicks& time_limit);
+
+  void Signal();
+  void Broadcast();
+
+  // Return true if the file system initialization on the browser side has
+  // already been done.
+  bool IsBrowserReadyLocked() const;
+
+  // Checks if |fd| is managed by posix_translation.
+  bool IsKnownDescriptor(int fd);
+
+  // Return an inode number for the |path|. If it's not assigned yet, assign
+  // a new number and return it.
+  ino_t GetInodeLocked(const std::string& path);
+
+  // The same as GetInodeLocked() except that this function does not check if
+  // |path| is normalized. This function is only for implementing GetInodeLocked
+  // and lstat. Always use GetInodeLocked instead.
+  ino_t GetInodeUncheckedLocked(const std::string& path);
+
+  // Remove the inode number for the |path| assigned by GetInodeLocked().
+  void RemoveInodeLocked(const std::string& path);
+  // Reassign the inode for |oldpath| to |newpath|. This is for supporting
+  // rename(2).
+  void ReassignInodeLocked(const std::string& oldpath,
+                           const std::string& newpath);
+
+  int AddFileStreamLocked(scoped_refptr<FileStream> stream);
+  bool CloseLocked(int fd);
+  int DupLocked(int fd, int newfd);
+
+  scoped_refptr<FileStream> GetStreamLocked(int fd);
+
+  // Option to specify how to normalize a path. Public for testing.
+  enum NormalizeOption {
+    // Resolve all symlinks for a path.
+    // Example: /link/link/link -> /dir/dir/file
+    kResolveSymlinks,
+    // Resolve parent symlinks for a path. This is used for implementing
+    // functions that handles symlinks such as readlink() and lstat().
+    // Example: /link/link/link -> /dir/dir/link
+    kResolveParentSymlinks,
+    kDoNotResolveSymlinks,
+  };
+
+  // Converts |in_out_path| to an absolute path. If |option| is
+  // kResolveSymlinks or kResolveParentSymlinks, symlinks are resolved.
+  void GetNormalizedPathLocked(std::string* in_out_path,
+                               NormalizeOption option);
+
+ private:
+  enum SelectReadyEvent {
+    SELECT_READY_READ,
+    SELECT_READY_WRITE,
+    SELECT_READY_EXCEPTION
+  };
+
+  friend class FileSystemTestCommon;
+  template <typename> friend class FileSystemBackgroundTestCommon;
+  friend class MemoryRegionTest;
+  friend class PepperFileTest;
+  friend class PepperTCPSocketTest;
+
+  typedef base::hash_map<std::string, ino_t> InodeMap;  // NOLINT
+
+  // Gets the FileSystemHandler object for |path|. See the comment in
+  // mount_point_manager.h for detail about the return value.
+  // Also sets |out_permission| if not NULL.
+  FileSystemHandler* GetFileSystemHandlerLocked(
+      const std::string& path, PermissionInfo* out_permission);
+
+  int GetFirstUnusedDescriptorLocked();
+
+  int IsSelectReadyLocked(int nfds, fd_set* fds,
+                          SelectReadyEvent event,
+                          bool apply);
+  int IsPollReadyLocked(struct pollfd* fds, nfds_t nfds, bool apply);
+
+  // Returns true if all memory pages in [addr, addr+length) are not in use.
+  // This is for testing.
+  bool IsMemoryRangeAvailableLocked(void* addr, size_t length);
+
+  int StatLocked(const std::string& pathname, struct stat* out);
+
+  // Returns true if |path| is already normalized with kResolveSymlinks.
+  bool IsNormalizedPathLocked(const std::string& path);
+
+  // Sets appropriate errno for file creation. This function should be
+  // called only when we already know write access to |path| is denied.
+  // |path| must be already normalized.|path| might be modified in the
+  // function. This function always returns -1.
+  int DenyAccessForCreateLocked(std::string* path, FileSystemHandler* handler);
+
+  // Sets appropriate errno for file modification. See above comment
+  // for other details of this function.
+  int DenyAccessForModifyLocked(const std::string& path,
+                                FileSystemHandler* handler);
+
+  // Gets a /proc/self/maps like memory map for debugging in a human readable
+  // format.
+  std::string GetMemoryMapAsStringLocked();
+
+  // Resolves symlinks in path in-place.
+  // TODO(satorux): Write a unit test for this function once gmock is gone
+  // from virtual_file_system_test.cc crbug.com/335430.
+  void ResolveSymlinks(std::string* in_out_path);
+
+  static VirtualFileSystem* file_system_;
+
+  // True if the file system initialization on the browser side has been done.
+  bool browser_ready_;
+
+  pp::Instance* instance_;
+
+  ProcessEnvironment* process_environment_;
+
+  // TODO(crbug.com/245003): Stop locking the |mutex_| when calling into
+  // FileSystemHandler/FileStream.
+  base::Lock mutex_;
+  // TODO(yusukes): Remove this global cond_. All condition variables
+  // should be targeted to specific functions or streams to reduce contention
+  // on var's internal lock. At the same time try to avoid using broadcast().
+  base::ConditionVariable cond_;
+
+  scoped_ptr<FdToFileStreamMap> fd_to_stream_;
+  scoped_ptr<MemoryRegion> memory_region_;
+  InodeMap inodes_;
+  ino_t next_inode_;
+  scoped_ptr<MountPointManager> mount_points_;
+
+  HostResolver host_resolver_;
+
+  bool abort_on_unexpected_memory_maps_;  // For unit testing.
+
+  DISALLOW_COPY_AND_ASSIGN(VirtualFileSystem);
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_H_
diff --git a/src/posix_translation/virtual_file_system_host_resolver_test.cc b/src/posix_translation/virtual_file_system_host_resolver_test.cc
new file mode 100644
index 0000000..ea26941
--- /dev/null
+++ b/src/posix_translation/virtual_file_system_host_resolver_test.cc
@@ -0,0 +1,373 @@
+// Copyright (c) 2014 The Chromium OS 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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "ppapi_mocks/ppb_host_resolver.h"
+#include "ppapi_mocks/ppb_net_address.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/test_util/virtual_file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+
+using ::testing::DoAll;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::SetArgPointee;
+
+namespace posix_translation {
+
+// This class is used to test host resolution functions in VirtualFileSystem
+// such as getaddrinfo().
+class FileSystemHostResolverTest
+    : public FileSystemBackgroundTestCommon<FileSystemHostResolverTest> {
+ public:
+  FileSystemHostResolverTest() {
+  }
+
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv4);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv4NumberNullHint);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv4NumberAF_INET);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv4NumberAF_UNSPEC);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv4NumberAF_INET6);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv6);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv6NumberNullHint);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv6NumberAF_INET6);
+  DECLARE_BACKGROUND_TEST(TestGetAddrInfoIPv6NumberAF_UNSPEC);
+  // TODO(crbug.com/247201): Add tests for failure cases for the various API's
+  // invoked by the getaddrinfo() implementation.
+
+ protected:
+  typedef FileSystemBackgroundTestCommon<FileSystemHostResolverTest>
+      CommonType;
+
+  static const int kResolverResource = 191;
+  static const int kNetAddressResource = 192;
+
+  virtual void SetUp() OVERRIDE {
+    CommonType::SetUp();
+    factory_.GetMock(&ppb_host_resolver_);
+    factory_.GetMock(&ppb_netaddress_);
+  }
+
+  void ExpectResolve(const char* expected_hostname, uint16_t expected_port) {
+    EXPECT_CALL(*ppb_host_resolver_, Create(kInstanceNumber)).
+        WillOnce(Return(kResolverResource));
+    // We only support blocking call.
+    EXPECT_CALL(*ppb_host_resolver_,
+                Resolve(kResolverResource,
+                        expected_hostname,
+                        expected_port, NotNull(), _)).
+        WillOnce(Return(static_cast<int32_t>(PP_OK)));
+  }
+
+  void ExpectGetCanonicalName(const char* returned_hostname) {
+    EXPECT_CALL(*ppb_host_resolver_,
+                GetCanonicalName(kResolverResource)).
+        WillOnce(Return(pp::Var(returned_hostname).pp_var()));
+  }
+
+  void ExpectGetNetAddressCount(int size) {
+    EXPECT_CALL(*ppb_host_resolver_,
+                GetNetAddressCount(kResolverResource)).WillOnce(Return(size));
+  }
+
+  void ExpectGetNetAddressIPv4(int index, uint16_t returned_port,
+                               const struct in_addr& returned_addr) {
+    EXPECT_CALL(*ppb_host_resolver_, GetNetAddress(kResolverResource, index)).
+        WillOnce(Return(kNetAddressResource));
+
+    // Setup IPv4 NetAddress instance.
+    PP_NetAddress_IPv4 ipv4_addr = {};
+    ipv4_addr.port = returned_port;
+    memcpy(ipv4_addr.addr, &returned_addr, sizeof(ipv4_addr.addr));
+
+    EXPECT_CALL(*ppb_netaddress_, GetFamily(kNetAddressResource)).
+        WillRepeatedly(Return(PP_NETADDRESS_FAMILY_IPV4));
+    EXPECT_CALL(*ppb_netaddress_,
+                DescribeAsIPv4Address(kNetAddressResource, _)).
+        WillRepeatedly(DoAll(SetArgPointee<1>(ipv4_addr), Return(PP_TRUE)));
+  }
+
+  void ExpectGetNetAddressIPv6(int index, uint16_t returned_port,
+                               const struct in6_addr& returned_addr) {
+    EXPECT_CALL(*ppb_host_resolver_, GetNetAddress(kResolverResource, index)).
+        WillOnce(Return(kNetAddressResource));
+
+    // Setup IPv6 NetAddress instance.
+    PP_NetAddress_IPv6 ipv6_addr = {};
+    ipv6_addr.port = returned_port;
+    memcpy(ipv6_addr.addr, &returned_addr, sizeof(ipv6_addr.addr));
+
+    EXPECT_CALL(*ppb_netaddress_, GetFamily(kNetAddressResource)).
+        WillRepeatedly(Return(PP_NETADDRESS_FAMILY_IPV6));
+    EXPECT_CALL(*ppb_netaddress_,
+                DescribeAsIPv6Address(kNetAddressResource, _)).
+        WillRepeatedly(DoAll(SetArgPointee<1>(ipv6_addr), Return(PP_TRUE)));
+  }
+
+ private:
+  ::testing::NiceMock<PPB_HostResolver_Mock>* ppb_host_resolver_;
+  ::testing::NiceMock<PPB_NetAddress_Mock>* ppb_netaddress_;
+};
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest, TestGetAddrInfoIPv4) {
+  ExpectResolve("example.com", 0);
+  ExpectGetCanonicalName("resolve.example.com");
+  ExpectGetNetAddressCount(1);
+  const in_addr kReturnAddr = {0x12345678};
+  ExpectGetNetAddressIPv4(0, 101, kReturnAddr);
+
+  addrinfo* res = NULL;
+  errno = 0;
+  EXPECT_EQ(0, file_system_->getaddrinfo("example.com", NULL, NULL, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res[0].ai_flags);
+  EXPECT_EQ(AF_INET, res[0].ai_family);
+  EXPECT_EQ(SOCK_STREAM, res[0].ai_socktype);
+  EXPECT_EQ(0, res[0].ai_protocol);
+  // socklen_t is signed in bionic so we need a cast.
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in)),
+            res[0].ai_addrlen);
+  EXPECT_STREQ("resolve.example.com", res[0].ai_canonname);
+  struct sockaddr_in* addr = reinterpret_cast<struct sockaddr_in*>(
+      res[0].ai_addr);
+  EXPECT_EQ(AF_INET, addr->sin_family);
+  EXPECT_EQ(101, addr->sin_port);
+  EXPECT_EQ(kReturnAddr.s_addr, addr->sin_addr.s_addr);
+
+  file_system_->freeaddrinfo(res);
+  res = NULL;
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv4NumberNullHint) {
+  const in_addr kReturnAddr = {htonl(0x7F000001)};
+
+  // getaddrinfo with no hint.
+  addrinfo* res = NULL;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo("127.0.0.1", NULL, NULL, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in)),
+            res->ai_addrlen);
+  struct sockaddr_in* addr =
+      reinterpret_cast<struct sockaddr_in*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET, addr->sin_family);
+  EXPECT_EQ(0, addr->sin_port);
+  EXPECT_EQ(kReturnAddr.s_addr, addr->sin_addr.s_addr);
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv4NumberAF_INET) {
+  const in_addr kReturnAddr = {htonl(0x7F000001)};
+
+  addrinfo* res = NULL;
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = AF_INET;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo("127.0.0.1", NULL, &hint, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in)),
+            res->ai_addrlen);
+  struct sockaddr_in* addr =
+      reinterpret_cast<struct sockaddr_in*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET, addr->sin_family);
+  EXPECT_EQ(0, addr->sin_port);
+  EXPECT_EQ(kReturnAddr.s_addr, addr->sin_addr.s_addr);
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv4NumberAF_UNSPEC) {
+  const in_addr kReturnAddr = {htonl(0x7F000001)};
+
+  addrinfo* res = NULL;
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = AF_UNSPEC;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo("127.0.0.1", NULL, &hint, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in)),
+            res->ai_addrlen);
+  struct sockaddr_in* addr =
+      reinterpret_cast<struct sockaddr_in*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET, addr->sin_family);
+  EXPECT_EQ(0, addr->sin_port);
+  EXPECT_EQ(kReturnAddr.s_addr, addr->sin_addr.s_addr);
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv4NumberAF_INET6) {
+  const in6_addr kReturnAddr = {{{
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1}}};
+
+  addrinfo* res = NULL;
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = AF_INET6;
+  hint.ai_flags = AI_V4MAPPED;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo("127.0.0.1", NULL, &hint, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET6, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in6)),
+            res->ai_addrlen);
+  struct sockaddr_in6* addr =
+      reinterpret_cast<struct sockaddr_in6*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET6, addr->sin6_family);
+  EXPECT_EQ(0, addr->sin6_port);
+  EXPECT_THAT(addr->sin6_addr.s6_addr, ElementsAreArray(kReturnAddr.s6_addr));
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest, TestGetAddrInfoIPv6) {
+  ExpectResolve("example.com", 0);
+  ExpectGetCanonicalName("resolve.example.com");
+  ExpectGetNetAddressCount(1);
+  const in6_addr kReturnAddr = {{{
+    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}};
+  ExpectGetNetAddressIPv6(0, 101, kReturnAddr);
+
+  addrinfo* res = NULL;
+  errno = 0;
+  EXPECT_EQ(0, file_system_->getaddrinfo("example.com", NULL, NULL, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res[0].ai_flags);
+  EXPECT_EQ(AF_INET6, res[0].ai_family);
+  EXPECT_EQ(SOCK_STREAM, res[0].ai_socktype);
+  EXPECT_EQ(0, res[0].ai_protocol);
+  // socklen_t is signed in bionic so we need a cast.
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in6)),
+            res[0].ai_addrlen);
+  EXPECT_STREQ("resolve.example.com", res[0].ai_canonname);
+  struct sockaddr_in6* addr = reinterpret_cast<struct sockaddr_in6*>(
+      res[0].ai_addr);
+  EXPECT_EQ(AF_INET6, addr->sin6_family);
+  EXPECT_EQ(101, addr->sin6_port);
+  EXPECT_THAT(addr->sin6_addr.s6_addr, ElementsAreArray(kReturnAddr.s6_addr));
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv6NumberNullHint) {
+  const in6_addr kReturnAddr = {{{
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
+
+  // getaddrinfo with no hint.
+  addrinfo* res = NULL;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo(
+      "1:203:405:607:809:A0B:C0D:E0F", NULL, NULL, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET6, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in6)),
+            res->ai_addrlen);
+  struct sockaddr_in6* addr =
+      reinterpret_cast<struct sockaddr_in6*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET6, addr->sin6_family);
+  EXPECT_EQ(0, addr->sin6_port);
+  EXPECT_THAT(addr->sin6_addr.s6_addr, ElementsAreArray(kReturnAddr.s6_addr));
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv6NumberAF_INET6) {
+  const in6_addr kReturnAddr = {{{
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
+
+  addrinfo* res = NULL;
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = AF_INET6;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo(
+      "1:203:405:607:809:A0B:C0D:E0F", NULL, &hint, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET6, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in6)),
+            res->ai_addrlen);
+  struct sockaddr_in6* addr =
+      reinterpret_cast<struct sockaddr_in6*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET6, addr->sin6_family);
+  EXPECT_EQ(0, addr->sin6_port);
+  EXPECT_THAT(addr->sin6_addr.s6_addr, ElementsAreArray(kReturnAddr.s6_addr));
+
+  file_system_->freeaddrinfo(res);
+}
+
+TEST_BACKGROUND_F(FileSystemHostResolverTest,
+                  TestGetAddrInfoIPv6NumberAF_UNSPEC) {
+  const in6_addr kReturnAddr = {{{
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}};
+
+  addrinfo* res = NULL;
+  addrinfo hint;
+  memset(&hint, 0, sizeof(hint));
+  hint.ai_family = AF_UNSPEC;
+  errno = 0;
+  ASSERT_EQ(0, file_system_->getaddrinfo(
+      "1:203:405:607:809:A0B:C0D:E0F", NULL, &hint, &res));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, res->ai_flags);
+  EXPECT_EQ(AF_INET6, res->ai_family);
+  EXPECT_EQ(SOCK_STREAM, res->ai_socktype);
+  EXPECT_EQ(0, res->ai_protocol);
+  EXPECT_EQ(static_cast<socklen_t>(sizeof(struct sockaddr_in6)),
+            res->ai_addrlen);
+  struct sockaddr_in6* addr =
+      reinterpret_cast<struct sockaddr_in6*>(res->ai_addr);
+  ASSERT_TRUE(addr);
+  EXPECT_EQ(AF_INET6, addr->sin6_family);
+  EXPECT_EQ(0, addr->sin6_port);
+  EXPECT_THAT(addr->sin6_addr.s6_addr, ElementsAreArray(kReturnAddr.s6_addr));
+
+  file_system_->freeaddrinfo(res);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/virtual_file_system_interface.h b/src/posix_translation/virtual_file_system_interface.h
new file mode 100644
index 0000000..0342922
--- /dev/null
+++ b/src/posix_translation/virtual_file_system_interface.h
@@ -0,0 +1,88 @@
+// Copyright 2014 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 POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_INTERFACE_H_
+#define POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_INTERFACE_H_
+
+#include <unistd.h>
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "common/export.h"
+
+struct PP_FileInfo;
+
+namespace posix_translation {
+
+class FileStream;
+class FileSystemHandler;
+
+// An abstraction layer on top of multiple concrete file systems.
+// It exports file system initialization interface for plugins.
+class VirtualFileSystemInterface {
+ public:
+  virtual ~VirtualFileSystemInterface() {}
+
+  // Registers |handler| to |path|. If |path| ends with '/', this is
+  // considered as a directory and files under |path| will be handled
+  // by |handler|. This function does not take the ownership of
+  // |handler|. The UID of the mount point added is kRootUid.
+  virtual void Mount(const std::string& path, FileSystemHandler* handler) = 0;
+
+  // Unregisters handler associated with |path| if exists. Do nothing if no
+  // handler is associated with |path|.
+  virtual void Unmount(const std::string& path) = 0;
+
+  // Changes the owner of |path| to |owner_uid|. If |path| is not
+  // registered yet, this function will add a mount point using the
+  // FileSystemHandler for |path|. When |path| is a directory, it must
+  // end with '/'.
+  virtual void ChangeMountPointOwner(const std::string& path,
+                                     uid_t owner_uid) = 0;
+
+  // Called when the file system initialization on the browser side is done.
+  // Until this method is called, PepperFileHandler::Initialize() will block.
+  virtual void SetBrowserReady() = 0;
+
+  // Invalidates any data cached by FileSystemHandlers.
+  virtual void InvalidateCache() = 0;
+
+  // Adds metadata for the |path| to the cache in a FileSystemHandler for the
+  // |path|.
+  // TODO(yusukes): Change the type of |file_info| to a non-Pepper one. Then
+  // remove the forward declaration at the beginning of this file too.
+  virtual void AddToCache(const std::string& path,
+                          const PP_FileInfo& file_info,
+                          bool exists) = 0;
+
+  // Associates |stream| with |fd|. Returns false if |fd| is already in use.
+  // This interface is useful for e.g. registering FileStreams for pre-existing
+  // FDs like STDIN/STDOUT/STDERR_FILENOs.
+  virtual bool RegisterFileStream(int fd, scoped_refptr<FileStream> stream) = 0;
+
+  // Returns a FileSystemHandler which is for the |path|. NULL if no handler
+  // is registered for the |path|.
+  virtual FileSystemHandler* GetFileSystemHandler(const std::string& path) = 0;
+
+  // Returns true if the file associated with |inode| is or was mmapped with
+  // PROT_WRITE.
+  virtual bool IsWriteMapped(ino_t inode) = 0;
+  // Returns true if the file associated with |inode| is currently mmapped
+  // regardless of the protection mode.
+  virtual bool IsCurrentlyMapped(ino_t inode) = 0;
+
+  // Gets a /proc/self/maps like memory map for debugging in a human readable
+  // format.
+  virtual std::string GetMemoryMapAsString() = 0;
+  // Gets Pepper IPC stats in a human readable format.
+  virtual std::string GetIPCStatsAsString() = 0;
+
+  // Performs stat(2). Exposed for unit tests where system calls are not
+  // wrapped.
+  virtual int StatForTesting(const std::string& pathname, struct stat* out) = 0;
+};
+
+}  // namespace posix_translation
+#endif  // POSIX_TRANSLATION_VIRTUAL_FILE_SYSTEM_INTERFACE_H_
diff --git a/src/posix_translation/virtual_file_system_path_test.cc b/src/posix_translation/virtual_file_system_path_test.cc
new file mode 100644
index 0000000..0b36d12
--- /dev/null
+++ b/src/posix_translation/virtual_file_system_path_test.cc
@@ -0,0 +1,1137 @@
+// Copyright (c) 2014 The Chromium OS 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 <string.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "common/process_emulator.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/path_util.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/test_util/virtual_file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+// Changes the user ID and sets it back to the original user ID when this
+// object goes away.
+class ScopedUidSetter {
+ public:
+  explicit ScopedUidSetter(uid_t uid)
+      : original_uid_(arc::ProcessEmulator::GetUid()) {
+    arc::ProcessEmulator::SetFallbackUidForTesting(uid);
+  }
+
+  ~ScopedUidSetter() {
+    arc::ProcessEmulator::SetFallbackUidForTesting(original_uid_);
+  }
+
+ private:
+  const uid_t original_uid_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedUidSetter);
+};
+
+namespace {
+
+const time_t kTime = 1355707320;
+const time_t kTime2 = 1355707399;
+
+const mode_t kDirectoryMode = S_IFDIR | 0755;
+const mode_t kRegularFileMode = S_IFREG | 0644;
+
+// A stub implementation of FileStream.
+class StubFileStream : public FileStream {
+ public:
+  StubFileStream()
+      : FileStream(0, "") {
+  }
+
+  virtual ssize_t read(void*, size_t) OVERRIDE { return -1; }
+  virtual ssize_t write(const void*, size_t) OVERRIDE { return -1; }
+  virtual const char* GetStreamType() const OVERRIDE { return "stub"; }
+
+  // Sets some dummy value. Used to verify that fstat() is called.
+  virtual int fstat(struct stat* out) OVERRIDE {
+    out->st_mode = S_IFREG | 0777;
+    return 0;
+  }
+};
+
+// A stub/fake-ish implementation of FileSystemHandler. This class maintains
+// a map for entries, a map for symlinks, and a map for streams, so that
+// functions like readlink() can have fake behaviors. Some functions just
+// record parameters for verifycation purpose (ex. open()).
+class TestFileSystemHandler : public FileSystemHandler {
+ public:
+  TestFileSystemHandler()
+      : FileSystemHandler("TestFileSystemHandler"),
+        mode_param_(-1),
+        flags_param_(-1),
+        length_param_(-1),
+        times_param_() {
+  }
+
+  virtual scoped_refptr<FileStream> open(
+      int fd, const std::string& path, int flags, mode_t mode) OVERRIDE {
+    path_param_ = path;
+    flags_param_ = flags;
+    mode_param_ = mode;
+    std::map<std::string, scoped_refptr<FileStream> >::const_iterator iter =
+        stream_map_.find(path);
+    if (iter != stream_map_.end())
+      return iter->second;
+    errno = ENOENT;
+    return NULL;
+  }
+  virtual Dir* OnDirectoryContentsNeeded(const std::string&) OVERRIDE {
+    return NULL;
+  }
+
+  virtual int mkdir(const std::string& path, mode_t mode) OVERRIDE {
+    const std::string parent = util::GetDirName(path);
+    std::map<std::string, mode_t>::const_iterator iter =
+        entry_map_.find(parent);
+    // Parent not found.
+    if (iter == entry_map_.end()) {
+      errno = ENOENT;
+      return -1;
+    }
+    // Parent is not a directory
+    if (!S_ISDIR(iter->second)) {
+      errno = ENOTDIR;
+      return -1;
+    }
+    AddEntry(path, S_IFDIR | mode);
+    return 0;
+  }
+
+  virtual ssize_t readlink(const std::string& path,
+                           std::string* resolved) OVERRIDE {
+    std::map<std::string, std::string>::const_iterator iter =
+        symlink_map_.find(path);
+    if (iter != symlink_map_.end()) {
+      *resolved = iter->second;
+      return resolved->size();
+    }
+
+    errno = EINVAL;
+    return -1;
+  }
+
+  virtual int rename(const std::string& oldpath,
+                     const std::string& newpath) OVERRIDE {
+    if (entry_map_.count(oldpath) == 0) {
+      errno = ENOENT;
+      return -1;
+    }
+
+    if (entry_map_.count(newpath) != 0) {
+      errno = EEXIST;
+      return 0;
+    }
+
+    entry_map_[newpath] = entry_map_[oldpath];
+    entry_map_.erase(oldpath);
+    return 0;
+  }
+
+  virtual int stat(const std::string& path, struct stat* out) OVERRIDE {
+    std::string parent = path;
+    while (parent != "/") {
+      util::GetDirNameInPlace(&parent);
+      std::map<std::string, mode_t>::const_iterator iter =
+          entry_map_.find(parent);
+      // Parent not found.
+      if (iter == entry_map_.end()) {
+        errno = ENOENT;
+        return -1;
+      }
+      // Non-directory parent found.
+      if (!S_ISDIR(iter->second)) {
+        errno = ENOTDIR;
+        return -1;
+      }
+    }
+
+    memset(out, 0, sizeof(*out));
+    std::map<std::string, mode_t>::const_iterator iter =
+        entry_map_.find(path);
+    if (iter != entry_map_.end()) {
+      out->st_mode = iter->second;
+      return 0;
+    }
+    errno = ENOENT;
+    return -1;
+  }
+
+  // If |path| is known, returns the number of files.
+  virtual int statfs(const std::string& path, struct statfs* out) OVERRIDE {
+    if (entry_map_.count(path) != 0) {
+      memset(out, 0, sizeof(*out));
+      out->f_files = entry_map_.size();
+      return 0;
+    } else {
+      errno = ENOENT;
+      return -1;
+    }
+  }
+
+  int symlink(const std::string& oldpath,
+              const std::string& newpath) {
+    struct stat st;
+    // Save errno because it can be changed by stat below.
+    int old_errno = errno;
+    if (symlink_map_.count(newpath) != 0 || stat(newpath, &st) == 0) {
+      errno = EEXIST;
+      return -1;
+    }
+    errno = old_errno;
+    AddSymlink(newpath, oldpath);
+    return 0;
+  }
+
+  // If |path| is known, succeeds. Records |length| for verfiication.
+  virtual int truncate(const std::string& path, off64_t length) OVERRIDE {
+    length_param_ = length;
+    if (entry_map_.count(path) != 0) {
+      return 0;
+    } else {
+      errno = EINVAL;
+      return -1;
+    }
+  }
+
+
+  // If |path| is known, removes it from the entry map.
+  virtual int unlink(const std::string& path) OVERRIDE {
+    if (entry_map_.count(path) != 0) {
+      entry_map_.erase(path);
+      return 0;
+    } else {
+      errno = ENOENT;
+      return -1;
+    }
+  }
+
+  // If |path| is known. Records |times| for verification.
+  virtual int utimes(const std::string& path,
+                     const struct timeval times[2]) OVERRIDE {
+    times_param_[0] = times[0];
+    times_param_[1] = times[1];
+    if (entry_map_.count(path) != 0) {
+      return 0;
+    } else {
+      errno = ENOENT;
+      return -1;
+    }
+  }
+
+  void AddSymlink(const std::string& from, const std::string& to) {
+    symlink_map_[from] = to;
+  }
+
+  void AddStream(const std::string& path, scoped_refptr<FileStream> stream) {
+    stream_map_[path] = stream;
+    AddEntry(path, kRegularFileMode);
+  }
+
+  void AddEntry(const std::string& path, mode_t mode) {
+    entry_map_[path] = mode;
+  }
+
+  std::string path_param_;
+  mode_t mode_param_;
+  int flags_param_;
+  off64_t length_param_;
+  struct timeval times_param_[2];
+
+  std::map<std::string, mode_t> entry_map_;
+  std::map<std::string, std::string> symlink_map_;
+  std::map<std::string, scoped_refptr<FileStream> > stream_map_;
+};
+
+}  // namespace
+
+// This class is used to test path-related functions in VirtualFileSystem,
+// such as access(), chdir(), lstat(), readlink(), rename(), etc.
+class FileSystemPathTest
+    : public FileSystemBackgroundTestCommon<FileSystemPathTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(TestGetNormalizedPathResolvingSymlinks);
+  DECLARE_BACKGROUND_TEST(TestAccess);
+  DECLARE_BACKGROUND_TEST(TestChangedDirectoryPath);
+  DECLARE_BACKGROUND_TEST(TestClose);
+  DECLARE_BACKGROUND_TEST(TestCloseBadFD);
+  DECLARE_BACKGROUND_TEST(TestFstat);
+  DECLARE_BACKGROUND_TEST(TestFstatBadFD);
+  DECLARE_BACKGROUND_TEST(TestFstatClosedFD);
+  DECLARE_BACKGROUND_TEST(TestFtruncateNegative);
+  DECLARE_BACKGROUND_TEST(TestFtruncateBadFD);
+  DECLARE_BACKGROUND_TEST(TestFtruncateClosedFD);
+  DECLARE_BACKGROUND_TEST(TestLstat);
+  DECLARE_BACKGROUND_TEST(TestLstat_RelativePath);
+  DECLARE_BACKGROUND_TEST(TestLstat_NestedSymlinks);
+  DECLARE_BACKGROUND_TEST(TestMkdir);
+  DECLARE_BACKGROUND_TEST(TestMkdirFail);
+  DECLARE_BACKGROUND_TEST(TestOpen);
+  DECLARE_BACKGROUND_TEST(TestOpenDup2Close);
+  DECLARE_BACKGROUND_TEST(TestOpenDupClose);
+  DECLARE_BACKGROUND_TEST(TestOpenFail);
+  DECLARE_BACKGROUND_TEST(TestReadLink);
+  DECLARE_BACKGROUND_TEST(TestReadLink_RelativePath);
+  DECLARE_BACKGROUND_TEST(TestReadLink_RelativeTargetPath);
+  DECLARE_BACKGROUND_TEST(TestReadLink_NestedSymlinks);
+  DECLARE_BACKGROUND_TEST(TestRealpath);
+  DECLARE_BACKGROUND_TEST(TestRealpathWithBuf);
+  DECLARE_BACKGROUND_TEST(TestRename);
+  DECLARE_BACKGROUND_TEST(TestStat);
+  DECLARE_BACKGROUND_TEST(TestStatFS);
+  DECLARE_BACKGROUND_TEST(TestSymlink);
+  DECLARE_BACKGROUND_TEST(TestTruncate);
+  DECLARE_BACKGROUND_TEST(TestUnlink);
+  DECLARE_BACKGROUND_TEST(TestUTime);
+  DECLARE_BACKGROUND_TEST(TestUTimes);
+
+ protected:
+  typedef FileSystemBackgroundTestCommon<FileSystemPathTest> CommonType;
+
+  virtual void SetUp() OVERRIDE {
+    CommonType::SetUp();
+    handler_.AddEntry("/", kDirectoryMode);
+    AddMountPoint("/", &handler_);  // for realpath(".");
+    errno = -1;
+  }
+
+  virtual void TearDown() OVERRIDE {
+    ClearMountPoints();
+    CommonType::TearDown();
+  }
+
+  const char* GetCurrentWorkingDirectory() {
+    current_working_directory_.reset(file_system_->getcwd(NULL, 0));
+    return current_working_directory_.get();
+  }
+
+  TestFileSystemHandler handler_;
+  scoped_ptr<char, base::FreeDeleter> current_working_directory_;
+};
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestGetNormalizedPathResolvingSymlinks) {
+  base::AutoLock lock(mutex());
+  handler_.AddSymlink("/link.file", "/test.file");
+  handler_.AddSymlink("/test.dir/link.file", "/test.file");
+  handler_.AddSymlink("/link.dir/link.file", "/test.file");
+  handler_.AddSymlink("/link.dir", "/test.dir");
+  handler_.AddSymlink("/test.dir/link.dir", "/test2.dir");
+
+  EXPECT_EQ("/link.file",
+            GetNormalizedPath("/link.file",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/link.file",
+            GetNormalizedPath("/link.file",
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.file",
+            GetNormalizedPath("/link.file",
+                              VirtualFileSystem::kResolveSymlinks));
+
+  EXPECT_EQ("/test.dir/link.file",
+            GetNormalizedPath("/test.dir/link.file",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/test.dir/link.file",
+            GetNormalizedPath("/test.dir/link.file",
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.file",
+            GetNormalizedPath("/test.dir/link.file",
+                              VirtualFileSystem::kResolveSymlinks));
+
+  EXPECT_EQ("/link.dir/link.file",
+            GetNormalizedPath("/link.dir/link.file",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/test.dir/link.file",
+            GetNormalizedPath("/link.dir/link.file",
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.file",
+            GetNormalizedPath("/link.dir/link.file",
+                              VirtualFileSystem::kResolveSymlinks));
+
+  // Test '..' resolution.
+  std::string test_path = "/link.dir/../link.dir";
+  EXPECT_EQ("/link.dir",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/link.dir",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveSymlinks));
+
+  test_path = "/link.dir/../link.dir/link.file";
+  EXPECT_EQ("/link.dir/link.file",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/test.dir/link.file",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.file",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveSymlinks));
+
+  test_path = "/test.dir/link.dir/..";
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveSymlinks));
+  EXPECT_EQ("/",
+            GetNormalizedPath(test_path,
+                              VirtualFileSystem::kResolveParentSymlinks));
+
+  // Test '.' resolution.
+  EXPECT_EQ("/link.dir",
+            GetNormalizedPath("/link.dir/.",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/link.dir",
+            GetNormalizedPath("/link.dir/./",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/link.dir",
+            GetNormalizedPath("/link.dir/.//",
+                              VirtualFileSystem::kDoNotResolveSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/.",
+                              VirtualFileSystem::kResolveSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/./",
+                              VirtualFileSystem::kResolveSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/.//",
+                              VirtualFileSystem::kResolveSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/.",
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/./",
+                              VirtualFileSystem::kResolveParentSymlinks));
+  EXPECT_EQ("/test.dir",
+            GetNormalizedPath("/link.dir/.//",
+                              VirtualFileSystem::kResolveParentSymlinks));
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestAccess) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  // Test as a system user.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.dir", F_OK));
+  EXPECT_EQ(0, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.dir", R_OK | W_OK | X_OK));
+  EXPECT_EQ(0, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.file", F_OK));
+  EXPECT_EQ(0, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.file", R_OK | W_OK));
+  EXPECT_EQ(0, errno);
+
+  // A file is not executable.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->access("/test.file", X_OK));
+  EXPECT_EQ(EACCES, errno);
+  errno = 0;
+
+  // Test as an app.
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.dir", F_OK));
+  EXPECT_EQ(0, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.dir", R_OK | X_OK));
+  EXPECT_EQ(0, errno);
+
+  // User cannot modify system directories.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->access("/test.dir", W_OK));
+  EXPECT_EQ(EACCES, errno);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->access("/test.dir", R_OK));
+  EXPECT_EQ(0, errno);
+
+  // User cannot write system files.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->access("/test.file", W_OK));
+  EXPECT_EQ(EACCES, errno);
+
+  // A file is not executable.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->access("/test.file", X_OK));
+  EXPECT_EQ(EACCES, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestChangedDirectoryPath) {
+  handler_.AddEntry("/", kDirectoryMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+
+  // Check if chdir("") fails with ENOENT.
+  EXPECT_EQ(-1, file_system_->chdir(""));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.file") fails with ENOTDIR.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->chdir("/test.file"));
+  EXPECT_EQ(ENOTDIR, errno);
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.dir") dir works
+  EXPECT_EQ(0, file_system_->chdir("/test.dir"));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Check if chdir(".") succeeds with current directory.
+  EXPECT_EQ(0, file_system_->chdir("."));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Reset the current directory.
+  EXPECT_EQ(0, file_system_->chdir("/"));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.dir/") works (with a trailing "/").
+  EXPECT_EQ(0, file_system_->chdir("/test.dir" + std::string("/")));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Check if chdir("no-such-dir") fails, and the current directory does not
+  // change.
+  EXPECT_EQ(-1, file_system_->chdir("no-such-dir"));
+  EXPECT_EQ(ENOENT, errno);
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Reset the current directory.
+  EXPECT_EQ(0, file_system_->chdir("/"));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("test.dir") works (chdir via a relative path).
+  EXPECT_EQ(0, file_system_->chdir("test.dir"));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Reset the current directory.
+  EXPECT_EQ(0, file_system_->chdir("/"));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.dir////" works.
+  EXPECT_EQ(0, file_system_->chdir("test.dir////"));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Reset the current directory.
+  EXPECT_EQ(0, file_system_->chdir("/"));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.dir/./") works.
+  EXPECT_EQ(0, file_system_->chdir("/test.dir/./"));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Reset the current directory.
+  EXPECT_EQ(0, file_system_->chdir("/"));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+
+  // Check if chdir("/test.dir/././.") works.
+  EXPECT_EQ(0, file_system_->chdir("/test.dir/././."));
+  EXPECT_STREQ("/test.dir", GetCurrentWorkingDirectory());
+
+  // Check if chdir("..") works.
+  EXPECT_EQ(0, file_system_->chdir(".."));
+  EXPECT_STREQ("/", GetCurrentWorkingDirectory());
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestClose) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  int fd = file_system_->open("/test.file", O_RDONLY, 0);
+  EXPECT_LE(0, fd);
+  errno = 0;
+  EXPECT_EQ(0, file_system_->close(fd));
+  EXPECT_EQ(0, errno);
+  EXPECT_ERROR(file_system_->close(fd), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestCloseBadFD) {
+  EXPECT_ERROR(file_system_->close(-1), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFstat) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  int fd = file_system_->open("/test.file", O_RDONLY, 0);
+  struct stat st = {};
+
+  errno = 0;
+  // Verify that StubFileStream::fstat() is called.
+  EXPECT_EQ(0, file_system_->fstat(fd, &st));
+  EXPECT_EQ(static_cast<mode_t>(S_IFREG | 0777), st.st_mode);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fd));
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFstatBadFD) {
+  struct stat st, zerost;
+  memset(&zerost, 0, sizeof(st));
+  st = zerost;
+  EXPECT_ERROR(file_system_->fstat(-1, &st), EBADF);
+  EXPECT_EQ(0, memcmp(&zerost, &st, sizeof(st)));
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFstatClosedFD) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  int fd = file_system_->open("/test.file", O_RDONLY, 0);
+  EXPECT_LE(0, fd);
+  EXPECT_EQ(0, file_system_->close(fd));
+  struct stat st;
+  EXPECT_ERROR(file_system_->fstat(fd, &st), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFtruncateNegative) {
+  EXPECT_ERROR(file_system_->ftruncate(-1, -123), EINVAL);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFtruncateBadFD) {
+  EXPECT_ERROR(file_system_->ftruncate(-1, 0), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestFtruncateClosedFD) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  int fd = file_system_->open("/test.file", O_RDWR, 0);
+  EXPECT_EQ(0, file_system_->close(fd));
+  EXPECT_ERROR(file_system_->ftruncate(fd, 0), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestLstat) {
+  handler_.AddEntry("/test.file", S_IFREG);
+  handler_.AddSymlink("/link.file", "/test.file");
+
+  errno = 0;
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, file_system_->lstat("/test.file", &st));
+  EXPECT_EQ(0, errno);
+
+  memset(&st, 1, sizeof(st));
+  errno = 0;
+  EXPECT_EQ(0, file_system_->lstat("/link.file", &st));
+  EXPECT_EQ(S_IFLNK, static_cast<int>(st.st_mode & S_IFMT));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestLstat_RelativePath) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddSymlink("/test.dir/link.file", "/test.file");
+
+  EXPECT_EQ(0, file_system_->chdir("/test.dir"));
+
+  // Confirm that lstat() works with a relative path.
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, file_system_->lstat("link.file", &st));
+  EXPECT_EQ(S_IFLNK, static_cast<int>(st.st_mode & S_IFMT));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestLstat_NestedSymlinks) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddSymlink("/link.dir", "/test.dir");
+  handler_.AddSymlink("/test.dir/link.file", "/test.file");
+
+  // Confirm that lstat() works with nested symlinks.
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, file_system_->lstat("/link.dir/link.file", &st));
+  EXPECT_EQ(S_IFLNK, static_cast<int>(st.st_mode & S_IFMT));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestMkdir) {
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Make "/test.dir" app-writable, to allow mkdir() on this path.
+  ChangeMountPointOwner("/test.dir", arc::kFirstAppUid);
+
+  // "/test.dir" should be created as expected.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->mkdir("/test.dir", 0777));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(static_cast<mode_t>(S_IFDIR | 0777),
+            handler_.entry_map_["/test.dir"]);
+
+  // If the parent directory exists, mkdir should set EACCES to errno.
+  handler_.AddEntry("/readonly.dir",  kDirectoryMode);
+  EXPECT_EQ(-1, file_system_->mkdir("/readonly.dir/foo", 0777));
+  EXPECT_EQ(EACCES, errno);
+  errno = 0;
+
+  // If the parent directory does not exist, mkdir should set ENOENT to errno.
+  EXPECT_EQ(-1, file_system_->mkdir("/nonexistent.dir/bar", 0777));
+  EXPECT_EQ(ENOENT, errno);
+  errno = 0;
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestMkdirFail) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  AddMountPoint("/test.file", &handler_);
+
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Linux kernel prefers EEXIST over EACCES. We emulate the behavior.
+  EXPECT_ERROR(file_system_->mkdir("/test.file", 0), EEXIST);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestOpen) {
+  handler_.AddStream("/test.file", new StubFileStream);
+  errno = 0;
+  int fd = file_system_->open("/test.file", O_RDONLY, 0);
+  EXPECT_LE(0, fd);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("/test.file", handler_.path_param_);
+  EXPECT_EQ(O_RDONLY, handler_.flags_param_);
+  EXPECT_EQ(static_cast<mode_t>(0), handler_.mode_param_);
+
+  // If the path is empty, ENOENT should be returned.
+  fd = file_system_->open("", O_RDONLY, 0);
+  EXPECT_LE(-1, fd);
+  EXPECT_EQ(ENOENT, errno);
+  fd = file_system_->open("", O_WRONLY | O_CREAT, 0700);
+  EXPECT_LE(-1, fd);
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestOpenDup2Close) {
+  handler_.AddStream("/test.file", new StubFileStream);
+
+  int fd = file_system_->open("/test.file", O_RDWR | O_CREAT, 0);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("/test.file", handler_.path_param_);
+  EXPECT_EQ(O_RDWR | O_CREAT, handler_.flags_param_);
+  EXPECT_EQ(static_cast<mode_t>(0), handler_.mode_param_);
+
+  static const int kUnusedFd = 12345;  // large number
+  int fd2 = file_system_->dup2(fd, kUnusedFd);
+  EXPECT_EQ(kUnusedFd, fd2);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fd));
+  EXPECT_EQ(0, file_system_->close(fd2));
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestOpenDupClose) {
+  handler_.AddStream("/test.file", new StubFileStream);
+
+  int fd = file_system_->open("/test.file", O_RDWR | O_CREAT, 0);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("/test.file", handler_.path_param_);
+  EXPECT_EQ(O_RDWR | O_CREAT, handler_.flags_param_);
+  EXPECT_EQ(static_cast<mode_t>(0), handler_.mode_param_);
+
+  int fd2 = file_system_->dup(fd);
+  EXPECT_NE(fd, fd2);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, file_system_->close(fd2));
+  EXPECT_EQ(0, file_system_->close(fd));
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestOpenFail) {
+  // No stream is associated with "/test.file".
+  EXPECT_ERROR(file_system_->open("/test.file", O_RDONLY, 0), ENOENT);
+
+  handler_.AddStream("/test.file", new StubFileStream);
+  AddMountPoint("/test.file", &handler_);
+
+  // open() will fail because "/test.file" is owned by the system UID, which
+  // cannot be modified by the app UID.
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  EXPECT_ERROR(file_system_->open("/test.file", O_RDWR | O_CREAT, 0), EACCES);
+  EXPECT_ERROR(file_system_->open("/test.file", O_RDONLY | O_CREAT, 0), EACCES);
+  // When O_CREAT|O_EXCL is specified, Linux kernel prefers EEXIST over EACCES.
+  // We emulate the behavior.
+  EXPECT_ERROR(file_system_->open("/test.file", O_RDONLY | O_CREAT | O_EXCL, 0),
+               EEXIST);
+  EXPECT_ERROR(file_system_->open("/test.file", O_RDONLY | O_TRUNC, 0), EACCES);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestReadLink) {
+  handler_.AddSymlink("/link.file", "/test.file");
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  char buf[64];
+  errno = 0;
+  ssize_t len = file_system_->readlink("/link.file", buf, 63);
+  ASSERT_EQ(strlen("/test.file"), static_cast<size_t>(len));
+  EXPECT_EQ(0, errno);
+  buf[len] = '\0';
+  EXPECT_STREQ("/test.file", buf);
+
+  // The buffer size is too small.
+  buf[5] = 'X';  // Sentinel to make sure the result is actually truncated.
+  len = file_system_->readlink("/link.file", buf, 5);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(5, len);
+  EXPECT_EQ('X', buf[5]);  // The trailing bytes should not be touched.
+  buf[5] = '\0';  // '\0'-terminate to compare the buf as a string.
+  EXPECT_STREQ("/test", buf);
+
+  // The path is not a symbolic link.
+  len = file_system_->readlink("/test.file", buf, 63);
+  EXPECT_EQ(-1, len);
+  EXPECT_EQ(EINVAL, errno);
+
+  // The path does not exist.
+  errno = 0;
+  len = file_system_->readlink("/nonexistent.file", buf, 63);
+  EXPECT_EQ(-1, len);
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestReadLink_RelativePath) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddSymlink("/test.dir/link.file", "/test.file");
+
+  // Move to "/test.dir".
+  EXPECT_EQ(0, file_system_->chdir("/test.dir"));
+
+  // Confirm that readlink() works with a relative path.
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  char buf[64];
+  errno = 0;
+  ssize_t len = file_system_->readlink("link.file", buf, 63);
+  buf[len] = '\0';
+  EXPECT_STREQ("/test.file", buf);
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestReadLink_RelativeTargetPath) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddSymlink("/test.dir/link.file", "../test.file");
+
+  // Move to "/test.dir".
+  EXPECT_EQ(0, file_system_->chdir("/test.dir"));
+
+  // Confirm that readlink() works with a relative path.
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  char buf[64];
+  errno = 0;
+  ssize_t len = file_system_->readlink("link.file", buf, 63);
+  buf[len] = '\0';
+  EXPECT_STREQ("../test.file", buf);
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestReadLink_NestedSymlinks) {
+  handler_.AddEntry("/test.dir", kDirectoryMode);
+  handler_.AddSymlink("/link.dir", "/test.dir");
+  handler_.AddSymlink("/test.dir/link.file", "/test.file");
+
+  // Confirm that readlink() works nested symlinks
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  char buf[64];
+  ssize_t len = file_system_->readlink("/link.dir/link.file", buf, 63);
+  buf[len] = '\0';
+  EXPECT_STREQ("/test.file", buf);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestRealpath) {
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  EXPECT_EQ(0, file_system_->chdir("/"));
+
+  // Test if NULL is allowed.
+  char* result = file_system_->realpath(NULL, NULL);
+  ASSERT_TRUE(result == NULL);
+  result = file_system_->realpath("", NULL);
+  ASSERT_TRUE(result == NULL);
+  result = file_system_->realpath("/test.file", NULL);
+  ASSERT_TRUE(result != NULL);
+  EXPECT_STREQ("/test.file", result);
+  free(result);  // confirm this does not crash.
+
+  // Check that the function normalize dot(s).
+  result = file_system_->realpath(".", NULL);
+  EXPECT_TRUE(result != NULL);
+  free(result);  // confirm this does not crash.
+  result = file_system_->realpath(("/." + std::string("/test.file")).c_str(),
+                                  NULL);
+  ASSERT_TRUE(result != NULL);
+  EXPECT_STREQ("/test.file", result);
+  free(result);  // confirm this does not crash.
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestRealpathWithBuf) {
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  // Confirm that non-NULL buffer is also allowed.
+  char buf[PATH_MAX];
+  char* result = file_system_->realpath("/test.file", buf);
+  ASSERT_EQ(buf, result);
+  EXPECT_STREQ("/test.file", result);
+
+  // Check that the function normalize dots.
+  result = file_system_->realpath(
+      ("/." + std::string("/test.file")).c_str(), buf);
+  ASSERT_EQ(buf, result);
+  EXPECT_STREQ("/test.file", result);
+  result = file_system_->realpath(("/./." + std::string("/test.file")).c_str(),
+                                  buf);
+  ASSERT_EQ(buf, result);
+  EXPECT_STREQ("/test.file", result);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestRename) {
+  ScopedUidSetter setter(arc::kFirstAppUid);
+
+  handler_.AddEntry("/readonly.dir", kDirectoryMode);
+  handler_.AddStream("/test.file", new StubFileStream);
+  AddMountPoint("/test.file", &handler_);
+
+  // This mount point will be unmounted in TearDown().
+  AddMountPoint("/test.new", &handler_);
+  // Make the following paths writable, to allow rename() on these paths.
+  ChangeMountPointOwner("/test.file", arc::kFirstAppUid);
+  ChangeMountPointOwner("/test.new", arc::kFirstAppUid);
+
+  // Before the rename, "/test.file" should exist but "/test.new" should not.
+  EXPECT_EQ(1u, handler_.entry_map_.count("/test.file"));
+  EXPECT_EQ(0u, handler_.entry_map_.count("/test.new"));
+
+  EXPECT_EQ(0, file_system_->rename("/test.file", "/test.new"));
+  EXPECT_EQ(0, errno);
+
+  // After the rename, "/test.file" should not exist but "/test.new" should.
+  EXPECT_EQ(0u, handler_.entry_map_.count("/test.file"));
+  EXPECT_EQ(1u, handler_.entry_map_.count("/test.new"));
+
+  // Rename it back to "/test.file" as it's referenced later.
+  ASSERT_EQ(0, file_system_->rename("/test.new", "/test.file"));
+
+  // If the old path does not exist, rename should set ENOENT to errno.
+  EXPECT_EQ(-1, file_system_->rename("/readonly.dir/old", "/readonly.dir/new"));
+  EXPECT_EQ(ENOENT, errno);
+
+  // If the old path and the parent path exist, rename should set EACCES to
+  // errno.
+  errno = 0;
+  handler_.AddEntry("/readonly.dir/old", kRegularFileMode);
+  EXPECT_EQ(-1, file_system_->rename("/readonly.dir/old", "/readonly.dir/new"));
+  EXPECT_EQ(EACCES, errno);
+
+  // If the parent of the old path does not exist, rename should set ENOENT
+  // to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->rename("/nonexistent.dir/old",
+                                     "/readonly.dir/new"));
+  EXPECT_EQ(ENOENT, errno);
+
+  // ENOTDIR is preferred to ENOENT. Here, ENOTDIR should be raised because
+  // "/test.file" in the old path is not a directory.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->rename("/test.file/old", "/readonly.dir/new"));
+  EXPECT_EQ(ENOTDIR, errno);
+
+  // Likewise, ENOTDIR should be raised because "/test.file" in the new path
+  // is not a directory.
+  // TODO(crbug.com/370788) However, this test does not pass because
+  // VirtualFileSystem::rename() does not handle this case correctly.
+  // errno = 0;
+  // EXPECT_EQ(-1, file_system_->rename("/readonly.dir/old", "/test.file/new"));
+  // EXPECT_EQ(ENOTDIR, errno);
+
+  // If |old_path| is empty, ENOENT should be returned.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->rename("", "/readonly.dir/new"));
+  EXPECT_EQ(ENOENT, errno);
+
+  // If |new_path| is empty, ENOENT should be returned too.
+  EXPECT_EQ(-1, file_system_->rename("/readonly.dir/old", ""));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestStat) {
+  handler_.AddEntry("/test.file", kRegularFileMode);
+  handler_.AddSymlink("/link.file", "/test.file");
+
+  struct stat st;
+  memset(&st, 1, sizeof(st));
+  errno = 0;
+  EXPECT_EQ(0, file_system_->stat("/test.file", &st));
+  EXPECT_EQ(0, errno);
+
+  memset(&st, 1, sizeof(st));
+  EXPECT_EQ(0, file_system_->stat("/link.file", &st));
+  EXPECT_NE(S_IFLNK, static_cast<int>(st.st_mode & S_IFMT));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestStatFS) {
+  struct statfs statfs = {};
+
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->statfs("/nonexistent.file", &statfs));
+  EXPECT_EQ(ENOENT, errno);
+
+  // "/" always exists in the file system.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->statfs("/", &statfs));
+  // Because we have 1 entry (the root).
+  EXPECT_EQ(1u, statfs.f_files);
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestSymlink) {
+  errno = 0;
+  EXPECT_EQ(0, file_system_->symlink("/test.file", "/link.file"));
+  EXPECT_EQ(0, errno);
+
+  handler_.AddEntry("/test.file", kRegularFileMode);
+  errno = 0;
+  // test.dir doesn't exist.
+  EXPECT_EQ(-1, file_system_->symlink("/test.file", "/test.dir/link1.file"));
+  EXPECT_EQ(ENOENT, errno);
+
+  // Access rights are ignored by root, so run tests below as normal user.
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  EXPECT_EQ(0, handler_.mkdir("/test.dir", 0555));
+
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->symlink("/test.file", "/test.dir/link2.file"));
+  EXPECT_EQ(EACCES, errno);
+
+  handler_.AddEntry("/test.dir/link3.file", kRegularFileMode);
+  errno = 0;
+  // Check that EEXIST has priority over EACCES.
+  EXPECT_EQ(-1, file_system_->symlink("/test.file", "/test.dir/link3.file"));
+  EXPECT_EQ(EEXIST, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestTruncate) {
+  handler_.AddEntry("/readonly.file", kRegularFileMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Make "/test.file" app-writable, to allow tuncate() on this path.
+  ChangeMountPointOwner("/test.file", arc::kFirstAppUid);
+
+  EXPECT_EQ(0, file_system_->truncate("/test.file", 0));
+  EXPECT_EQ(0, handler_.length_param_);
+
+  // Do the same with non-zero |length|.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->truncate("/test.file", 12345));
+  EXPECT_EQ(12345, handler_.length_param_);
+  EXPECT_EQ(0, errno);
+
+  // If the read-only file eixsts, truncate() should set EACCES to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->truncate("/readonly.file", 0777));
+  EXPECT_EQ(EACCES, errno);
+
+  // If the file does not exist, truncate should set ENOENT to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->truncate("/nonexistent.file", 0777));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestUnlink) {
+  handler_.AddEntry("/readonly.file", kRegularFileMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Make "/test.file" app-writable, to allow unlink() on this path.
+  ChangeMountPointOwner("/test.file", arc::kFirstAppUid);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->unlink("/test.file"));
+  EXPECT_EQ(0, errno);
+
+  // This time, unlink() should fail because /test.file is gone.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->unlink("/test.file"));
+  EXPECT_EQ(ENOENT, errno);
+
+  // If the read-only file exists, unlink should set EACCES to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->unlink("/readonly.file"));
+  EXPECT_EQ(EACCES, errno);
+
+  // If the file does not exist, unlink should set ENOENT to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->unlink("/nonexistent.file"));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestUTime) {
+  handler_.AddEntry("/readonly.file", kRegularFileMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Make "/test.file" app-writable, to allow utime() on this path.
+  ChangeMountPointOwner("/test.file", arc::kFirstAppUid);
+
+  struct utimbuf time;
+  time.actime = kTime;
+  time.modtime = kTime2;
+  // Expect the microseconds are 0.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->utime("/test.file", &time));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kTime, handler_.times_param_[0].tv_sec);
+  EXPECT_EQ(kTime2, handler_.times_param_[1].tv_sec);
+  EXPECT_EQ(0, handler_.times_param_[0].tv_usec);
+  EXPECT_EQ(0, handler_.times_param_[1].tv_usec);
+
+  // If the read-only file exists, utime should set EACCES to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->utime("/readonly.file", &time));
+  EXPECT_EQ(EACCES, errno);
+
+  // If the file does not exist, utime should set ENOENT to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->utime("/nonexistent.file", &time));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemPathTest, TestUTimes) {
+  handler_.AddEntry("/readonly.file", kRegularFileMode);
+  handler_.AddEntry("/test.file", kRegularFileMode);
+
+  ScopedUidSetter setter(arc::kFirstAppUid);
+  // Make "/test.file" app-writable, to allow utimes() on this path.
+  ChangeMountPointOwner("/test.file", arc::kFirstAppUid);
+
+  struct timeval times[2];
+  times[0].tv_sec = kTime;
+  times[0].tv_usec = 100;
+  times[1].tv_sec = kTime2;
+  times[1].tv_usec = 200;
+  errno = 0;
+  file_system_->utimes("/test.file", times);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(kTime, handler_.times_param_[0].tv_sec);
+  EXPECT_EQ(kTime2, handler_.times_param_[1].tv_sec);
+  EXPECT_EQ(100, handler_.times_param_[0].tv_usec);
+  EXPECT_EQ(200, handler_.times_param_[1].tv_usec);
+
+  // If the read-only file exists, utimes should set EACCES to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->utimes("/readonly.file", times));
+  EXPECT_EQ(EACCES, errno);
+
+  // If the file does not exist, utimes should set ENOENT to errno.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->utimes("/nonexistent.file", times));
+  EXPECT_EQ(ENOENT, errno);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/virtual_file_system_stream_test.cc b/src/posix_translation/virtual_file_system_stream_test.cc
new file mode 100644
index 0000000..28ae10b
--- /dev/null
+++ b/src/posix_translation/virtual_file_system_stream_test.cc
@@ -0,0 +1,655 @@
+// Copyright (c) 2014 The Chromium OS 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 <string.h>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gtest/gtest.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/test_util/virtual_file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+
+namespace posix_translation {
+
+namespace {
+
+// A stub/mock/fake-ish implementation of FileStream. Most functions simply
+// record input parameters for verification purpose, and return
+// constants. Some functions such as read() and write() have simple logic to
+// provide fake read-write behaviors using the internal buffer (content_).
+class TestFileStream : public FileStream {
+ public:
+  TestFileStream()
+      : FileStream(0, ""),
+        dirent_ptr_(NULL),
+        sockaddr_ptr_(NULL),
+        socklen_ptr_(NULL),
+        optval_ptr_(NULL),
+        optlen_ptr_(NULL),
+        backlog_value_(0),
+        flags_value_(0),
+        level_value_(0),
+        optname_value_(0),
+        request_value_(0),
+        whence_value_(0),
+        offset_value_(0),
+        dirent_count_value_(0),
+        socklen_value_(0) {
+  }
+
+  virtual const char* GetStreamType() const OVERRIDE { return "test"; }
+
+  virtual int accept(sockaddr* addr, socklen_t* addrlen) OVERRIDE {
+    sockaddr_ptr_ = addr;
+    socklen_ptr_ = addrlen;
+    return kFd;
+  }
+
+  virtual int bind(const sockaddr* addr, socklen_t addrlen) OVERRIDE {
+    sockaddr_ptr_ = addr;
+    socklen_value_ = addrlen;
+    return kFd;
+  }
+
+  virtual int connect(const sockaddr* addr, socklen_t addrlen) OVERRIDE {
+    sockaddr_ptr_ = addr;
+    socklen_value_ = addrlen;
+    return kFd;
+  }
+
+  virtual int getdents(dirent* buf, size_t count) OVERRIDE {
+    dirent_ptr_ = buf;
+    dirent_count_value_ = count;
+    return kFd;
+  }
+
+  virtual int getsockname(sockaddr* name, socklen_t* namelen) OVERRIDE {
+    sockaddr_ptr_ = name;
+    socklen_ptr_ = namelen;
+    return kFd;
+  }
+
+  virtual int getsockopt(int level, int optname, void* optval,
+                         socklen_t* optlen) OVERRIDE {
+    level_value_ = level;
+    optname_value_ = optname;
+    optval_ptr_ = optval;
+    optlen_ptr_ = optlen;
+    return kFd;
+  }
+
+  virtual int ioctl(int request, va_list ap) OVERRIDE {
+    request_value_ = request;
+    return kFd;
+  }
+
+  virtual int listen(int backlog) OVERRIDE {
+    backlog_value_ = backlog;
+    return kFd;
+  }
+
+  virtual off64_t lseek(off64_t offset, int whence) OVERRIDE {
+    offset_value_ = offset;
+    whence_value_ = whence;
+    return kFd;
+  }
+
+  virtual ssize_t pread(void* buf, size_t count, off64_t offset) OVERRIDE {
+    ALOG_ASSERT(offset < content_.size());
+    size_t length = std::min(count,
+                             static_cast<size_t>(content_.size() - offset));
+    memcpy(buf, content_.data() + offset, length);
+    return length;
+  }
+
+  virtual ssize_t PwriteImpl(
+      const void* buf, size_t count, off64_t offset) OVERRIDE {
+    ALOG_ASSERT(offset < content_.size());
+    content_.replace(offset, count, static_cast<const char*>(buf));
+    return count;
+  }
+
+  virtual ssize_t read(void* buf, size_t count) OVERRIDE {
+    size_t length = std::min(count, static_cast<size_t>(content_.size()));
+    memcpy(buf, content_.data(), length);
+    return length;
+  }
+
+  virtual ssize_t recv(void* buf, size_t count, int flags) OVERRIDE {
+    flags_value_ = flags;
+    return read(buf, count);
+  }
+
+  virtual ssize_t recvfrom(void* buf, size_t count, int flags,
+                           sockaddr* addr, socklen_t* addrlen) OVERRIDE {
+    flags_value_ = flags;
+    sockaddr_ptr_ = addr;
+    socklen_ptr_ = addrlen;
+    return read(buf, count);
+  }
+
+  virtual ssize_t send(const void* buf, size_t count, int flags) OVERRIDE {
+    flags_value_ = flags;
+    return write(buf, count);
+  }
+
+  virtual ssize_t sendto(const void* buf, size_t count, int flags,
+                         const sockaddr* dest_addr,
+                         socklen_t addrlen) OVERRIDE {
+    flags_value_ = flags;
+    sockaddr_ptr_ = dest_addr;
+    socklen_value_ = addrlen;
+    return write(buf, count);
+  }
+
+  virtual int setsockopt(int level, int optname, const void* optval,
+                         socklen_t optlen) OVERRIDE {
+    level_value_ = level;
+    optname_value_ = optname;
+    optval_ptr_ = optval;
+    socklen_value_ = optlen;
+    return 0;
+  }
+
+  virtual ssize_t write(const void* buf, size_t count) OVERRIDE {
+    content_.assign(static_cast<const char*>(buf), count);
+    return count;
+  }
+
+  // The file descriptor number that the class returns.
+  static const int kFd = 12345;
+
+  const dirent* dirent_ptr_;
+  const sockaddr* sockaddr_ptr_;
+  const socklen_t* socklen_ptr_;
+  const void* optval_ptr_;
+  const socklen_t* optlen_ptr_;
+  int backlog_value_;
+  int flags_value_;
+  int level_value_;
+  int optname_value_;
+  int request_value_;
+  int whence_value_;
+  off64_t offset_value_;
+  size_t dirent_count_value_;
+  socklen_t socklen_value_;
+
+  // The content used for read(), write(), etc.
+  std::string content_;
+};
+
+const int TestFileStream::kFd;
+
+}  // namespace
+
+// This class is used to test stream-related functions in VirtualFileSystem,
+// such as read(), write(), getdents(), etc.
+//
+// Most tests in the class just verify that the functions in TestFileStream
+// are called with expected parameters via VirtualFileSystem, and not called
+// when an invalid file descripter is passed.
+//
+// Tests for read(), write(), and friends verify that the buffer in
+// TestFileStream (content_) are modified as expected.
+class FileSystemStreamTest
+    : public FileSystemBackgroundTestCommon<FileSystemStreamTest> {
+ public:
+  DECLARE_BACKGROUND_TEST(TestAccept);
+  DECLARE_BACKGROUND_TEST(TestBind);
+  DECLARE_BACKGROUND_TEST(TestConnect);
+  DECLARE_BACKGROUND_TEST(TestGetDents);
+  DECLARE_BACKGROUND_TEST(TestGetSockName);
+  DECLARE_BACKGROUND_TEST(TestGetSockOpt);
+  DECLARE_BACKGROUND_TEST(TestIOCtl);
+  DECLARE_BACKGROUND_TEST(TestListen);
+  DECLARE_BACKGROUND_TEST(TestLSeek);
+  DECLARE_BACKGROUND_TEST(TestPRead);
+  DECLARE_BACKGROUND_TEST(TestPWrite);
+  DECLARE_BACKGROUND_TEST(TestRead);
+  DECLARE_BACKGROUND_TEST(TestReadV);
+  DECLARE_BACKGROUND_TEST(TestRecv);
+  DECLARE_BACKGROUND_TEST(TestRecvFrom);
+  DECLARE_BACKGROUND_TEST(TestSend);
+  DECLARE_BACKGROUND_TEST(TestSendTo);
+  DECLARE_BACKGROUND_TEST(TestSetSockOpt);
+  DECLARE_BACKGROUND_TEST(TestShutdown);
+  DECLARE_BACKGROUND_TEST(TestWrite);
+  DECLARE_BACKGROUND_TEST(TestWriteV);
+
+ protected:
+  typedef FileSystemBackgroundTestCommon<FileSystemStreamTest> CommonType;
+
+  virtual void SetUp() OVERRIDE {
+    CommonType::SetUp();
+
+    fd_ = GetFirstUnusedDescriptor();
+    EXPECT_GE(fd_, 0);
+    stream_ = new TestFileStream();
+    AddFileStream(fd_, stream_);
+  }
+
+  int fd_;
+  scoped_refptr<TestFileStream> stream_;
+};
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestAccept) {
+  sockaddr addr = {};
+  socklen_t addrlen = 1;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->accept(fd_, &addr, &addrlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(&addr, stream_->sockaddr_ptr_);
+  EXPECT_EQ(&addrlen, stream_->socklen_ptr_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->accept(0, &addr, &addrlen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestBind) {
+  sockaddr addr = {};
+  socklen_t addrlen = 1;
+
+  // Normal call.
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->bind(fd_, &addr, addrlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(&addr, stream_->sockaddr_ptr_);
+  EXPECT_EQ(addrlen, stream_->socklen_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->bind(0, &addr, addrlen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestConnect) {
+  sockaddr addr = {};
+  socklen_t addrlen = 1;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->connect(fd_, &addr, addrlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(&addr, stream_->sockaddr_ptr_);
+  EXPECT_EQ(addrlen, stream_->socklen_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->connect(0, &addr, addrlen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestGetDents) {
+  dirent buf;
+  size_t count = 123;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->getdents(fd_, &buf, count));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(&buf, stream_->dirent_ptr_);
+  EXPECT_EQ(count, stream_->dirent_count_value_);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->getdents(0, &buf, count), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestGetSockName) {
+  sockaddr name;
+  socklen_t namelen;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->getsockname(fd_, &name, &namelen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(&name, stream_->sockaddr_ptr_);
+  EXPECT_EQ(&namelen, stream_->socklen_ptr_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->getsockname(0, &name, &namelen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestGetSockOpt) {
+  int level = 123;
+  int optname = 456;
+  char optval[1024];
+  socklen_t optlen = 987;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->getsockopt(fd_, level, optname, &optval, &optlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(level, stream_->level_value_);
+  EXPECT_EQ(optname, stream_->optname_value_);
+  EXPECT_EQ(&optval, stream_->optval_ptr_);
+  EXPECT_EQ(&optlen, stream_->optlen_ptr_);
+
+  // Bad sockfd
+  EXPECT_ERROR(
+      file_system_->getsockopt(0, level, optname, &optval, &optlen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestIOCtl) {
+  const int request = 0x5301;  // CDROMPAUSE (takes an empty va_list)
+  va_list ap;
+  memset(&ap, 0, sizeof(ap));
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd, file_system_->ioctl(fd_, request, ap));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(request, stream_->request_value_);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->ioctl(0, request, ap), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestListen) {
+  const int backlog = 123;
+
+  // Normal call.
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd, file_system_->listen(fd_, backlog));
+  EXPECT_EQ(0, errno);
+
+  // Bad sockfd.
+  EXPECT_ERROR(file_system_->listen(0, backlog), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestLSeek) {
+  const off64_t offset = 123;
+  const int whence = 456;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(TestFileStream::kFd,
+            file_system_->lseek(fd_, offset, whence));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(offset, stream_->offset_value_);
+  EXPECT_EQ(whence, stream_->whence_value_);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->lseek(0, offset, whence), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestPRead) {
+  char buffer[1024];
+  const size_t count = sizeof(buffer);
+  const size_t offset = 3;
+
+  // Test that a portion of this content ("3456789") is read via pread().
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(7, file_system_->pread(fd_, buffer, count, offset));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("3456789", std::string(buffer, 7));
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->pread(0, buffer, count, offset), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestPWrite) {
+  const char buffer[] = "abcd";
+  const size_t count = sizeof(buffer) - 1;
+  const size_t offset = 7;
+
+  // Test that this content becomes "0123456789abcd" via pwrite().
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(4, file_system_->pwrite(fd_, buffer, count, offset));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("0123456abcd", stream_->content_);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->pwrite(0, buffer, count, offset), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestRead) {
+  char buf[1024];
+
+  // Test that a portion the content is read via read().
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(5, file_system_->read(fd_, buf, 5));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("01234", std::string(buf, 5));
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->read(0, buf, sizeof(buf)), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestReadV) {
+  char buf1[1] = {};
+  const size_t count1 = 0;
+  char buf2[2] = {};
+  const size_t count2 = sizeof(buf2);
+  char buf3[3] = {};
+  const size_t count3 = sizeof(buf3);
+
+  struct iovec iov[3] = {{buf1, count1}, {buf2, count2}, {buf3, count3}};
+
+  // Test that a portion of this content ("01") is read via the logic in
+  // file_stream.cc.
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(5, file_system_->readv(fd_, iov, sizeof(iov)/sizeof(iov[0])));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(std::string("\0", 1), std::string(buf1, sizeof(buf1)));
+  EXPECT_EQ(std::string("01", 2), std::string(buf2, sizeof(buf2)));
+  EXPECT_EQ(std::string("234", 3), std::string(buf3, sizeof(buf3)));
+
+  // Zero length iovec array
+  errno = 0;
+  EXPECT_EQ(0, file_system_->readv(fd_, iov, 0));
+  EXPECT_EQ(0, errno);
+
+  // NULL iov with 0-length.
+  EXPECT_EQ(0, file_system_->readv(fd_, NULL, 0));
+  EXPECT_EQ(0, errno);
+
+  // Illegal length iovec array
+  errno = 0;
+  EXPECT_ERROR(file_system_->readv(fd_, iov, -1), EINVAL);
+
+  // Illegal iov_len.
+  iov[0].iov_len = -1;
+  errno = 0;
+  EXPECT_ERROR(
+      file_system_->readv(fd_, iov, sizeof(iov)/sizeof(iov[0])), EINVAL);
+
+  // NULL iov_base with iov_len == 0.
+  iov[0].iov_len = 0;
+  iov[0].iov_base = NULL;
+  errno = 0;
+  EXPECT_EQ(0, file_system_->readv(fd_, iov, 1));
+  EXPECT_EQ(0, errno);
+
+  // EINVAL has priority to EFAULT in iov verification.
+  iov[1].iov_len = -1;
+  errno = 0;
+  EXPECT_ERROR(file_system_->readv(fd_, iov, 2), EINVAL);
+
+  // Bad fd
+  errno = 0;
+  EXPECT_ERROR(
+      file_system_->readv(0, iov, sizeof(iov)/sizeof(iov[0])), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestRecv) {
+  char buf[1024];
+  int flags = 456;
+
+  // Test that a portion of the content is read via recv().
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(5, file_system_->recv(fd_, buf, 5, flags));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("01234", std::string(buf, 5));
+  EXPECT_EQ(flags, stream_->flags_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->recv(0, buf, sizeof(buf), flags), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestRecvFrom) {
+  char buf[1024];
+  int flags = 456;
+  sockaddr addr = {};
+  socklen_t addrlen;
+
+  // Test that a portion of the content is read via recv().
+  stream_->content_ = "0123456789";
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(5, file_system_->recvfrom(fd_, buf, 5, flags, &addr, &addrlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ("01234", std::string(buf, 5));
+  EXPECT_EQ(flags, stream_->flags_value_);
+  EXPECT_EQ(&addr, stream_->sockaddr_ptr_);
+  EXPECT_EQ(&addrlen, stream_->socklen_ptr_);
+
+  // Bad sockfd
+  EXPECT_ERROR(
+      file_system_->recvfrom(0, buf, sizeof(buf), flags, &addr, &addrlen),
+      EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestSend) {
+  // Test that the content is written to the stream via send().
+  const char buf[] = "hello";
+  size_t count = sizeof(buf) - 1;
+  int flags = 456;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(static_cast<ssize_t>(count),
+            file_system_->send(fd_, buf, count, flags));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(buf, stream_->content_);
+  EXPECT_EQ(flags, stream_->flags_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(file_system_->send(0, buf, count, flags), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestSendTo) {
+  // Test that the content is written to the stream via send().
+  const char buf[] = "hello";
+  size_t count = sizeof(buf) - 1;
+  int flags = 456;
+  sockaddr dest_addr = {};
+  socklen_t addrlen = 654;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(static_cast<ssize_t>(count),
+            file_system_->sendto(fd_, buf, count, flags, &dest_addr,
+                                 addrlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(buf, stream_->content_);
+  EXPECT_EQ(flags, stream_->flags_value_);
+  EXPECT_EQ(&dest_addr, stream_->sockaddr_ptr_);
+  EXPECT_EQ(addrlen, stream_->socklen_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(
+      file_system_->sendto(0, buf, count, flags, &dest_addr, addrlen),
+      EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestSetSockOpt) {
+  const int level = 123;
+  const int optname = 456;
+  const void* optval = "abc";
+  socklen_t optlen = 789;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(0, file_system_->setsockopt(fd_, level, optname, optval, optlen));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(level, stream_->level_value_);
+  EXPECT_EQ(optname, stream_->optname_value_);
+  EXPECT_EQ(optval, stream_->optval_ptr_);
+  EXPECT_EQ(optlen, stream_->socklen_value_);
+
+  // Bad sockfd
+  EXPECT_ERROR(
+      file_system_->setsockopt(0, level, optname, optval, optlen), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestShutdown) {
+  const int how = 0;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(0, file_system_->shutdown(fd_, how));
+  EXPECT_EQ(0, errno);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->shutdown(0, how), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestWrite) {
+  // Test that the content is written to the stream via write().
+  const char buf[] = "hello";
+  const size_t count = sizeof(buf) - 1;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(static_cast<ssize_t>(count),
+            file_system_->write(fd_, buf, count));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(buf, stream_->content_);
+
+  // Bad fd
+  EXPECT_ERROR(file_system_->write(0, buf, count), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemStreamTest, TestWriteV) {
+  // Test that the content in the vector is written to the stream via the
+  // logic in file_stream.cc.
+  char buf1[1] = {'0'};
+  const size_t count1 = 0;
+  char buf2[2] = {'1', '2'};
+  const size_t count2 = sizeof(buf2);
+  char buf3[3] = {'3', '4', '5'};
+  const size_t count3 = sizeof(buf3);
+  char bufnul[1] = {};
+  const size_t count4 = 1;
+
+  struct iovec iov[4] = {
+    {buf1, count1}, {buf2, count2}, {buf3, count3}, {bufnul, count4}};
+
+  const size_t content_size = count1 + count2 + count3 + count4;
+
+  // Normal call
+  errno = 0;
+  EXPECT_EQ(static_cast<ssize_t>(content_size),
+            file_system_->writev(fd_, iov, sizeof(iov)/sizeof(iov[0])));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(std::string("12345\0", 6), stream_->content_);
+
+  // Zero length iovec array
+  errno = 0;
+  EXPECT_EQ(0, file_system_->writev(fd_, iov, 0));
+  EXPECT_EQ(0, errno);
+  // Bad length iovec array
+  EXPECT_ERROR(file_system_->writev(fd_, iov, -1), EINVAL);
+  // Bad fd
+  EXPECT_ERROR(
+      file_system_->writev(0, iov, sizeof(iov)/sizeof(iov[0])), EBADF);
+}
+
+}  // namespace posix_translation
diff --git a/src/posix_translation/virtual_file_system_test.cc b/src/posix_translation/virtual_file_system_test.cc
new file mode 100644
index 0000000..41904b4
--- /dev/null
+++ b/src/posix_translation/virtual_file_system_test.cc
@@ -0,0 +1,1108 @@
+// Copyright (c) 2013 The Chromium OS 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 <pthread.h>
+#include <stdlib.h>  // posix_memalign
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "posix_translation/address_util.h"
+#include "posix_translation/dir.h"
+#include "posix_translation/test_util/file_system_background_test_common.h"
+#include "posix_translation/test_util/virtual_file_system_test_common.h"
+#include "posix_translation/virtual_file_system.h"
+#include "ppapi_mocks/ppb_tcp_socket.h"
+#include "ppapi_mocks/ppb_udp_socket.h"
+
+using ::testing::NiceMock;
+
+namespace posix_translation {
+
+namespace {
+
+// Mock-ish implementation of FileStream. The behaviors of IsSelectReadReady()
+// etc. can be controled via the corresponding pre-set values
+// (ex. is_select_read_ready_). mmap() and munmap() record passed parameters
+// for verification purpose. mmap() also retuns a pre-set value (mapped_buf_),
+// and munmap() checks that too.
+class TestFileStream : public FileStream {
+ public:
+  TestFileStream()
+      : FileStream(0, ""),
+        is_select_read_ready_(false),
+        is_select_write_ready_(false),
+        is_select_exception_ready_(false),
+        flags_value_(0),
+        prot_value_(0),
+        offset_value_(-1),
+        length_value_(0),
+        mapped_buf_(NULL),
+        returns_same_address_for_multiple_mmaps_(false),
+        is_munmap_called_(false) {
+    EnableListenerSupport();
+  }
+
+  virtual bool ReturnsSameAddressForMultipleMmaps() const OVERRIDE {
+    return returns_same_address_for_multiple_mmaps_;
+  }
+
+  virtual ssize_t read(void*, size_t) OVERRIDE { return -1; }
+  virtual ssize_t write(const void*, size_t) OVERRIDE { return -1; }
+  virtual const char* GetStreamType() const OVERRIDE { return "test"; }
+
+  // Returns pre-set values.
+  virtual bool IsSelectReadReady() const OVERRIDE {
+    return is_select_read_ready_;
+  }
+  virtual bool IsSelectWriteReady() const OVERRIDE {
+    return is_select_write_ready_;
+  }
+  virtual bool IsSelectExceptionReady() const OVERRIDE {
+    return is_select_exception_ready_;
+  }
+  virtual int16_t GetPollEvents() const OVERRIDE {
+    return ((IsSelectReadReady() ? POLLIN : 0) |
+            (IsSelectWriteReady() ? POLLOUT : 0) |
+            (IsSelectExceptionReady() ? POLLERR : 0));
+  }
+
+  // If MAP_FIXED is specified, returns |addr|. Otherwise, returns mapped_buf_
+  // or fails if |addr| is non-NULL.
+  virtual void* mmap(
+      void* addr, size_t length, int prot, int flags, off_t offset) OVERRIDE {
+    if (!(flags & MAP_FIXED) && (addr != NULL))
+      return MAP_FAILED;
+
+    length_value_ = length;
+    prot_value_ = prot;
+    flags_value_ = flags;
+    offset_value_ = offset;
+    if (flags & MAP_FIXED)
+      mapped_buf_ = addr;
+    return mapped_buf_;
+  }
+
+  // Fails if |addr| does not match mapped_buf_.
+  virtual int munmap(void* addr, size_t length) OVERRIDE {
+    is_munmap_called_ = true;
+    if (addr != mapped_buf_)
+      return -1;
+
+    length_value_ = length;
+    return 0;
+  }
+
+
+  bool is_select_read_ready_;
+  bool is_select_write_ready_;
+  bool is_select_exception_ready_;
+  int flags_value_;
+  int prot_value_;
+  off64_t offset_value_;
+  size_t length_value_;
+  void* mapped_buf_;
+  bool returns_same_address_for_multiple_mmaps_;
+  bool is_munmap_called_;
+};
+
+// Stub-ish implementation of FileSystemHandler, that simply returns the
+// stream given to the constructor when open() is called.
+class TestFileSystemHandler : public FileSystemHandler {
+ public:
+  explicit TestFileSystemHandler(scoped_refptr<FileStream> stream)
+      : FileSystemHandler("TestFileSystemHandler"),
+        stream_(stream) {
+  }
+
+  virtual scoped_refptr<FileStream> open(int, const std::string&, int,
+                                         mode_t) OVERRIDE {
+    return stream_;
+  }
+  virtual Dir* OnDirectoryContentsNeeded(const std::string&) OVERRIDE {
+    return NULL;
+  }
+  virtual int stat(const std::string&, struct stat*) OVERRIDE { return -1; }
+  virtual int statfs(const std::string&, struct statfs*) OVERRIDE { return -1; }
+
+ private:
+  scoped_refptr<FileStream> stream_;
+};
+
+// A dummy file path used in tests.
+const char kTestPath[] = "/test.file";
+
+}  // namespace
+
+// This class is used to test event-related functions such as epoll_*(),
+// select(), poll(), select(), as well as some other miscellaneous functions
+// in VirtualFileSystem.
+//
+// See also other tests files for VirtualFileSystem:
+// - virtual_file_system_path_test.cc (path-related functions)
+// - virtual_file_system_stream_test.cc (stream-related functions)
+// - virtual_file_system_host_resolver_test.cc (host resolution)
+class FileSystemTest : public FileSystemBackgroundTestCommon<FileSystemTest> {
+ public:
+  // These tests all are fairly unique and there is no real common setup.
+  DECLARE_BACKGROUND_TEST(TestEPollBasic);
+  DECLARE_BACKGROUND_TEST(TestEPollClose);
+  DECLARE_BACKGROUND_TEST(TestEPollErrorHandling);
+  DECLARE_BACKGROUND_TEST(TestEPollSuccess);
+  DECLARE_BACKGROUND_TEST(TestEPollUnexpectedCalls);
+  DECLARE_BACKGROUND_TEST(TestGetNameInfo);
+  DECLARE_BACKGROUND_TEST(TestMmap);
+  DECLARE_BACKGROUND_TEST(TestInvalidMmap);
+  DECLARE_BACKGROUND_TEST(TestMmapWithMemoryFile);
+  DECLARE_BACKGROUND_TEST(TestAnonymousMmap);
+  DECLARE_BACKGROUND_TEST(TestNoMunmap);
+  DECLARE_BACKGROUND_TEST(TestPipe);
+  DECLARE_BACKGROUND_TEST(TestPoll);
+  DECLARE_BACKGROUND_TEST(TestSelect);
+  DECLARE_BACKGROUND_TEST(TestSocket);
+  DECLARE_BACKGROUND_TEST(TestSocketpair);
+
+ protected:
+  int GetOpenFD(int flags);
+
+  virtual void SetUp() OVERRIDE {
+    FileSystemBackgroundTestCommon<FileSystemTest>::SetUp();
+    factory_.GetMock(&ppb_tcpsocket_);
+    factory_.GetMock(&ppb_udpsocket_);
+  }
+
+ private:
+  // TCPSocket and UDPSocket are used in TestSocket implicitly.
+  // So, declare NiceMock here to inject them.
+  NiceMock<PPB_TCPSocket_Mock>* ppb_tcpsocket_;
+  NiceMock<PPB_UDPSocket_Mock>* ppb_udpsocket_;
+};
+
+TEST_F(FileSystemTest, ConstructPendingDestruct) {
+  // Just tests that the initialization that runs in SetUp() itself
+  // succeeds.
+}
+
+int FileSystemTest::GetOpenFD(int open_flags) {
+  // |stream| is deleted when it is closed or when VirtualFileSystem is
+  // destructed.
+  TestFileSystemHandler handler(new TestFileStream);
+  AddMountPoint(kTestPath, &handler);
+
+  int fd = file_system_->open(kTestPath, open_flags, 0);
+  EXPECT_GE(fd, 0) << "Open failed";
+  ClearMountPoints();
+  return fd;
+}
+
+TEST_F(FileSystemTest, TestGetInode) {
+  base::AutoLock lock(mutex());
+
+  ino_t inode = GetInode(kTestPath);
+  EXPECT_GT(inode, 0U);
+  ino_t another = GetInode("/some/other/path");
+  EXPECT_GT(another, 0U);
+  EXPECT_NE(inode, another);
+  EXPECT_EQ(inode, GetInode(kTestPath));
+  RemoveInode(kTestPath);
+  // The same inode should not be reused.
+  EXPECT_NE(inode, GetInode(kTestPath));
+}
+
+TEST_F(FileSystemTest, TestReassignInode) {
+  base::AutoLock lock(mutex());
+
+  ino_t inode = GetInode(kTestPath);
+  EXPECT_GT(inode, 0U);
+  ReassignInode(kTestPath, "/some/other/path");
+  EXPECT_EQ(inode, GetInode("/some/other/path"));
+  ino_t another = GetInode(kTestPath);
+  EXPECT_NE(inode, another);
+  EXPECT_GT(another, 0U);
+
+  // Test the case where the inode for the old path has not been generated yet.
+  inode = GetInode("/some/other/path/2");
+  EXPECT_GT(inode, 0U);
+  ReassignInode("/does/not/have/inode/yet", "/some/other/path/2");
+  another = GetInode("/some/other/path/2");
+  EXPECT_NE(inode, another);
+  EXPECT_GT(another, 0U);
+}
+
+TEST_F(FileSystemTest, TestGetFirstUnusedDescriptor) {
+  int fd = GetFirstUnusedDescriptor();
+  EXPECT_GE(fd, 0);
+  EXPECT_EQ(fd + 1, GetFirstUnusedDescriptor());
+  EXPECT_EQ(fd + 2, GetFirstUnusedDescriptor());
+
+  // Test if the smallest one available is returned.
+  RemoveFileStream(fd + 1);
+  EXPECT_EQ(fd + 1, GetFirstUnusedDescriptor());
+
+  RemoveFileStream(fd + 2);
+  EXPECT_EQ(fd + 2, GetFirstUnusedDescriptor());
+
+  RemoveFileStream(fd);
+  EXPECT_EQ(fd, GetFirstUnusedDescriptor());
+
+  RemoveFileStream(fd + 1);
+  RemoveFileStream(fd + 2);
+  EXPECT_EQ(fd + 1, GetFirstUnusedDescriptor());
+  EXPECT_EQ(fd + 2, GetFirstUnusedDescriptor());
+}
+
+TEST_F(FileSystemTest, TestNumOfDescriptorsAvailable) {
+  // 1023 descriptors should be available.
+  static const size_t kNum = kMaxFdForTesting - kMinFdForTesting + 1;
+  for (size_t i = 0; i < kNum; ++i) {
+    EXPECT_GE(GetFirstUnusedDescriptor(), 0) << i;
+  }
+  EXPECT_EQ(-1, GetFirstUnusedDescriptor());
+}
+
+TEST_F(FileSystemTest, TestTooManyDescriptors) {
+  bool failed = false;
+  for (size_t i = 0; i < FD_SETSIZE; ++i) {
+    if (GetFirstUnusedDescriptor() < 0) {
+      failed = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(failed);
+}
+
+TEST_F(FileSystemTest, TestGetCurrentWorkingDirectory) {
+  const char kRootDirPath[] = "/";
+
+  EXPECT_EQ(0, file_system_->chdir(kRootDirPath));
+
+  scoped_ptr<char, base::FreeDeleter> scoped_result;
+  scoped_result.reset(file_system_->getcwd(NULL, 0));
+  EXPECT_TRUE(scoped_result.get());
+  EXPECT_STREQ(kRootDirPath, scoped_result.get());
+
+  // Size should be 0 or correct value for NULL.
+  scoped_result.reset(file_system_->getcwd(NULL, 1));
+  EXPECT_FALSE(scoped_result.get());
+  EXPECT_EQ(ERANGE, errno);
+  scoped_result.reset(file_system_->getcwd(NULL, 100));
+  EXPECT_TRUE(scoped_result.get());
+
+  char buf[2];
+  char* result = file_system_->getcwd(buf, 2);
+  EXPECT_EQ(buf, result);
+  EXPECT_STREQ(kRootDirPath, result);
+
+  // Size argument can not be 0.
+  result = file_system_->getcwd(buf, 0);
+  EXPECT_EQ(EINVAL, errno);
+  EXPECT_EQ(NULL, result);
+
+  // Buffer size 1 is too small.
+  result = file_system_->getcwd(buf, 1);
+  EXPECT_EQ(ERANGE, errno);
+  EXPECT_EQ(NULL, result);
+
+  // Too large buffer size.
+  result = file_system_->getcwd(NULL, static_cast<size_t>(-1));
+  EXPECT_EQ(ENOMEM, errno);
+  EXPECT_EQ(NULL, result);
+}
+
+TEST_F(FileSystemTest, TestGetNormalizedPath) {
+  base::AutoLock lock(mutex());
+  const VirtualFileSystem::NormalizeOption kOption =
+      VirtualFileSystem::kDoNotResolveSymlinks;
+
+  EXPECT_EQ("/", GetNormalizedPath("/", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("//", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("///", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/./foo", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/././foo", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/./././foo", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("./path/to/./foo", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("././path/to/./foo", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/foo/.", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/foo/./.", kOption));
+  EXPECT_EQ("/path/to/foo", GetNormalizedPath("/path/to/foo/././.", kOption));
+  EXPECT_EQ("/path/to/foo",
+            GetNormalizedPath("//././path/to/./foo/./.", kOption));
+  EXPECT_EQ("/path/to/foo",
+            GetNormalizedPath("/././path/to/./foo/./.", kOption));
+  EXPECT_EQ("/.dot_file", GetNormalizedPath("/.dot_file", kOption));
+  EXPECT_EQ("/path/to/.dot_file",
+            GetNormalizedPath("/path/to/.dot_file", kOption));
+  EXPECT_EQ("/ends_with_dot.", GetNormalizedPath("/ends_with_dot.", kOption));
+  EXPECT_EQ("/ends_with_dot.", GetNormalizedPath("/ends_with_dot./", kOption));
+  EXPECT_EQ("/ends_with_dot./a",
+            GetNormalizedPath("/ends_with_dot./a", kOption));
+  EXPECT_EQ("/", GetNormalizedPath(".", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("./", kOption));
+  EXPECT_EQ("/", GetNormalizedPath(".//", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("./.", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("././", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("././/", kOption));
+  EXPECT_EQ("", GetNormalizedPath("", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("../", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("foo/../", kOption));
+  EXPECT_EQ("/bar", GetNormalizedPath("foo/../bar", kOption));
+
+  EXPECT_EQ("/twodots/something",
+            GetNormalizedPath("/twodots/with/../something", kOption));
+  EXPECT_EQ("/twodots/something",
+            GetNormalizedPath("/twodots/with/../something/", kOption));
+  EXPECT_EQ("/something",
+            GetNormalizedPath("/twodots/with/../../something", kOption));
+  EXPECT_EQ("/something",
+            GetNormalizedPath("/twodots/with/../../something/", kOption));
+  EXPECT_EQ("/something",
+            GetNormalizedPath("/twodots/with/../../../something", kOption));
+  EXPECT_EQ("/something",
+            GetNormalizedPath("/twodots/with/../../../something/", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/twodots/with/../..", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/twodots/with/../../", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/twodots/with/../../../", kOption));
+  EXPECT_EQ("/relative", GetNormalizedPath("twodots/../relative/", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/..", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/../", kOption));
+  EXPECT_EQ("/a", GetNormalizedPath("/../a", kOption));
+  EXPECT_EQ("/a", GetNormalizedPath("/../a/", kOption));
+  EXPECT_EQ("/", GetNormalizedPath("/../..", kOption));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestEPollBasic) {
+  int fd1;
+  int ep_fd1;
+  struct epoll_event ev1 = {};
+
+  // Simple create/close
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+
+  // Simple create, add file, close epoll, close file
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+  EXPECT_EQ(0, file_system_->close(fd1));
+
+  // Simple create, add file, close file, close epoll
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestEPollErrorHandling) {
+  int fd1, fd2;
+  int ep_fd1;
+  struct epoll_event ev1 = {};
+  struct epoll_event ev2 = {};
+
+  // Verify error handling of epoll_ctl.
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_DEL, fd1, &ev1), ENOENT);
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_MOD, fd1, &ev1), ENOENT);
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, ep_fd1, &ev1), EINVAL);
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1), EBADF);
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(fd1, EPOLL_CTL_ADD, ep_fd1, &ev1), EBADF);
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1), EEXIST);
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_DEL, fd1, &ev1));
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_DEL, fd1, &ev1), ENOENT);
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+
+  // Verify passing in bogus fd to as epoll fd
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  fd2 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd2, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd2;
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(fd1, EPOLL_CTL_ADD, fd2, &ev1), EINVAL);
+  EXPECT_ERROR(file_system_->epoll_wait(fd1, &ev2, 1, 0), EINVAL);
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(fd2));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestEPollUnexpectedCalls) {
+  int ep_fd = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd, 0);
+
+  char buf[1];
+  EXPECT_ERROR(file_system_->read(ep_fd, buf, 1), EINVAL);
+  EXPECT_ERROR(file_system_->write(ep_fd, buf, 1), EINVAL);
+
+  EXPECT_EQ(0, file_system_->close(ep_fd));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestEPollClose) {
+  int fd1, fd2, fd3;
+  int ep_fd1, ep_fd2;
+  struct epoll_event ev1 = {};
+  struct epoll_event ev2 = {};
+
+  // More complex testing of close ordering - close epolls first
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  ep_fd2 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd2, 0);
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  fd2 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd2, 0);
+  fd3 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd3, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd2, EPOLL_CTL_ADD, fd1, &ev1));
+  ev1.data.fd = fd2;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd2, &ev1));
+  ev1.data.fd = fd3;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd2, EPOLL_CTL_ADD, fd3, &ev1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd2));
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(fd2));
+  EXPECT_EQ(0, file_system_->close(fd3));
+
+  // More complex testing of close ordering - close one file first
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  ep_fd2 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd2, 0);
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd1, 0);
+  fd2 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd2, 0);
+  fd3 = GetOpenFD(O_RDWR | O_CREAT);
+  EXPECT_GE(fd3, 0);
+  ev1.events = EPOLLIN;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd2, EPOLL_CTL_ADD, fd1, &ev1));
+  ev1.data.fd = fd2;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd2, &ev1));
+  ev1.data.fd = fd3;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd2, EPOLL_CTL_ADD, fd3, &ev1));
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_DEL, fd1, &ev1), EBADF);
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_MOD, fd1, &ev1), EBADF);
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd2, EPOLL_CTL_DEL, fd1, &ev1), EBADF);
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd2));
+  EXPECT_ERROR(
+      file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_MOD, fd2, &ev1), EBADF);
+  EXPECT_EQ(0, file_system_->close(fd2));
+  EXPECT_EQ(0, file_system_->close(fd3));
+
+  // Simple create, wait, close
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  EXPECT_EQ(0, file_system_->epoll_wait(ep_fd1, &ev2, 1, 0));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+
+  // Simple create, wait, close
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  EXPECT_EQ(0, file_system_->epoll_wait(ep_fd1, &ev2, 1, 50));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestEPollSuccess) {
+  int fd1;
+  int ep_fd1;
+  struct epoll_event ev1 = {};
+  struct epoll_event ev2 = {};
+
+  // Test a successful epoll
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  scoped_refptr<TestFileStream> stream1 =
+      static_cast<TestFileStream*>(GetStream(fd1).get());
+  stream1->is_select_read_ready_ = true;
+  stream1->is_select_write_ready_ = true;
+
+  EXPECT_GE(fd1, 0);
+  ev1.events = EPOLLIN | EPOLLOUT;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  memset(&ev2, 0xA5, sizeof(ev2));
+  EXPECT_EQ(1, file_system_->epoll_wait(ep_fd1, &ev2, 1, 50));
+  EXPECT_EQ(ev2.events, (uint32_t)(EPOLLIN | EPOLLOUT));
+  EXPECT_EQ(ev2.data.fd, fd1);
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+
+  // Test successful epoll after modding event data
+  ep_fd1 = file_system_->epoll_create1(0);
+  EXPECT_GE(ep_fd1, 0);
+  fd1 = GetOpenFD(O_RDWR | O_CREAT);
+  stream1 = static_cast<TestFileStream*>(GetStream(fd1).get());
+  stream1->is_select_read_ready_ = true;
+  stream1->is_select_write_ready_ = true;
+
+  EXPECT_GE(fd1, 0);
+  ev1.events = EPOLLIN | EPOLLOUT;
+  ev1.data.fd = fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_ADD, fd1, &ev1));
+  memset(&ev2, 0xA5, sizeof(ev2));
+  EXPECT_EQ(1, file_system_->epoll_wait(ep_fd1, &ev2, 1, 50));
+  EXPECT_EQ(ev2.events, (uint32_t)(EPOLLIN | EPOLLOUT));
+  EXPECT_EQ(ev2.data.fd, fd1);
+  ev1.events = EPOLLIN | EPOLLOUT;
+  ev1.data.fd = -fd1;
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_MOD, fd1, &ev1));
+  memset(&ev2, 0x5A, sizeof(ev2));
+  EXPECT_EQ(1, file_system_->epoll_wait(ep_fd1, &ev2, 1, 50));
+  EXPECT_EQ(ev2.events, (uint32_t)(EPOLLIN | EPOLLOUT));
+  EXPECT_EQ(ev2.data.fd, -fd1);
+  EXPECT_EQ(0, file_system_->epoll_ctl(ep_fd1, EPOLL_CTL_DEL, fd1, &ev1));
+  EXPECT_EQ(0, file_system_->epoll_wait(ep_fd1, &ev2, 1, 0));
+  EXPECT_EQ(0, file_system_->close(fd1));
+  EXPECT_EQ(0, file_system_->close(ep_fd1));
+
+  // Test double-close
+  EXPECT_ERROR(file_system_->close(ep_fd1), EBADF);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestPipe) {
+  int pipefd[2];
+  int dupfd;
+  char writebuffer[100];
+  char readbuffer[100];
+  EXPECT_EQ(0, file_system_->pipe2(pipefd, O_NONBLOCK));
+  EXPECT_GE(pipefd[0], 0);
+  EXPECT_GE(pipefd[1], 0);
+  dupfd = file_system_->dup(pipefd[1]);
+  EXPECT_GE(dupfd, 0);
+  memset(writebuffer, 0x55, sizeof(writebuffer));
+  memset(readbuffer, 0xAA, sizeof(readbuffer));
+  EXPECT_ERROR(file_system_->write(pipefd[0], writebuffer, 100), EBADF);
+  EXPECT_ERROR(file_system_->read(pipefd[1], readbuffer, 100), EBADF);
+  EXPECT_ERROR(file_system_->read(pipefd[0], readbuffer, 100), EAGAIN);
+  EXPECT_EQ(file_system_->write(pipefd[1], writebuffer, 100), 100);
+  EXPECT_EQ(file_system_->read(pipefd[0], readbuffer, 100), 100);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, sizeof(writebuffer)), 0);
+  for (size_t iii = 0; iii < sizeof(writebuffer); iii++)
+    writebuffer[iii] = static_cast<char>(iii);
+  EXPECT_EQ(file_system_->write(pipefd[1], writebuffer, 50), 50);
+  EXPECT_EQ(file_system_->read(pipefd[0], readbuffer, 100), 50);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, 50), 0);
+
+  EXPECT_EQ(file_system_->write(pipefd[1], writebuffer, 100), 100);
+  EXPECT_EQ(file_system_->read(pipefd[0], readbuffer, 50), 50);
+  EXPECT_EQ(file_system_->read(pipefd[0], readbuffer, 50), 50);
+  EXPECT_EQ(memcmp(writebuffer+50, readbuffer, 50), 0);
+
+  // Close the original write end of the pipe, but a duplicated end exists.
+  EXPECT_EQ(0, file_system_->close(pipefd[1]));
+
+  memset(readbuffer, 0xAA, sizeof(readbuffer));
+  EXPECT_EQ(file_system_->write(dupfd, writebuffer, 50), 50);
+  EXPECT_EQ(file_system_->read(pipefd[0], readbuffer, 50), 50);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, 50), 0);
+
+  EXPECT_EQ(0, file_system_->close(pipefd[0]));
+  EXPECT_EQ(0, file_system_->close(dupfd));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestSocketpair) {
+  int sockets[2];
+  char writebuffer[5000];
+  char readbuffer[5000];
+  int iii;
+  EXPECT_EQ(0, file_system_->socketpair(AF_UNIX, SOCK_STREAM, 0, sockets));
+  EXPECT_GE(sockets[0], 0);
+  EXPECT_GE(sockets[1], 0);
+
+  for (iii = 0; iii < 5000; iii++ )
+    writebuffer[iii] = static_cast<char>(iii * 3);
+  EXPECT_EQ(file_system_->write(sockets[0], writebuffer, 5000), 5000);
+  EXPECT_EQ(file_system_->read(sockets[1], readbuffer, 3000), 3000);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, 3000), 0);
+  EXPECT_EQ(file_system_->read(sockets[1], readbuffer, 3000), 2000);
+  EXPECT_EQ(memcmp(writebuffer + 3000, readbuffer, 2000), 0);
+  for (iii = 0; iii < 5000; iii++)
+    writebuffer[iii] = writebuffer[iii] ^ static_cast<char>(iii*5);
+  EXPECT_EQ(file_system_->write(sockets[0], writebuffer, 5000), 5000);
+  EXPECT_EQ(file_system_->read(sockets[1], readbuffer, 5000), 5000);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, 5000), 0);
+
+  memset(readbuffer, 0, sizeof(readbuffer));
+  EXPECT_EQ(file_system_->write(sockets[0], writebuffer, 100), 100);
+  EXPECT_EQ(file_system_->read(sockets[1], readbuffer, 5000), 100);
+  EXPECT_EQ(memcmp(writebuffer, readbuffer, 100), 0);
+  EXPECT_EQ(readbuffer[100], 0);
+
+  EXPECT_EQ(0, file_system_->close(sockets[0]));
+  EXPECT_EQ(0, file_system_->close(sockets[1]));
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestPoll) {
+  struct pollfd fds[3] = {};
+
+  fds[0].fd = GetFirstUnusedDescriptor();
+  fds[0].events = POLLIN | POLLPRI | POLLOUT | POLLRDHUP;
+
+  fds[1].fd = GetFirstUnusedDescriptor();
+  fds[1].events = POLLIN | POLLPRI | POLLOUT | POLLRDHUP;
+
+  fds[2].fd = GetFirstUnusedDescriptor();
+  fds[2].events = POLLIN | POLLPRI | POLLOUT | POLLRDHUP;
+
+  scoped_refptr<TestFileStream> stream0 = new TestFileStream;
+  AddFileStream(fds[0].fd, stream0);
+
+  scoped_refptr<TestFileStream> stream1 = new TestFileStream;
+  AddFileStream(fds[1].fd, stream1);
+
+  stream0->is_select_read_ready_ = false;
+  stream0->is_select_write_ready_ = false;
+  stream0->is_select_exception_ready_ = false;
+
+  stream1->is_select_read_ready_ = true;
+  stream1->is_select_write_ready_ = true;
+  stream1->is_select_exception_ready_ = true;
+
+  // Check a non-blocking call with one non-signaling fd, one completely
+  // signaling fd, and one unknown fd.
+  errno = 0;
+  EXPECT_EQ(2, file_system_->poll(fds, sizeof(fds)/sizeof(fds[0]), 0));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, fds[0].revents);
+  EXPECT_EQ(POLLIN | POLLOUT | POLLERR, fds[1].revents);
+  EXPECT_EQ(POLLNVAL, fds[2].revents);
+
+  scoped_refptr<TestFileStream> stream2 = new TestFileStream;
+  AddFileStream(fds[2].fd, stream2);
+
+  stream0->is_select_read_ready_ = true;
+  stream0->is_select_write_ready_ = false;
+  stream0->is_select_exception_ready_ = false;
+
+  stream1->is_select_read_ready_ = false;
+  stream1->is_select_write_ready_ = true;
+  stream1->is_select_exception_ready_ = false;
+
+  stream2->is_select_read_ready_ = false;
+  stream2->is_select_write_ready_ = false;
+  stream2->is_select_exception_ready_ = true;
+
+  // Check a very-short blocking timeout blocking call where the fds are
+  // each distinctly signalling.
+  errno = 0;
+  EXPECT_EQ(3, file_system_->poll(fds, sizeof(fds)/sizeof(fds[0]), 1));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(POLLIN, fds[0].revents);
+  EXPECT_EQ(POLLOUT, fds[1].revents);
+  EXPECT_EQ(POLLERR, fds[2].revents);
+
+  stream0->is_select_read_ready_ = false;
+  stream0->is_select_write_ready_ = false;
+  stream0->is_select_exception_ready_ = false;
+
+  stream1->is_select_read_ready_ = false;
+  stream1->is_select_write_ready_ = false;
+  stream1->is_select_exception_ready_ = false;
+
+  stream2->is_select_read_ready_ = false;
+  stream2->is_select_write_ready_ = false;
+  stream2->is_select_exception_ready_ = false;
+
+  // Check a non-blocking call where all fds are non-signaling.
+  errno = 0;
+  EXPECT_EQ(0, file_system_->poll(fds, sizeof(fds)/sizeof(fds[0]), 0));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(0, fds[0].revents);
+  EXPECT_EQ(0, fds[1].revents);
+  EXPECT_EQ(0, fds[2].revents);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestSelect) {
+  fd_set readfds;
+  fd_set writefds;
+  fd_set exceptfds;
+  struct timeval timeout = {};
+
+  int fd0 = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream0 = new TestFileStream;
+  AddFileStream(fd0, stream0);
+
+  int fd1 = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream1 = new TestFileStream;
+  AddFileStream(fd1, stream1);
+
+  int fd2 = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream2 = new TestFileStream;
+  AddFileStream(fd2, stream2);
+
+  int fd3 = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream3 = new TestFileStream;
+  AddFileStream(fd3, stream3);
+
+  int nfds = 1 + std::max(std::max(fd0, fd1), std::max(fd2, fd3));
+
+  FD_ZERO(&readfds);
+  FD_ZERO(&writefds);
+  FD_ZERO(&exceptfds);
+  FD_SET(fd0, &readfds);
+  FD_SET(fd1, &readfds);
+  FD_SET(fd2, &writefds);
+  FD_SET(fd3, &exceptfds);
+
+  // Expect fd0 will never be ready, but fd1-fd3 to be immediately ready.
+  stream0->is_select_read_ready_ = false;
+  stream1->is_select_read_ready_ = true;
+  stream2->is_select_write_ready_ = true;
+  stream3->is_select_exception_ready_ = true;
+
+  // Issue a non-blocking call with the four fds.
+  errno = 0;
+  EXPECT_EQ(3, file_system_->select(nfds, &readfds, &writefds, &exceptfds,
+                                    &timeout));
+  EXPECT_EQ(0, errno);
+  EXPECT_FALSE(FD_ISSET(fd0, &readfds));
+  EXPECT_TRUE(FD_ISSET(fd1, &readfds));
+  EXPECT_TRUE(FD_ISSET(fd2, &writefds));
+  EXPECT_TRUE(FD_ISSET(fd3, &exceptfds));
+
+  // Issue a super-short blocking call with no fds.
+  memset(&timeout, 0, sizeof(timeout));
+  timeout.tv_usec = 1;
+  EXPECT_EQ(0, file_system_->select(0, NULL, NULL, NULL, &timeout));
+  // |timeout| should be updated.
+  EXPECT_EQ(0L, timeout.tv_sec);
+  EXPECT_EQ(0L, timeout.tv_usec);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestMmap) {
+  size_t length = util::GetPageSize();
+  // Use non-default values, to verify that TestFileStream::mmap() is called
+  // with these values, via VirtualFileSystem::mmap().
+  const int prot = 123;
+  const int flags = 456 & ~MAP_FIXED;
+  const off64_t offset = 0;
+
+  const int fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  AddFileStream(fd, stream);
+
+  void* mapped_buf;
+  ASSERT_EQ(0, posix_memalign(&mapped_buf, util::GetPageSize(), length));
+  stream->mapped_buf_ = mapped_buf;
+
+  errno = 0;
+  void* retval = file_system_->mmap(NULL, length, prot, flags, fd, offset);
+  EXPECT_EQ(mapped_buf, retval);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(length, stream->length_value_);
+  EXPECT_EQ(prot, stream->prot_value_);
+  EXPECT_EQ(flags, stream->flags_value_);
+  EXPECT_EQ(offset, stream->offset_value_);
+
+  errno = 0;
+  EXPECT_EQ(0, file_system_->munmap(retval, length));
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(length, stream->length_value_);
+
+  free(mapped_buf);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestInvalidMmap) {
+  const int fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  AddFileStream(fd, stream);
+
+  const uintptr_t aligned_addr = util::GetPageSize();
+  const uintptr_t unaligned_addr = aligned_addr + 1;
+
+  // Test mmap with unaligned address.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, file_system_->mmap(
+      reinterpret_cast<void*>(unaligned_addr), 1, PROT_WRITE,
+      MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test mprotect with unaligned address.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->mprotect(
+      reinterpret_cast<void*>(unaligned_addr), 1, PROT_READ));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test munmap with unaligned address.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->munmap(
+      reinterpret_cast<void*>(unaligned_addr), 1));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test zero-length mmap.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, file_system_->mmap(
+      NULL, 0, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Note: zero-length mprotect is legal.
+
+  // Test zero-length munmap.
+  errno = 0;
+  EXPECT_EQ(-1, file_system_->munmap(
+      reinterpret_cast<void*>(aligned_addr), 0));
+  EXPECT_EQ(EINVAL, errno);
+
+  // Test mmap with unaligned offset.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, file_system_->mmap(
+      NULL, 1, PROT_READ, MAP_PRIVATE, fd, 1));
+  EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestMmapWithMemoryFile) {
+  const size_t length = util::GetPageSize();
+  const int prot = PROT_READ | PROT_WRITE;
+  const int flags = MAP_PRIVATE;
+  const off64_t offset = 0;
+
+  const int fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  AddFileStream(fd, stream);
+
+  // Mimic MemoryFile which returns the same address for multiple mmap() calls.
+  // It should work though the behavior is not posix compliant.
+  stream->returns_same_address_for_multiple_mmaps_ = true;
+
+  void* mapped_buf;
+  ASSERT_EQ(0, posix_memalign(&mapped_buf, util::GetPageSize(), length));
+  stream->mapped_buf_ = mapped_buf;
+
+  // Note that the reference count for a region bound to |fd| becomes 3.
+  EXPECT_EQ(mapped_buf,
+            file_system_->mmap(NULL, length, prot, flags, fd, offset));
+  EXPECT_EQ(mapped_buf,
+            file_system_->mmap(NULL, length, prot, flags, fd, offset));
+  EXPECT_EQ(mapped_buf,
+            file_system_->mmap(NULL, length, prot, flags, fd, offset));
+
+  // It should not be replaced with another MemoryFileStream.
+  // In that case, how to handle the reference count is not trivial.
+  SetMemoryMapAbortEnableFlags(false);
+  // Following failure decreases the reference count to 2 internally.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, file_system_->mmap(
+      mapped_buf, length, prot, flags | MAP_FIXED, fd, offset));
+  EXPECT_EQ(ENODEV, errno);
+  SetMemoryMapAbortEnableFlags(true);
+
+  // It should not be replaced with another kind of FileStream, too.
+  const int another_fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> another_stream = new TestFileStream;
+  AddFileStream(another_fd, another_stream);
+  SetMemoryMapAbortEnableFlags(false);
+  // Following failure decreases the reference count to 1 internally.
+  errno = 0;
+  EXPECT_EQ(MAP_FAILED, file_system_->mmap(
+      mapped_buf, length, prot, flags | MAP_FIXED, another_fd, offset));
+  EXPECT_EQ(ENODEV, errno);
+  SetMemoryMapAbortEnableFlags(true);
+  EXPECT_EQ(0, file_system_->munmap(mapped_buf, length));
+
+  // On the other hands, MemoryFile with single reference can be replaced with
+  // another MemoryFileStream.
+  EXPECT_EQ(mapped_buf, file_system_->mmap(
+      NULL, length, prot, flags, fd, offset));
+  EXPECT_EQ(mapped_buf, file_system_->mmap(
+      mapped_buf, length, prot, flags | MAP_FIXED, fd, offset));
+  EXPECT_EQ(0, file_system_->munmap(mapped_buf, length));
+
+  free(mapped_buf);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestAnonymousMmap) {
+  const size_t length = util::GetPageSize();
+  const size_t doubled_length = length * 2;
+  const int prot = PROT_READ;
+  const int anonymous_fd = -1;
+  const off64_t offset = 0;
+
+  // Call mmap() with MAP_ANONYMOUS. It should ignore |fd| and not call
+  // underlaying mmap() implementation, but real mmap().
+  errno = 0;
+  char* anonymous_addr = static_cast<char*>(file_system_->mmap(
+      NULL, doubled_length, prot, MAP_ANONYMOUS | MAP_PRIVATE, anonymous_fd,
+      offset));
+  EXPECT_NE(MAP_FAILED, anonymous_addr);
+  EXPECT_NE(static_cast<char*>(NULL), anonymous_addr);
+  EXPECT_EQ(0, errno);
+
+  // Call mmap() with MAP_FIXED and |fd|. The address is the same with
+  // previously allocated anonymous region.
+  const int fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  AddFileStream(fd, stream);
+  errno = 0;
+  void* retval = file_system_->mmap(
+      anonymous_addr, doubled_length, prot, MAP_FIXED | MAP_PRIVATE, fd,
+      offset);
+  EXPECT_EQ(anonymous_addr, retval);
+  EXPECT_EQ(0, errno);
+
+  // Call mmap() with MAP_FIXED and MAP_ANONYMOUS. It should not call underlying
+  // munmap() implementation to release previously allocated memory region.
+  errno = 0;
+  retval = file_system_->mmap(
+      anonymous_addr, doubled_length, prot,
+      MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, anonymous_fd, offset);
+  EXPECT_EQ(anonymous_addr, retval);
+  EXPECT_FALSE(stream->is_munmap_called_);
+  EXPECT_EQ(0, errno);
+
+  // Confirm that mprotect is supported. Note that zero-length mprotect should
+  // return 0.
+  EXPECT_EQ(0, file_system_->mprotect(retval, 0, PROT_READ));
+  ASSERT_EQ(0, file_system_->mprotect(retval, 1, PROT_WRITE));
+  static_cast<char*>(retval)[0] = 'X';  // confirm this does not crash.
+  ASSERT_EQ(0, file_system_->mprotect(retval, doubled_length, prot));
+
+  // munmap() can be called partially.
+  EXPECT_EQ(0, file_system_->munmap(anonymous_addr, length));
+  char* latter_half_addr = anonymous_addr + length;
+  EXPECT_EQ(0, file_system_->munmap(latter_half_addr, length));
+  EXPECT_EQ(0, errno);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestNoMunmap) {
+  size_t length = util::GetPageSize();
+  // Use non-default values, to verify that TestFileStream::munmap() is
+  // called with these values, via VirtualFileSystem::munmap().
+  int prot = 123;
+  int flags = 456;
+  off64_t offset = 0;
+
+  int fd = GetFirstUnusedDescriptor();
+  scoped_refptr<TestFileStream> stream = new TestFileStream;
+  AddFileStream(fd, stream);
+
+  void* mapped_buf;
+  ASSERT_EQ(0, posix_memalign(&mapped_buf, util::GetPageSize(), length));
+  stream->mapped_buf_ = mapped_buf;
+
+  errno = 0;
+  void* retval = file_system_->mmap(NULL, length, prot, flags, fd, offset);
+  EXPECT_EQ(mapped_buf, retval);
+  EXPECT_EQ(0, errno);
+  EXPECT_EQ(length, stream->length_value_);
+  EXPECT_EQ(prot, stream->prot_value_);
+  EXPECT_EQ(flags, stream->flags_value_);
+  EXPECT_EQ(offset, stream->offset_value_);
+
+  file_system_->close(fd);
+
+  free(mapped_buf);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestGetNameInfo) {
+  sockaddr_in sin = {};
+  char host[1024] = {};
+  char serv[1024] = {};
+  const int flags = 0;
+  sin.sin_family = AF_INET;
+  sin.sin_port = htons(80);
+  sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+  int retval;
+  // Normal, just hostname
+  retval = file_system_->getnameinfo(reinterpret_cast<sockaddr*>(&sin),
+                                     sizeof(sin), host, sizeof(host),
+                                     NULL, 0, 0);
+  EXPECT_EQ(0, retval);
+  EXPECT_STREQ("127.0.0.1", host);
+
+  // Normal, just servname
+  retval = file_system_->getnameinfo(reinterpret_cast<sockaddr*>(&sin),
+                                     sizeof(sin), NULL, 0, serv,
+                                     sizeof(serv), 0);
+  EXPECT_EQ(0, retval);
+  EXPECT_STREQ("80", serv);
+
+  // Invalid request -- either hostname or servname must be requested.
+  retval = file_system_->getnameinfo(reinterpret_cast<sockaddr*>(&sin),
+                                     sizeof(sin), NULL, 0, NULL, 0, 0);
+  EXPECT_EQ(EAI_NONAME, retval);
+
+  // Unsupported
+  sockaddr_in unsupported = {};
+  unsupported.sin_family = AF_BRIDGE;
+  retval = file_system_->getnameinfo(reinterpret_cast<sockaddr*>(&unsupported),
+                                     sizeof(unsupported), host, sizeof(host),
+                                     serv, sizeof(serv), flags);
+  EXPECT_EQ(EAI_FAMILY, retval);
+}
+
+TEST_BACKGROUND_F(FileSystemTest, TestSocket) {
+  int fd = 0;
+
+  errno = -1;
+  fd = file_system_->socket(AF_INET, SOCK_DGRAM, PF_INET);
+  EXPECT_NE(-1, fd);
+  EXPECT_EQ(-1, errno);
+  EXPECT_STREQ("udp", GetStream(fd)->GetStreamType());
+  EXPECT_EQ(0, file_system_->close(fd));
+
+  errno = -1;
+  fd = file_system_->socket(AF_INET6, SOCK_STREAM, PF_INET6);
+  EXPECT_NE(-1, fd);
+  EXPECT_EQ(-1, errno);
+  EXPECT_STREQ("tcp", GetStream(fd)->GetStreamType());
+  EXPECT_EQ(0, file_system_->close(fd));
+  errno = -1;
+
+  EXPECT_ERROR(file_system_->socket(AF_INET, SOCK_RAW, PF_INET), EAFNOSUPPORT);
+  EXPECT_ERROR(file_system_->socket(AF_INET, SOCK_RDM, PF_INET), EAFNOSUPPORT);
+  EXPECT_ERROR(
+      file_system_->socket(AF_INET, SOCK_SEQPACKET, PF_INET), EAFNOSUPPORT);
+  EXPECT_ERROR(
+      file_system_->socket(AF_INET, SOCK_PACKET, PF_INET), EAFNOSUPPORT);
+  EXPECT_ERROR(
+      file_system_->socket(AF_BRIDGE, SOCK_DGRAM, PF_BRIDGE), EAFNOSUPPORT);
+}
+
+}  // namespace posix_translation
diff --git a/src/ppapi_mocks/background_test.cc b/src/ppapi_mocks/background_test.cc
new file mode 100644
index 0000000..10ba275
--- /dev/null
+++ b/src/ppapi_mocks/background_test.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 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 "ppapi_mocks/background_test.h"
+
+CompletionCallbackExecutor::CompletionCallbackExecutor(
+    BackgroundThread* bg,
+    int32_t final_result)
+    : bg_(bg), interim_result_(PP_OK_COMPLETIONPENDING),
+      final_result_(final_result) {}
+
+int32_t CompletionCallbackExecutor::ExecuteOnMainThread(
+    struct PP_CompletionCallback cb) {
+  // Return the final result now if |cb| is pp::BlockUntilComplete().
+  if (!cb.func)
+    return final_result_;
+  bg_->CallOnMainThread(0, cb, final_result_);
+  return interim_result_;
+}
+
+int32_t CompletionCallbackExecutor::final_result() const {
+  return final_result_;
+}
diff --git a/src/ppapi_mocks/background_test.h b/src/ppapi_mocks/background_test.h
new file mode 100644
index 0000000..180f62f
--- /dev/null
+++ b/src/ppapi_mocks/background_test.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+//
+// Utilities for background thread testing.
+
+#ifndef PPAPI_MOCKS_BACKGROUND_TEST_H_
+#define PPAPI_MOCKS_BACKGROUND_TEST_H_
+
+#include "base/basictypes.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "ppapi_mocks/background_thread.h"
+
+// The interface that needs to be implemented by the test class that runs
+// background tests with TEST_BACKGROUND_F.
+// TODO(crbug.com/234097): Merge BackgroundTest and BackgroundThread.
+template <typename T>
+class BackgroundTest {
+ public:
+  virtual ~BackgroundTest() {}
+  virtual BackgroundThread* GetBackgroundThread() = 0;
+  virtual pp::CompletionCallbackFactory<T>* GetCompletionCallbackFactory() = 0;
+};
+
+// A class for executing a PP_CompletionCallback function on the main thread.
+class CompletionCallbackExecutor {
+ public:
+  CompletionCallbackExecutor(BackgroundThread* bg, int32_t final_result);
+  int32_t ExecuteOnMainThread(struct PP_CompletionCallback cb);
+  int32_t final_result() const;
+
+ private:
+  BackgroundThread* bg_;
+  const int32_t interim_result_;
+  const int32_t final_result_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompletionCallbackExecutor);
+};
+
+#define DECLARE_BACKGROUND_TEST(_a) void _a(int32_t)
+
+#define TEST_BACKGROUND_F(_fixture, _test)                                 \
+  TEST_F(_fixture, _test) {                                                \
+    GetBackgroundThread()->Start(                                          \
+        GetCompletionCallbackFactory()->NewCallback(&_fixture::_test), 0); \
+    GetBackgroundThread()->RunMainThreadLoop();                            \
+  }                                                                        \
+  void _fixture::_test(int32_t)
+
+#endif  // PPAPI_MOCKS_BACKGROUND_TEST_H_
diff --git a/src/ppapi_mocks/background_thread.cc b/src/ppapi_mocks/background_thread.cc
new file mode 100644
index 0000000..e08d2e0
--- /dev/null
+++ b/src/ppapi_mocks/background_thread.cc
@@ -0,0 +1,81 @@
+// Copyright 2014 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.
+//
+// Instantiate mocks and provide interface lookup functions.
+
+#include "ppapi_mocks/background_thread.h"
+
+#include "common/alog.h"
+#include "gmock/gmock.h"
+#include "ppapi_mocks/ppapi_test.h"
+#include "ppapi_mocks/ppb_core.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+
+BackgroundThread::BackgroundThread(PpapiTest* ppapi_test)
+    : background_thread_(0), cond_(&mutex_), ppapi_test_(ppapi_test) {
+  main_thread_ = pthread_self();
+}
+
+BackgroundThread::~BackgroundThread() {
+  if (background_thread_ != 0)
+    pthread_join(background_thread_, NULL);
+}
+
+void BackgroundThread::SetUp() {
+  EXPECT_CALL(*ppapi_test_->ppb_core_, CallOnMainThread(_, _, _)).
+    WillRepeatedly(Invoke(this, &BackgroundThread::CallOnMainThread));
+}
+
+void BackgroundThread::Start(const pp::CompletionCallback& cc,
+                                  int32_t result) {
+  cc_test_thread_ = cc;
+  test_thread_result_ = result;
+  pthread_create(&background_thread_, NULL, RunBackground, this);
+}
+
+void* BackgroundThread::RunBackground(void* void_self) {
+  BackgroundThread* self =
+    reinterpret_cast<BackgroundThread*>(void_self);
+  self->cc_test_thread_.Run(self->test_thread_result_);
+  self->EnqueueEvent(Event(Event::kFinished));
+  return NULL;
+}
+
+void BackgroundThread::EnqueueEvent(const Event& e) {
+  mutex_.Acquire();
+  event_queue_.push_back(e);
+  if (pthread_self() != main_thread_)
+    cond_.Signal();
+  mutex_.Release();
+}
+
+void BackgroundThread::RunMainThreadLoop() {
+  do {
+    mutex_.Acquire();
+    while (event_queue_.empty())
+      cond_.Wait();
+    Event event(event_queue_.front());
+    event_queue_.pop_front();
+    mutex_.Release();
+    switch (event.kind) {
+      case Event::kFinished:
+        return;
+      case Event::kCallOnMainThread:
+        ALOG_ASSERT(event.cc.func);
+        PP_RunCompletionCallback(&event.cc, event.result);
+        break;
+    }
+  } while (true);
+}
+
+void BackgroundThread::CallOnMainThread(
+    int32_t delay_in_milliseconds,
+    struct PP_CompletionCallback cc,
+    int32_t result) {
+  // Note delay_in_milliseconds is purposefully ignored.  We should
+  // not need to add delays to any tests.
+  EnqueueEvent(Event(cc, result));
+}
diff --git a/src/ppapi_mocks/background_thread.h b/src/ppapi_mocks/background_thread.h
new file mode 100644
index 0000000..ec8e83a
--- /dev/null
+++ b/src/ppapi_mocks/background_thread.h
@@ -0,0 +1,59 @@
+// Copyright 2014 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.
+//
+// Test framework for background thread testing.
+
+#ifndef PPAPI_MOCKS_BACKGROUND_THREAD_H_
+#define PPAPI_MOCKS_BACKGROUND_THREAD_H_
+
+#include <pthread.h>
+#include <list>
+
+#include "base/synchronization/condition_variable.h"
+#include "base/synchronization/lock.h"
+#include "ppapi/cpp/completion_callback.h"
+
+class PpapiTest;
+
+class BackgroundThread {
+ public:
+  explicit BackgroundThread(PpapiTest* ppapi_test);
+  ~BackgroundThread();
+
+  void SetUp();
+
+  void Start(const pp::CompletionCallback& cc, int32_t result);
+  void RunMainThreadLoop();
+  void CallOnMainThread(int32_t delay_in_milliseconds,
+                        struct PP_CompletionCallback cc,
+                        int32_t result);
+
+ private:
+  struct Event {
+    enum Kind {
+      kCallOnMainThread,
+      kFinished
+    } kind;
+    explicit Event(Kind kind) : kind(kind) {}
+    Event(const PP_CompletionCallback& cc, int32_t result)
+      : kind(kCallOnMainThread),
+        cc(cc),
+        result(result) {}
+    PP_CompletionCallback cc;
+    int32_t result;
+  };
+  static void* RunBackground(void* void_self);
+  void EnqueueEvent(const Event& e);
+
+  std::list<Event> event_queue_;
+  pp::CompletionCallback cc_test_thread_;
+  int32_t test_thread_result_;
+  pthread_t background_thread_;
+  pthread_t main_thread_;
+  base::Lock mutex_;
+  base::ConditionVariable cond_;
+  PpapiTest* ppapi_test_;
+};
+
+#endif  // PPAPI_MOCKS_BACKGROUND_THREAD_H_
diff --git a/src/ppapi_mocks/config.py b/src/ppapi_mocks/config.py
new file mode 100755
index 0000000..03d7df6
--- /dev/null
+++ b/src/ppapi_mocks/config.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+# Copyright 2014 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.
+
+import os
+import pipes
+
+import build_common
+import ninja_generator
+import staging
+
+
+_CONTAINER_DIR = 'ppapi_mocks'
+_MY_DIR = 'src/ppapi_mocks'
+
+
+def _get_ppapi_mocks_generated_dir():
+  return os.path.join(build_common.get_build_dir(), _CONTAINER_DIR)
+
+
+def _add_ppapi_mock_compile_flags(n):
+  n.add_ppapi_compile_flags()
+  n.add_libchromium_base_compile_flags()
+  n.add_include_paths('third_party/testing/gmock/include',
+                      _MY_DIR,
+                      _get_ppapi_mocks_generated_dir())
+
+
+def _get_ppapi_include_dirs():
+  chrome_host_path = os.path.relpath(
+      build_common.get_chrome_ppapi_root_path(), 'third_party')
+  return [chrome_host_path,
+          os.path.join(chrome_host_path, 'ppapi/lib/gl/include')]
+
+
+def _generate_libppapi_mocks():
+  rule_name = 'gen_ppapi_mock'
+  # Set instances to zero since libppapi_mocks is not used by any
+  # shared objects (only test executables) and instances only counts
+  # shared object uses.
+  n = ninja_generator.ArchiveNinjaGenerator('libppapi_mocks',
+                                            instances=0,
+                                            enable_clang=True)
+  ppapi_dir = staging.as_staging('chromium-ppapi/ppapi')
+  generators_dir = os.path.join(ppapi_dir, 'generators')
+  api_dir = os.path.join(ppapi_dir, 'api')
+  script_path = os.path.join(_MY_DIR, 'gen_ppapi_mock.py')
+  out_dir = os.path.join(
+      ninja_generator.PpapiTestNinjaGenerator.get_ppapi_mocks_generated_dir(),
+      _CONTAINER_DIR)
+  log_file = os.path.join(out_dir, 'log.txt')
+  stamp_file = os.path.join(out_dir, 'STAMP')
+
+  command = ['PYTHONPATH=%s' % pipes.quote(generators_dir),
+             'python', pipes.quote(script_path),
+             '--wnone',  # Suppress all warnings.
+             '--range=start,end',  # Generate code for all revisions.
+             '--ppapicgen',  # Generate PpapiMock source files.
+             '--ppapihgen',  # Generate PpapiMock header files.
+             '--srcroot', pipes.quote(api_dir),
+             '--dstroot', pipes.quote(out_dir),
+             '>', '$log_file',
+             '|| (cat $log_file; rm $log_file; exit 1)']
+  n.rule(rule_name,
+         command=('(' + ' '.join(command) + ') && touch $stamp'),
+         description=rule_name)
+
+  generated_files = []
+  idl_list = n.find_all_files('chromium-ppapi/ppapi/api', '.idl')
+  for idl_path in idl_list:
+    if 'finish_writing_these' in idl_path:
+      # Files under ppapi/api/private/finish_writing_these/ directory are not
+      # found by the parser of the idl generator library.
+      continue
+    path_stem = os.path.splitext(os.path.basename(idl_path))[0]
+    # We are interested in only PPB files.
+    if not path_stem.startswith('ppb_'):
+      continue
+    generated_files.append(os.path.join(out_dir, path_stem + '.cc'))
+    generated_files.append(os.path.join(out_dir, path_stem + '.h'))
+
+  n.build(generated_files + [stamp_file], rule_name, idl_list,
+          variables={'log_file': pipes.quote(log_file),
+                     'stamp': pipes.quote(stamp_file)},
+          implicit=([staging.as_staging(idl_path) for idl_path in idl_list] +
+                    [script_path]))
+  _add_ppapi_mock_compile_flags(n)
+  n.build_default(
+      [path for path in generated_files if path.endswith('.cc')] +
+      n.find_all_files([_MY_DIR], '.cc', include_tests=True),
+      implicit=[stamp_file])
+  n.archive()
+
+
+def generate_ninjas():
+  _generate_libppapi_mocks()
diff --git a/src/ppapi_mocks/gen_ppapi_mock.py b/src/ppapi_mocks/gen_ppapi_mock.py
new file mode 100755
index 0000000..3fba0ee
--- /dev/null
+++ b/src/ppapi_mocks/gen_ppapi_mock.py
@@ -0,0 +1,327 @@
+#!/usr/bin/python
+# Copyright 2014 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.
+#
+# Generator of PPAPI mock source files.
+
+import os
+import sys
+
+import idl_c_proto
+import idl_generator
+import idl_option
+import idl_parser
+
+idl_option.Option('dstroot', 'Base directory of output', default='')
+
+MAX_GMOCK_ARGS = 10
+
+
+def _GetMockStructName(cgen, interface):
+  return cgen.GetStructName(interface, None) + '_Mock'
+
+
+def _GetPpapiCHeaderPath(filenode):
+  path = filenode.GetProperty('NAME')
+  path = os.path.join('ppapi/c', os.path.splitext(path)[0] + '.h')
+  return os.path.normpath(path)
+
+
+def _GetMockPathInternal(filenode, relpath, ext):
+  basename = os.path.basename(filenode.GetProperty('NAME'))
+  path = os.path.join(relpath, os.path.splitext(basename)[0] + ext)
+  return os.path.normpath(path)
+
+
+def _GetMockHeaderPath(filenode, relpath):
+  return _GetMockPathInternal(filenode, relpath, '.h')
+
+
+def _GetMockSourcePath(filenode, relpath):
+  return _GetMockPathInternal(filenode, relpath, '.cc')
+
+
+def _GetGuardMacro(filenode, relpath):
+  header_path = _GetMockHeaderPath(filenode, relpath)
+  return header_path.replace('/', '_').replace('.', '_').upper() + '_'
+
+
+class PpapiMockHeaderGenerator(idl_generator.GeneratorByFile):
+  HEADER_TEMPLATE = '\n'.join([
+      '// Copyright 2014 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.',
+      '//',
+      '// Provide %(mock_list)s.',
+      '//',
+      '// Auto-generated. Do not edit!',
+      '',
+      '#ifndef %(guard_macro)s',
+      '#define %(guard_macro)s',
+      '',
+      '#include "gmock/gmock.h"',
+      '',
+      '#include "ppapi/c/pp_completion_callback.h"',
+      '#include "%(path)s"',
+      ''])
+
+  MOCK_CLASS_TEMPLATE = '\n'.join([
+      'class %(mock_name)s {',
+      ' public:',
+      '  %(mock_name)s();',
+      '  virtual ~%(mock_name)s();',
+      '',
+      '%(method_list)s',
+      '};'])
+
+  def __init__(self):
+    idl_generator.Generator.__init__(
+        self, 'PPAPI Mock Header', 'ppapihgen',
+        'Generate the PPAPI Mock headers')
+    self.cgen = idl_c_proto.CGen()
+
+  def GenerateFile(self, filenode, releases, options):
+    if not os.path.basename(filenode.GetName()).startswith('ppb_'):
+      return
+
+    # Generator library has an issue that some getters changes its object's
+    # internal status, and the main usage of the generator in Chrome (i.e.
+    # generating PPAPI C/C++ headers and shims) relies on that behavior.
+    # So, here we mimic the key part of what the main usage relies on.
+    # Without this, it actually causes a problem that it produces the
+    # un-compilable source code.
+    for node in filenode.GetListOf('Typedef'):
+      node.GetUniqueReleases(releases)
+
+    # Generate file content.
+    file_header = self.GenerateFileHeader(filenode, releases)
+    file_body = self.GenerateFileBody(filenode, releases)
+    file_footer = self.GenerateFileFooter(filenode, releases)
+
+    # Write the content to the file.
+    path = _GetMockHeaderPath(filenode, idl_option.GetOption('dstroot'))
+    with open(path, 'w') as stream:
+      stream.writelines([
+          file_header, '\n', file_body, '\n', file_footer, '\n'])
+
+  def GenerateFileHeader(self, filenode, releases):
+    struct_name_list = [
+        self.cgen.GetStructName(interface, release, include_version=True)
+        for interface in filenode.GetListOf('Interface')
+        for release in interface.GetUniqueReleases(releases)]
+
+    return PpapiMockHeaderGenerator.HEADER_TEMPLATE % {
+        'mock_list': ', '.join(name + ' mock' for name in struct_name_list),
+        'guard_macro': _GetGuardMacro(filenode, 'ppapi_mocks'),
+        'path': _GetPpapiCHeaderPath(filenode),
+    }
+
+  def GenerateFileBody(self, filenode, releases):
+    # Generate mock class declaration list.
+    cgen = self.cgen
+    mock_class_list = []
+    for interface in filenode.GetListOf('Interface'):
+      mock_name = _GetMockStructName(cgen, interface)
+      method_list = []
+      signature_set = set()
+      unique_releases = interface.GetUniqueReleases(releases)
+      for member in interface.GetListOf('Member'):
+        for release in unique_releases:
+          if not member.InReleases([release]):
+            continue
+          return_type, name, arrayspec, callspec = cgen.GetComponents(
+              member, release, 'ref')
+          if len(callspec) > MAX_GMOCK_ARGS:
+            # We do not generate methods which have more than MAX_GMOCK_ARGS
+            # arguments.
+            continue
+          signature = cgen.Compose(
+              return_type, '', arrayspec, callspec, '', False, False, False)
+          if (name, signature) in signature_set:
+            continue
+          signature_set.add((name, signature))
+          method_list.append(
+              '  MOCK_METHOD%d(%s, %s);' % (len(callspec), name, signature))
+      mock_class_list.append(PpapiMockHeaderGenerator.MOCK_CLASS_TEMPLATE % {
+          'mock_name': mock_name,
+          'method_list': '\n'.join(method_list)
+      })
+
+    return '\n\n'.join(mock_class_list) + '\n\n'
+
+  def GenerateFileFooter(self, filenode, release):
+    return '#endif  // %s' % _GetGuardMacro(filenode, 'ppapi_mocks')
+
+
+header_generator = PpapiMockHeaderGenerator()
+
+
+class PpapiMockSourceGenerator(idl_generator.GeneratorByFile):
+  HEADER_TEMPLATE = '\n'.join([
+      '// Copyright 2014 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.',
+      '//',
+      '// Provide %(mock_list)s.',
+      '//',
+      '// Auto-generated. Do not edit!',
+      '',
+      '#include "%(mock_header_path)s"',
+      ''
+      '#include "ppapi_mocks/ppapi_mock_factory.h"',
+      '#include "ppapi_mocks/ppapi_mock_impl.h"',
+      ''])
+
+  MOCK_FUNCTION_TEMPLATE = '\n'.join([
+      'static %(signature)s {',
+      '  return ppapi_mock::MockHolder<%(mock_name)s>::GetMock()->',
+      '      %(name)s(%(args)s);',
+      '}'])
+
+  EMPTY_MOCK_FUNCTION_TEMPLATE = '\n'.join([
+      'static %(signature)s {',
+      '  // FUNCTION CANNOT BE MOCKED.',
+      '  return %(return_type)s();',
+      '}'])
+
+  MOCK_SOURCE_TEMPLATE = '\n'.join([
+      '%(mock_name)s::%(mock_name)s() {',
+      '}',
+      '%(mock_name)s::~%(mock_name)s() {',
+      '}',
+      '',
+      'namespace ppapi_mock {',
+      'template<> ::testing::NiceMock<%(mock_name)s>*',
+      'MockHolder<%(mock_name)s>::instance_ = NULL;',
+      '}  // namespace ppapi_mock',
+      '',
+      'template<> void PpapiMockFactory::GetMock<%(mock_name)s>(',
+      '    ::testing::NiceMock<%(mock_name)s>** result) {',
+      '  *result = ppapi_mock::MockHolder<%(mock_name)s>::GetMock();',
+      '}',
+      '',
+      '%(struct_definition_list)s',
+      '',
+      'INVOKE_AT_OBJECT_LOAD_TIME(%(mock_name)s, {',
+      '  ppapi_mock::MockRegistry::GetInstance()->Register<%(mock_name)s>();',
+      '  %(struct_registration_list)s',
+      '});'])
+
+  INJECTED_STRUCT_TEMPLATE = '\n'.join([
+      '%(method_list)s',
+      '',
+      'static %(struct_name)s s_%(struct_name)s = {',
+      '%(struct_field_list)s',
+      '};',
+      ''])
+
+  STRUCT_REGISTRATION_TEMPLATE = '\n'.join([
+      '  ppapi_mock::InterfaceRegistry::GetInstance()->Register(',
+      '      %(macro_name)s, &s_%(struct_name)s);'])
+
+  def __init__(self):
+    idl_generator.Generator.__init__(
+        self, 'PPAPI Mock source', 'ppapicgen',
+        'Generate the PPAPI Mock sources')
+    self.cgen = idl_c_proto.CGen()
+
+  def GenerateFile(self, filenode, releases, options):
+    if not os.path.basename(filenode.GetName()).startswith('ppb_'):
+      return
+
+    # Similar to PpapiMockHeaderGenerator, we call to GetUniqueReleases
+    # as a work around. Please see also PpapiMockHeaderGenerator.GenerateFile.
+    for node in filenode.GetListOf('Typedef'):
+      node.GetUniqueReleases(releases)
+
+    # Generate file content.
+    file_header = self.GenerateFileHeader(filenode, releases)
+    file_body = self.GenerateFileBody(filenode, releases)
+
+    # Write the content to the file.
+    path = _GetMockSourcePath(filenode, idl_option.GetOption('dstroot'))
+    with open(path, 'w') as stream:
+      stream.writelines([file_header, '\n', file_body, '\n\n'])
+
+  def GenerateFileHeader(self, filenode, releases):
+    struct_name_list = [
+        self.cgen.GetStructName(interface, release, include_version=True)
+        for interface in filenode.GetListOf('Interface')
+        for release in interface.GetUniqueReleases(releases)]
+    return PpapiMockSourceGenerator.HEADER_TEMPLATE % {
+        'mock_list': ', '.join(name + ' mock' for name in struct_name_list),
+        'mock_header_path': _GetMockHeaderPath(filenode, 'ppapi_mocks'),
+    }
+
+  def GenerateFileBody(self, filenode, releases):
+    cgen = self.cgen
+    result = []
+    for interface in filenode.GetListOf('Interface'):
+      mock_name = _GetMockStructName(cgen, interface)
+      struct_definition_list = []
+      struct_registration_list = []
+      for release in interface.GetUniqueReleases(releases):
+        struct_name = cgen.GetStructName(
+            interface, release, include_version=True)
+        macro_name = cgen.GetInterfaceMacro(
+            interface, interface.GetVersion(release))
+
+        method_list = []
+        method_name_list = []
+        for member in interface.GetListOf('Member'):
+          if not member.InReleases([release]):
+            continue
+          return_type, name, arrayspec, callspec = cgen.GetComponents(
+              member, release, 'ref')
+          signature = cgen.GetSignature(
+              member, release, 'ref', prefix=struct_name + '_',
+              func_as_ptr=False, include_name=True, include_version=False)
+
+          if len(callspec) <= MAX_GMOCK_ARGS:
+            method_template = PpapiMockSourceGenerator.MOCK_FUNCTION_TEMPLATE
+          else:
+            method_template = (
+                PpapiMockSourceGenerator.EMPTY_MOCK_FUNCTION_TEMPLATE)
+          method_list.append(method_template % {
+              'signature': signature,
+              'mock_name': mock_name,
+              'return_type': return_type,
+              'name': name,
+              'args': ', '.join(arg for _, arg, _, _ in callspec)
+          })
+          method_name_list.append(struct_name + '_' + name)
+
+        injected_struct = PpapiMockSourceGenerator.INJECTED_STRUCT_TEMPLATE % {
+            'method_list': '\n\n'.join(method_list),
+            'struct_field_list': '\n'.join(
+                '    %s,' % method_name for method_name in method_name_list),
+            'struct_name': struct_name,
+        }
+        struct_definition_list.append(injected_struct)
+        registration = PpapiMockSourceGenerator.STRUCT_REGISTRATION_TEMPLATE % {
+            'macro_name': macro_name,
+            'struct_name': struct_name,
+        }
+        struct_registration_list.append(registration)
+      result.append(PpapiMockSourceGenerator.MOCK_SOURCE_TEMPLATE % {
+          'mock_name': mock_name,
+          'struct_definition_list': '\n\n'.join(struct_definition_list),
+          'struct_registration_list': '\n\n'.join(struct_registration_list)
+      })
+    return '\n\n'.join(result)
+
+
+source_generator = PpapiMockSourceGenerator()
+
+
+def main():
+  filenames = idl_option.ParseOptions(sys.argv[1:])
+  ast = idl_parser.ParseFiles(filenames)
+  assert not ast.errors, ast.errors
+  return idl_generator.Generator.Run(ast)
+
+if __name__ == '__main__':
+  main()
diff --git a/src/ppapi_mocks/ppapi_mock_factory.cc b/src/ppapi_mocks/ppapi_mock_factory.cc
new file mode 100644
index 0000000..12f973e
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_mock_factory.cc
@@ -0,0 +1,15 @@
+// Copyright 2014 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 "ppapi_mocks/ppapi_mock_factory.h"
+
+#include "ppapi_mocks/ppapi_mock_impl.h"
+
+PpapiMockFactory::PpapiMockFactory() {
+  ppapi_mock::MockRegistry::GetInstance()->CreateAllMocks();
+}
+
+PpapiMockFactory::~PpapiMockFactory() {
+  ppapi_mock::MockRegistry::GetInstance()->DeleteAllMocks();
+}
diff --git a/src/ppapi_mocks/ppapi_mock_factory.h b/src/ppapi_mocks/ppapi_mock_factory.h
new file mode 100644
index 0000000..323eef9
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_mock_factory.h
@@ -0,0 +1,27 @@
+// Copyright 2014 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 PPAPI_MOCKS_PPAPI_MOCK_FACTORY_H_
+#define PPAPI_MOCKS_PPAPI_MOCK_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "gmock/gmock.h"
+
+// Creates and manages mocks based on what is in the registry.  There
+// is only one instance of this allowed at a time but there can be
+// many of these created and destroyed over the lifetime of the
+// process.
+class PpapiMockFactory {
+ public:
+  PpapiMockFactory();
+  ~PpapiMockFactory();
+
+  // Returns the NiceMock instance of the mock class T.
+  // Note that the implementation is written in the generated code.
+  template<typename T> void GetMock(::testing::NiceMock<T>** result);
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PpapiMockFactory);
+};
+
+#endif  // PPAPI_MOCKS_PPAPI_MOCK_FACTORY_H_
diff --git a/src/ppapi_mocks/ppapi_mock_impl.cc b/src/ppapi_mocks/ppapi_mock_impl.cc
new file mode 100644
index 0000000..044d985
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_mock_impl.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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 "ppapi_mocks/ppapi_mock_impl.h"
+
+#include "base/memory/singleton.h"
+
+namespace ppapi_mock {
+
+InterfaceRegistry::InterfaceRegistry() {
+}
+InterfaceRegistry::~InterfaceRegistry() {
+}
+
+InterfaceRegistry* InterfaceRegistry::GetInstance() {
+  return Singleton<InterfaceRegistry,
+                   LeakySingletonTraits<InterfaceRegistry> >::get();
+}
+
+void InterfaceRegistry::Register(const char* interface_name,
+                                 const void* ppapi_interface) {
+  const bool is_inserted = interface_map_.insert(
+      std::make_pair(interface_name, ppapi_interface)).second;
+  LOG_ALWAYS_FATAL_IF(!is_inserted,
+                      "\"%s\" is registered twice", interface_name);
+}
+
+const void* InterfaceRegistry::GetInterface(const char* interface_name) const {
+  InterfaceMap::const_iterator iter = interface_map_.find(interface_name);
+  LOG_ALWAYS_FATAL_IF(iter == interface_map_.end(),
+                      "Requesting unmocked interface: %s\n", interface_name);
+  return iter->second;
+}
+
+MockRegistry::MockRegistry() {
+}
+MockRegistry::~MockRegistry() {
+}
+
+MockRegistry* MockRegistry::GetInstance() {
+  return Singleton<MockRegistry,
+                   LeakySingletonTraits<MockRegistry> >::get();
+}
+
+void MockRegistry::CreateAllMocks() {
+  for (size_t i = 0; i < mock_creators_.size(); ++i) {
+    mock_creators_[i]();
+  }
+}
+
+void MockRegistry::DeleteAllMocks() {
+  for (size_t i = 0; i < mock_deleters_.size(); ++i) {
+    mock_deleters_[i]();
+  }
+}
+
+const void* GetInterface(const char* interface_name) {
+  return InterfaceRegistry::GetInstance()->GetInterface(interface_name);
+}
+
+}  // namespace ppapi_mock
diff --git a/src/ppapi_mocks/ppapi_mock_impl.h b/src/ppapi_mocks/ppapi_mock_impl.h
new file mode 100644
index 0000000..c559716
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_mock_impl.h
@@ -0,0 +1,114 @@
+// Copyright 2014 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 PPAPI_MOCKS_PPAPI_MOCK_IMPL_H_
+#define PPAPI_MOCKS_PPAPI_MOCK_IMPL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "common/alog.h"
+#include "gmock/gmock.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace ppapi_mock {
+
+// Holds the map from PPAPI interface name to the function pointer table of
+// injected functions.
+class InterfaceRegistry {
+ public:
+  static InterfaceRegistry* GetInstance();
+
+  void Register(const char* interface_name, const void* ppapi_interface);
+  const void* GetInterface(const char* interface_name) const;
+
+ private:
+  friend struct DefaultSingletonTraits<InterfaceRegistry>;
+  typedef std::map<std::string, const void*> InterfaceMap;
+
+  InterfaceRegistry();
+  ~InterfaceRegistry();
+  InterfaceMap interface_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(InterfaceRegistry);
+};
+
+// Holds the mock instance of T.
+// We expect that:
+// 1) First, CreateMock() should be called to create an instance.
+// 2) Then GetMock() can be used.
+// 3) Once a test case finishes, DeleteMock() should be called.
+// Then we again can create another new mock by CreateMock().
+template<typename T>
+class MockHolder {
+ public:
+  static void CreateMock() {
+    LOG_ALWAYS_FATAL_IF(instance_ != NULL,
+                        "CreateMock must not be called twice consecutively.");
+    instance_ = new ::testing::NiceMock<T>;
+  }
+
+  static void DeleteMock() {
+    LOG_ALWAYS_FATAL_IF(instance_ == NULL,
+                        "DeleteMock must be called after CreateMock.");
+    delete instance_;
+    instance_ = NULL;
+  }
+
+  static ::testing::NiceMock<T>* GetMock() {
+    LOG_ALWAYS_FATAL_IF(instance_ == NULL,
+                        "GetMock must be called after CreateMock.");
+    return instance_;
+  }
+
+ private:
+  static ::testing::NiceMock<T>* instance_;
+  DISALLOW_IMPLICIT_CONSTRUCTORS(MockHolder);
+};
+
+// Manages mock instance creation for all the mock classes.
+class MockRegistry {
+ public:
+  static MockRegistry* GetInstance();
+
+  template<typename T>
+  void Register() {
+    mock_creators_.push_back(&MockHolder<T>::CreateMock);
+    mock_deleters_.push_back(&MockHolder<T>::DeleteMock);
+  }
+
+  void CreateAllMocks();
+  void DeleteAllMocks();
+
+ private:
+  friend struct DefaultSingletonTraits<MockRegistry>;
+
+  MockRegistry();
+  ~MockRegistry();
+
+  std::vector<void (*)()> mock_creators_;
+  std::vector<void (*)()> mock_deleters_;
+  DISALLOW_COPY_AND_ASSIGN(MockRegistry);
+};
+
+// This function should be passed to the PPP_InitializeModule in order to
+// inject mocked Pepper functions.
+const void* GetInterface(const char* interface_name);
+
+}  // namespace ppapi_mock
+
+// Helper macro to run a given function when the module is loaded.
+// Would have preferred to just use a function call in a static variable
+// initializer, but suppressing the warning that the variable is never
+// used requires compiler-specific syntax.
+#define INVOKE_AT_OBJECT_LOAD_TIME(_unique, _body)                        \
+    static class LoadHelper##_unique {                                    \
+     public:                                                              \
+      LoadHelper##_unique() _body                                         \
+    } s_load_helper_##_unique;
+
+#endif  // PPAPI_MOCKS_PPAPI_MOCK_IMPL_H_
diff --git a/src/ppapi_mocks/ppapi_test.cc b/src/ppapi_mocks/ppapi_test.cc
new file mode 100644
index 0000000..56f17ff
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_test.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 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.
+//
+// Instantiate mocks and provide interface lookup functions.
+
+#include "ppapi_mocks/ppapi_test.h"
+
+#include <stdio.h>
+
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/ppb.h"
+#include "ppapi/c/ppp.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi_mocks/ppapi_mock_impl.h"
+#include "ppapi_mocks/ppb_core.h"
+#include "ppapi_mocks/ppb_message_loop.h"
+#include "ppapi_mocks/ppb_messaging.h"
+#include "ppapi_mocks/ppb_var.h"
+#include "ppapi_mocks/ppb_var_array.h"
+#include "ppapi_mocks/ppb_var_dictionary.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+namespace {
+pp::Module* s_test_module = NULL;
+
+// Make the mapping of pp::Vars file scope to simplify creation of
+// Mock matchers.
+std::vector<std::string> s_ids;
+std::vector<std::map<std::string, PP_Var> > s_dict;
+std::vector<std::vector<PP_Var> > s_array;
+
+void CompletionCallbackNotSet(void *user_data, int32_t result) {
+  FAIL() << "Called back a completion callback that was not set.";
+}
+
+}  // namespace
+
+PpapiTest::~PpapiTest() {
+  EXPECT_TRUE(completion_callbacks_.empty())
+      << "Completion callback was set but not called.";
+  PPP_ShutdownModule();
+}
+
+void PpapiTest::SetUp() {
+  s_test_module = NULL;
+  instance_.reset(new pp::Instance(kInstanceNumber));
+  // We now proceed to set up the PPAPI interfaces that are needed
+  // by at least two tests.  Ones that are specific to a test (like
+  // GPU, OpenGL, image, audio) access should be defined in that
+  // specific tests' unit test harness.  The intention is simply to
+  // keep from having to have too many dependencies in this header.
+  factory_.GetMock(&ppb_audio_);
+  factory_.GetMock(&ppb_audioconfig_);
+  factory_.GetMock(&ppb_file_system_);
+  factory_.GetMock(&ppb_instance_);
+  factory_.GetMock(&ppb_core_);
+  factory_.GetMock(&ppb_messaging_);
+  factory_.GetMock(&ppb_var_);
+  factory_.GetMock(&ppb_var_array_);
+  factory_.GetMock(&ppb_var_dictionary_);
+  factory_.GetMock(&ppb_view_);
+  factory_.GetMock(&ppb_crxfs_);
+  factory_.GetMock(&ppb_message_loop_);
+  factory_.GetMock(&ppb_uma_);
+
+  module_ = new MockModule;
+  SetUpModule(module_);
+  main_thread_ = pthread_self();
+  EXPECT_CALL(*ppb_core_, IsMainThread()).WillRepeatedly(
+        Invoke(this, &PpapiTest::IsMainThread));
+
+  EXPECT_CALL(*ppb_var_, VarToResource(_)).
+    WillRepeatedly(Invoke(ppapi_mocks::VarToResource));
+  EXPECT_CALL(*ppb_var_, VarFromResource(_)).
+    WillRepeatedly(Invoke(ppapi_mocks::VarFromResource));
+  EXPECT_CALL(*ppb_var_, VarFromUtf8(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::VarFromUtf8));
+  EXPECT_CALL(*ppb_var_, VarFromUtf8(_, _, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::VarFromUtf8_1_0));
+  EXPECT_CALL(*ppb_var_, VarToUtf8(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::VarToUtf8));
+  EXPECT_CALL(*ppb_var_, AddRef(_)).WillRepeatedly(Return());
+  EXPECT_CALL(*ppb_var_, Release(_)).WillRepeatedly(Return());
+
+  EXPECT_CALL(*ppb_var_dictionary_, Create()).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionaryCreate));
+  EXPECT_CALL(*ppb_var_dictionary_, Get(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionaryGet));
+  EXPECT_CALL(*ppb_var_dictionary_, Set(_, _, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionarySet));
+  EXPECT_CALL(*ppb_var_dictionary_, Delete(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionaryDelete));
+  EXPECT_CALL(*ppb_var_dictionary_, HasKey(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionaryHasKey));
+  EXPECT_CALL(*ppb_var_dictionary_, GetKeys(_)).
+    WillRepeatedly(Invoke(ppapi_mocks::DictionaryGetKeys));
+
+  EXPECT_CALL(*ppb_var_array_, Create()).
+    WillRepeatedly(Invoke(ppapi_mocks::ArrayCreate));
+  EXPECT_CALL(*ppb_var_array_, Get(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::ArrayGet));
+  EXPECT_CALL(*ppb_var_array_, Set(_, _, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::ArraySet));
+  EXPECT_CALL(*ppb_var_array_, GetLength(_)).
+    WillRepeatedly(Invoke(ppapi_mocks::ArrayGetLength));
+  EXPECT_CALL(*ppb_var_array_, SetLength(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::ArraySetLength));
+
+  EXPECT_CALL(*ppb_uma_, IsCrashReportingEnabled(_, _)).
+    WillRepeatedly(Invoke(ppapi_mocks::IsCrashReportingEnabled));
+}
+
+void PpapiTest::PushCompletionCallback(PP_CompletionCallback cb) {
+  completion_callbacks_.push(cb);
+}
+
+PP_CompletionCallback PpapiTest::PopPendingCompletionCallback() {
+  if (completion_callbacks_.empty())
+    return PP_MakeCompletionCallback(CompletionCallbackNotSet, NULL);
+  PP_CompletionCallback temp = completion_callbacks_.front();
+  completion_callbacks_.pop();
+  return temp;
+}
+
+PP_Bool PpapiTest::IsMainThread() {
+  return pthread_self() == main_thread_ ? PP_TRUE : PP_FALSE;
+}
+
+void PpapiTest::SetUpModule(pp::Module* module) {
+  s_test_module = module;
+  PPP_InitializeModule(0, &ppapi_mock::GetInterface);
+}
+
+namespace pp {
+  Module* CreateModule() {
+    return s_test_module;
+  }
+}
+
+namespace ppapi_mocks {
+
+const char* VarToUtf8(const struct PP_Var var, uint32_t* len) {
+  if (var.type != PP_VARTYPE_STRING ||
+      var.value.as_id <= 0 ||
+      var.value.as_id > s_ids.size()) {
+    return NULL;
+  }
+  std::string& str = s_ids[var.value.as_id - 1];
+  *len = str.size();
+  return str.c_str();
+}
+
+std::string VarToString(const struct PP_Var var) {
+  uint32_t len = 0;
+  const char* s = ppapi_mocks::VarToUtf8(var, &len);
+  if (!s) return std::string();
+  return std::string(s, len);
+}
+
+PP_Var VarFromString(const std::string& str) {
+  return VarFromUtf8(str.c_str(), str.size());
+}
+
+PP_Var VarFromUtf8(const char* value, int len) {
+  s_ids.push_back(std::string(value, len));
+  PP_Var result;
+  result.type = PP_VARTYPE_STRING;
+  result.value.as_id = s_ids.size();
+  return result;
+}
+
+PP_Var VarFromUtf8_1_0(PP_Module unused_module, const char* value, int len) {
+  return VarFromUtf8(value, len);
+}
+
+PP_Var DictionaryCreate() {
+  s_dict.push_back(std::map<std::string, PP_Var>());
+  PP_Var result;
+  result.type = PP_VARTYPE_DICTIONARY;
+  result.value.as_id = s_dict.size() - 1;
+  return result;
+}
+
+PP_Var DictionaryGet(PP_Var dict, PP_Var key) {
+  ALOG_ASSERT(dict.type == PP_VARTYPE_DICTIONARY);
+  ALOG_ASSERT(key.type == PP_VARTYPE_STRING);
+  uint32_t unused_len = 0;
+  return s_dict[dict.value.as_id][VarToUtf8(key, &unused_len)];
+}
+
+PP_Bool DictionarySet(PP_Var dict, PP_Var key, PP_Var value) {
+  ALOG_ASSERT(dict.type == PP_VARTYPE_DICTIONARY);
+  ALOG_ASSERT(key.type == PP_VARTYPE_STRING);
+  uint32_t unused_len = 0;
+  s_dict[dict.value.as_id][VarToUtf8(key, &unused_len)] = value;
+  return PP_TRUE;
+}
+
+void DictionaryDelete(PP_Var dict, PP_Var key) {
+  ALOG_ASSERT(dict.type == PP_VARTYPE_DICTIONARY);
+  ALOG_ASSERT(key.type == PP_VARTYPE_STRING);
+  uint32_t unused_len = 0;
+  s_dict[dict.value.as_id].erase(VarToUtf8(key, &unused_len));
+}
+
+PP_Bool DictionaryHasKey(PP_Var dict, PP_Var key) {
+  ALOG_ASSERT(dict.type == PP_VARTYPE_DICTIONARY);
+  ALOG_ASSERT(key.type == PP_VARTYPE_STRING);
+  uint32_t unused_len = 0;
+  return s_dict[dict.value.as_id].find(VarToUtf8(key, &unused_len)) ==
+      s_dict[dict.value.as_id].end() ? PP_FALSE : PP_TRUE;
+}
+
+PP_Var DictionaryGetKeys(PP_Var dict) {
+  PP_Var result = ArrayCreate();
+  ArraySetLength(result, s_dict[dict.value.as_id].size());
+
+  std::map<std::string, PP_Var>::const_iterator it =
+      s_dict[dict.value.as_id].begin();
+  size_t i = 0;
+  while (it != s_dict[dict.value.as_id].end()) {
+    ArraySet(result, i++, VarFromUtf8(it->first.c_str(), it->first.size()));
+    ++it;
+  }
+  return result;
+}
+
+PP_Var ArrayCreate() {
+  s_array.push_back(std::vector<PP_Var>());
+  PP_Var result;
+  result.type = PP_VARTYPE_ARRAY;
+  result.value.as_id = s_array.size() - 1;
+  return result;
+}
+
+PP_Var ArrayGet(PP_Var array, uint32_t index) {
+  ALOG_ASSERT(array.type == PP_VARTYPE_ARRAY);
+  return s_array[array.value.as_id][index];
+}
+
+PP_Bool ArraySet(PP_Var array, uint32_t index, PP_Var value) {
+  ALOG_ASSERT(array.type == PP_VARTYPE_ARRAY);
+  s_array[array.value.as_id][index] = value;
+  return PP_TRUE;
+}
+
+uint32_t ArrayGetLength(PP_Var array) {
+  ALOG_ASSERT(array.type == PP_VARTYPE_ARRAY);
+  return s_array[array.value.as_id].size();
+}
+
+PP_Bool ArraySetLength(PP_Var array, uint32_t length) {
+  ALOG_ASSERT(array.type == PP_VARTYPE_ARRAY);
+  s_array[array.value.as_id].resize(length);
+  return PP_TRUE;
+}
+
+PP_Resource VarToResource(struct PP_Var var) {
+  return var.value.as_id;
+}
+
+PP_Var VarFromResource(PP_Resource resource) {
+  PP_Var result;
+  result.type = PP_VARTYPE_RESOURCE;
+  result.value.as_id = static_cast<uint32_t>(resource);
+  return result;
+}
+
+int32_t IsCrashReportingEnabled(PP_Instance instance,
+                                PP_CompletionCallback cb) {
+  // Return an error so the tests assume crash reporting is not enabled.
+  PP_RunCompletionCallback(&cb, PP_ERROR_FAILED);
+  return PP_OK_COMPLETIONPENDING;
+}
+
+// Pretty-printer for pp::Var types.
+::std::ostream& operator<<(::std::ostream& os, const PP_Var& var) {
+  uint32_t len = 0;
+  const char* str = VarToUtf8(var, &len);
+  if (str == NULL)
+    return os << "(Non-string var)";
+  else
+    return os << "\"" << str << "\"";
+}
+
+}  // namespace ppapi_mocks
diff --git a/src/ppapi_mocks/ppapi_test.h b/src/ppapi_mocks/ppapi_test.h
new file mode 100644
index 0000000..ba6d16b
--- /dev/null
+++ b/src/ppapi_mocks/ppapi_test.h
@@ -0,0 +1,136 @@
+// Copyright 2014 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.
+//
+// PPAPI test framework.
+
+#ifndef PPAPI_MOCKS_PPAPI_TEST_H_
+#define PPAPI_MOCKS_PPAPI_TEST_H_
+
+#include <string>
+#include <queue>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "ppapi/c/pp_bool.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/view.h"
+#include "ppapi_mocks/ppapi_mock_factory.h"
+#include "ppapi_mocks/ppb_audio.h"
+#include "ppapi_mocks/ppb_audio_config.h"
+#include "ppapi_mocks/ppb_core.h"
+#include "ppapi_mocks/ppb_ext_crx_file_system_private.h"
+#include "ppapi_mocks/ppb_file_system.h"
+#include "ppapi_mocks/ppb_instance.h"
+#include "ppapi_mocks/ppb_message_loop.h"
+#include "ppapi_mocks/ppb_messaging.h"
+#include "ppapi_mocks/ppb_uma_private.h"
+#include "ppapi_mocks/ppb_var.h"
+#include "ppapi_mocks/ppb_var_array.h"
+#include "ppapi_mocks/ppb_var_dictionary.h"
+#include "ppapi_mocks/ppb_view.h"
+
+namespace ppapi_mocks {
+// Avoid duplicating the implementation of scoped_ptr by Chromium base's
+// implementation it into our own namespace.
+using ::scoped_ptr;
+
+const char* VarToUtf8(const struct PP_Var var, uint32_t* len);
+PP_Var VarFromUtf8(const char* value, int len);
+PP_Var VarFromUtf8_1_0(PP_Module unused_module, const char* value, int len);
+::std::ostream& operator<<(::std::ostream& os, const PP_Var& var);
+
+// Helper functions to get string to/from |var|.
+std::string VarToString(const struct PP_Var var);
+PP_Var VarFromString(const std::string& str);
+
+PP_Var DictionaryCreate();
+PP_Var DictionaryGet(PP_Var dict, PP_Var key);
+PP_Bool DictionarySet(PP_Var dict, PP_Var key, PP_Var value);
+void DictionaryDelete(PP_Var dict, PP_Var key);
+PP_Bool DictionaryHasKey(PP_Var dict, PP_Var key);
+PP_Var DictionaryGetKeys(PP_Var dict);
+
+PP_Var ArrayCreate();
+PP_Var ArrayGet(PP_Var array, uint32_t index);
+PP_Bool ArraySet(PP_Var array, uint32_t index, PP_Var value);
+uint32_t ArrayGetLength(PP_Var array);
+PP_Bool ArraySetLength(PP_Var array, uint32_t length);
+
+PP_Resource VarToResource(struct PP_Var var);
+PP_Var VarFromResource(PP_Resource resource);
+
+int32_t IsCrashReportingEnabled(PP_Instance instance, PP_CompletionCallback cb);
+}  // namespace ppapi_mocks
+
+class MockModule : public pp::Module {
+ public:
+  MOCK_METHOD1(CreateInstance, pp::Instance*(PP_Instance));
+};
+
+class PpapiTest : public testing::Test {
+ public:
+  enum {
+    kInstanceNumber = 23,
+  };
+  static const PP_Resource kResource1 = 38;
+  static const PP_Resource kResource2 = 39;
+
+  ::testing::NiceMock<PPB_Audio_Mock>* ppb_audio_;
+  ::testing::NiceMock<PPB_AudioConfig_Mock>* ppb_audioconfig_;
+  ::testing::NiceMock<PPB_Instance_Mock>* ppb_instance_;
+  ::testing::NiceMock<PPB_Core_Mock>* ppb_core_;
+  ::testing::NiceMock<PPB_FileSystem_Mock>* ppb_file_system_;
+  ::testing::NiceMock<PPB_Messaging_Mock>* ppb_messaging_;
+  ::testing::NiceMock<PPB_Var_Mock>* ppb_var_;
+  ::testing::NiceMock<PPB_View_Mock>* ppb_view_;
+  ::testing::NiceMock<PPB_Ext_CrxFileSystem_Private_Mock>* ppb_crxfs_;
+  ::testing::NiceMock<PPB_MessageLoop_Mock>* ppb_message_loop_;
+  ::testing::NiceMock<PPB_VarDictionary_Mock>* ppb_var_dictionary_;
+  ::testing::NiceMock<PPB_VarArray_Mock>* ppb_var_array_;
+  ::testing::NiceMock<PPB_UMA_Private_Mock>* ppb_uma_;
+
+  // Completion callbacks will be invoked in FIFO order.
+  void PushCompletionCallback(PP_CompletionCallback cb);
+  PP_CompletionCallback PopPendingCompletionCallback();
+
+ protected:
+  virtual ~PpapiTest();
+  virtual void SetUp() OVERRIDE;
+
+  PP_Bool IsMainThread();
+
+  void SetUpModule(pp::Module* module);
+
+  pp::Module* module_;
+  pp::View view_;
+  pthread_t main_thread_;
+  PpapiMockFactory factory_;
+  ppapi_mocks::scoped_ptr<pp::Instance> instance_;
+
+ private:
+  std::queue<PP_CompletionCallback> completion_callbacks_;
+};
+
+namespace {  // NOLINT
+
+// The matcher needs to be in an anonymous namespace (copied
+// to every compilation including this header) because there is only a
+// macro supporting definition and none for declaration.  Since
+// this is for test code which runs on hosts with lots of memory,
+// so it seems reasonable.
+MATCHER_P(PPVarStrEq, str, std::string(negation ? "isn't" : "is") +
+          " equal to \"" + str + "\"") {
+  uint32_t len = 0;
+  const char* s = ppapi_mocks::VarToUtf8(arg, &len);
+  if (s == NULL) return false;
+  return strcmp(s, str) == 0;
+}
+
+}  // anonymous namespace
+
+
+#endif  // PPAPI_MOCKS_PPAPI_TEST_H_