| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <sys/stat.h> |
| |
| #include <map> |
| #include <string> |
| |
| #include "gtest/gtest.h" |
| |
| #include "nacl_io/filesystem.h" |
| #include "nacl_io/kernel_handle.h" |
| #include "nacl_io/kernel_object.h" |
| #include "nacl_io/path.h" |
| |
| using namespace nacl_io; |
| |
| namespace { |
| |
| class NodeForTesting : public Node { |
| public: |
| explicit NodeForTesting(Filesystem* fs) : Node(fs) {} |
| }; |
| |
| class FilesystemForTesting : public Filesystem { |
| public: |
| FilesystemForTesting() {} |
| |
| public: |
| Error Access(const Path& path, int a_mode) { return ENOSYS; } |
| Error OpenWithMode(const Path& path, int open_flags, |
| mode_t mode, ScopedNode* out_node) { |
| out_node->reset(NULL); |
| return ENOSYS; |
| } |
| Error Unlink(const Path& path) { return 0; } |
| Error Mkdir(const Path& path, int permissions) { return 0; } |
| Error Rmdir(const Path& path) { return 0; } |
| Error Remove(const Path& path) { return 0; } |
| Error Rename(const Path& path, const Path& newpath) { return 0; } |
| }; |
| |
| class KernelHandleForTesting : public KernelHandle { |
| public: |
| KernelHandleForTesting(const ScopedFilesystem& fs, const ScopedNode& node) |
| : KernelHandle(fs, node) {} |
| }; |
| |
| class KernelObjectTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| fs.reset(new FilesystemForTesting()); |
| node.reset(new NodeForTesting(fs.get())); |
| } |
| |
| void TearDown() { |
| // fs is ref-counted, it doesn't need to be explicitly deleted. |
| node.reset(NULL); |
| fs.reset(NULL); |
| } |
| |
| KernelObject proxy; |
| ScopedFilesystem fs; |
| ScopedNode node; |
| }; |
| |
| } // namespace |
| |
| TEST_F(KernelObjectTest, Referencing) { |
| // The filesystem and node should have 1 ref count at this point |
| EXPECT_EQ(1, fs->RefCount()); |
| EXPECT_EQ(1, node->RefCount()); |
| |
| // Pass the filesystem and node into a KernelHandle |
| KernelHandle* raw_handle = new KernelHandleForTesting(fs, node); |
| ScopedKernelHandle handle_a(raw_handle); |
| |
| // The filesystem and node should have 1 ref count at this point |
| EXPECT_EQ(1, handle_a->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| ScopedKernelHandle handle_b = handle_a; |
| |
| // There should be two references to the KernelHandle, the filesystem and node |
| // should be unchanged. |
| EXPECT_EQ(2, handle_a->RefCount()); |
| EXPECT_EQ(2, handle_b->RefCount()); |
| EXPECT_EQ(handle_a.get(), handle_b.get()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| // Allocating an FD should cause the KernelProxy to ref the handle and |
| // the node and filesystem should be unchanged. |
| int fd1 = proxy.AllocateFD(handle_a, "/example"); |
| EXPECT_EQ(3, handle_a->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| // If we "dup" the handle, we should bump the ref count on the handle |
| int fd2 = proxy.AllocateFD(handle_b, ""); |
| EXPECT_EQ(4, handle_a->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| // Handles are expected to come out in order |
| EXPECT_EQ(0, fd1); |
| EXPECT_EQ(1, fd2); |
| |
| // Now we "free" the handles, since the proxy should hold them. |
| handle_a.reset(NULL); |
| handle_b.reset(NULL); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| // We should find the handle by either fd |
| EXPECT_EQ(0, proxy.AcquireHandle(fd1, &handle_a)); |
| EXPECT_EQ(0, proxy.AcquireHandle(fd2, &handle_b)); |
| EXPECT_EQ(raw_handle, handle_a.get()); |
| EXPECT_EQ(raw_handle, handle_b.get()); |
| |
| EXPECT_EQ(4, handle_a->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| // A non existent fd should fail, and handleA should decrement as handleB |
| // is released by the call. |
| EXPECT_EQ(EBADF, proxy.AcquireHandle(-1, &handle_b)); |
| EXPECT_EQ(NULL, handle_b.get()); |
| EXPECT_EQ(3, handle_a->RefCount()); |
| |
| EXPECT_EQ(EBADF, proxy.AcquireHandle(100, &handle_b)); |
| EXPECT_EQ(NULL, handle_b.get()); |
| |
| // Now only the KernelProxy should reference the KernelHandle in the |
| // FD to KernelHandle Map. |
| handle_a.reset(); |
| handle_b.reset(); |
| |
| EXPECT_EQ(2, raw_handle->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| proxy.FreeFD(fd2); |
| EXPECT_EQ(1, raw_handle->RefCount()); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| |
| proxy.FreeFD(fd1); |
| EXPECT_EQ(1, fs->RefCount()); |
| EXPECT_EQ(1, node->RefCount()); |
| } |
| |
| TEST_F(KernelObjectTest, FreeAndReassignFD) { |
| // The filesystem and node should have 1 ref count at this point |
| EXPECT_EQ(1, fs->RefCount()); |
| EXPECT_EQ(1, node->RefCount()); |
| |
| KernelHandle* raw_handle = new KernelHandleForTesting(fs, node); |
| ScopedKernelHandle handle(raw_handle); |
| |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| EXPECT_EQ(1, raw_handle->RefCount()); |
| |
| proxy.AllocateFD(handle, "/example"); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| EXPECT_EQ(2, raw_handle->RefCount()); |
| |
| proxy.FreeAndReassignFD(5, handle, "/example"); |
| EXPECT_EQ(2, fs->RefCount()); |
| EXPECT_EQ(2, node->RefCount()); |
| EXPECT_EQ(3, raw_handle->RefCount()); |
| |
| |
| handle.reset(); |
| EXPECT_EQ(2, raw_handle->RefCount()); |
| |
| proxy.AcquireHandle(5, &handle); |
| EXPECT_EQ(3, raw_handle->RefCount()); |
| EXPECT_EQ(raw_handle, handle.get()); |
| } |