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_