| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <fcntl.h> |
| #include <gmock/gmock.h> |
| #include <ppapi/c/ppb_file_io.h> |
| #include <ppapi/c/pp_errors.h> |
| #include <ppapi/c/pp_instance.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include "fake_ppapi/fake_pepper_interface_url_loader.h" |
| |
| #include "nacl_io/dir_node.h" |
| #include "nacl_io/httpfs/http_fs.h" |
| #include "nacl_io/kernel_handle.h" |
| #include "nacl_io/kernel_intercept.h" |
| #include "nacl_io/osdirent.h" |
| #include "nacl_io/osunistd.h" |
| |
| using namespace nacl_io; |
| |
| namespace { |
| |
| class HttpFsForTesting : public HttpFs { |
| public: |
| HttpFsForTesting(StringMap_t map, PepperInterface* ppapi) { |
| FsInitArgs args(1); |
| args.string_map = map; |
| args.ppapi = ppapi; |
| EXPECT_EQ(0, Init(args)); |
| } |
| |
| using HttpFs::GetNodeCacheForTesting; |
| using HttpFs::ParseManifest; |
| using HttpFs::FindOrCreateDir; |
| }; |
| |
| enum { |
| kStringMapParamCacheNone = 0, |
| kStringMapParamCacheContent = 1, |
| kStringMapParamCacheStat = 2, |
| kStringMapParamCacheContentStat = |
| kStringMapParamCacheContent | kStringMapParamCacheStat, |
| }; |
| typedef uint32_t StringMapParam; |
| |
| StringMap_t MakeStringMap(StringMapParam param) { |
| StringMap_t smap; |
| if (param & kStringMapParamCacheContent) |
| smap["cache_content"] = "true"; |
| else |
| smap["cache_content"] = "false"; |
| |
| if (param & kStringMapParamCacheStat) |
| smap["cache_stat"] = "true"; |
| else |
| smap["cache_stat"] = "false"; |
| return smap; |
| } |
| |
| class HttpFsTest : public ::testing::TestWithParam<StringMapParam> { |
| public: |
| HttpFsTest(); |
| |
| protected: |
| FakePepperInterfaceURLLoader ppapi_; |
| HttpFsForTesting fs_; |
| }; |
| |
| HttpFsTest::HttpFsTest() : fs_(MakeStringMap(GetParam()), &ppapi_) {} |
| |
| class HttpFsLargeFileTest : public HttpFsTest { |
| public: |
| HttpFsLargeFileTest() {} |
| }; |
| |
| } // namespace |
| |
| TEST_P(HttpFsTest, OpenAndCloseServerError) { |
| EXPECT_TRUE(ppapi_.server_template()->AddError("file", 500)); |
| |
| ScopedNode node; |
| ASSERT_EQ(EIO, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| } |
| |
| TEST_P(HttpFsTest, ReadPartial) { |
| const char contents[] = "0123456789abcdefg"; |
| ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); |
| ppapi_.server_template()->set_allow_partial(true); |
| |
| int result_bytes = 0; |
| |
| char buf[10]; |
| memset(&buf[0], 0, sizeof(buf)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| HandleAttr attr; |
| EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| EXPECT_EQ(sizeof(buf) - 1, result_bytes); |
| EXPECT_STREQ("012345678", &buf[0]); |
| |
| // Read is clamped when reading past the end of the file. |
| attr.offs = 10; |
| ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| ASSERT_EQ(strlen("abcdefg"), result_bytes); |
| buf[result_bytes] = 0; |
| EXPECT_STREQ("abcdefg", &buf[0]); |
| |
| // Read nothing when starting past the end of the file. |
| attr.offs = 100; |
| EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes)); |
| EXPECT_EQ(0, result_bytes); |
| } |
| |
| TEST_P(HttpFsTest, ReadPartialNoServerSupport) { |
| const char contents[] = "0123456789abcdefg"; |
| ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); |
| ppapi_.server_template()->set_allow_partial(false); |
| |
| int result_bytes = 0; |
| |
| char buf[10]; |
| memset(&buf[0], 0, sizeof(buf)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| HandleAttr attr; |
| EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| EXPECT_EQ(sizeof(buf) - 1, result_bytes); |
| EXPECT_STREQ("012345678", &buf[0]); |
| |
| // Read is clamped when reading past the end of the file. |
| attr.offs = 10; |
| ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| ASSERT_EQ(strlen("abcdefg"), result_bytes); |
| buf[result_bytes] = 0; |
| EXPECT_STREQ("abcdefg", &buf[0]); |
| |
| // Read nothing when starting past the end of the file. |
| attr.offs = 100; |
| EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes)); |
| EXPECT_EQ(0, result_bytes); |
| } |
| |
| TEST_P(HttpFsTest, Write) { |
| const char contents[] = "contents"; |
| ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_WRONLY, &node)); |
| |
| // Writing always fails. |
| HandleAttr attr; |
| attr.offs = 3; |
| int bytes_written = 1; // Set to a non-zero value. |
| EXPECT_EQ(EACCES, node->Write(attr, "struct", 6, &bytes_written)); |
| EXPECT_EQ(0, bytes_written); |
| } |
| |
| TEST_P(HttpFsTest, GetStat) { |
| const char contents[] = "contents"; |
| ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| |
| struct stat statbuf; |
| EXPECT_EQ(0, node->GetStat(&statbuf)); |
| EXPECT_EQ(S_IFREG | S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode); |
| EXPECT_EQ(strlen(contents), statbuf.st_size); |
| // These are not currently set. |
| EXPECT_EQ(0, statbuf.st_atime); |
| EXPECT_EQ(0, statbuf.st_ctime); |
| EXPECT_EQ(0, statbuf.st_mtime); |
| } |
| |
| TEST_P(HttpFsTest, FTruncate) { |
| const char contents[] = "contents"; |
| ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDWR, &node)); |
| EXPECT_EQ(EACCES, node->FTruncate(4)); |
| } |
| |
| // Instantiate the above tests for all caching types. |
| INSTANTIATE_TEST_SUITE_P( |
| Default, |
| HttpFsTest, |
| ::testing::Values((uint32_t)kStringMapParamCacheNone, |
| (uint32_t)kStringMapParamCacheContent, |
| (uint32_t)kStringMapParamCacheStat, |
| (uint32_t)kStringMapParamCacheContentStat)); |
| |
| TEST_P(HttpFsLargeFileTest, ReadPartial) { |
| const char contents[] = "0123456789abcdefg"; |
| off_t size = 0x110000000ll; |
| ASSERT_TRUE( |
| ppapi_.server_template()->AddEntity("file", contents, size, NULL)); |
| ppapi_.server_template()->set_send_content_length(true); |
| ppapi_.server_template()->set_allow_partial(true); |
| |
| int result_bytes = 0; |
| |
| char buf[10]; |
| memset(&buf[0], 0, sizeof(buf)); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| HandleAttr attr; |
| EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| EXPECT_EQ(sizeof(buf) - 1, result_bytes); |
| EXPECT_STREQ("012345678", &buf[0]); |
| |
| // Read is clamped when reading past the end of the file. |
| attr.offs = size - 7; |
| ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes)); |
| ASSERT_EQ(strlen("abcdefg"), result_bytes); |
| buf[result_bytes] = 0; |
| EXPECT_STREQ("abcdefg", &buf[0]); |
| |
| // Read nothing when starting past the end of the file. |
| attr.offs = size + 100; |
| EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes)); |
| EXPECT_EQ(0, result_bytes); |
| } |
| |
| TEST_P(HttpFsLargeFileTest, GetStat) { |
| const char contents[] = "contents"; |
| off_t size = 0x110000000ll; |
| ASSERT_TRUE( |
| ppapi_.server_template()->AddEntity("file", contents, size, NULL)); |
| // TODO(binji): If the server doesn't send the content length, this operation |
| // will be incredibly slow; it will attempt to read all of the data from the |
| // server to find the file length. Can we do anything smarter? |
| ppapi_.server_template()->set_send_content_length(true); |
| |
| ScopedNode node; |
| ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node)); |
| |
| struct stat statbuf; |
| EXPECT_EQ(0, node->GetStat(&statbuf)); |
| EXPECT_TRUE(S_ISREG(statbuf.st_mode)); |
| EXPECT_EQ(S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode & S_MODEBITS); |
| EXPECT_EQ(size, statbuf.st_size); |
| // These are not currently set. |
| EXPECT_EQ(0, statbuf.st_atime); |
| EXPECT_EQ(0, statbuf.st_ctime); |
| EXPECT_EQ(0, statbuf.st_mtime); |
| } |
| |
| // Instantiate the large file tests, only when cache content is off. |
| // TODO(binji): make cache content smarter, so it doesn't try to cache enormous |
| // files. See http://crbug.com/369279. |
| INSTANTIATE_TEST_SUITE_P(Default, |
| HttpFsLargeFileTest, |
| ::testing::Values((uint32_t)kStringMapParamCacheNone, |
| (uint32_t)kStringMapParamCacheStat)); |
| |
| TEST(HttpFsDirTest, Root) { |
| StringMap_t args; |
| HttpFsForTesting fs(args, NULL); |
| |
| // Check root node is directory |
| ScopedNode node; |
| ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node)); |
| ASSERT_TRUE(node->IsaDir()); |
| |
| // We have to r+w access to the root node |
| struct stat buf; |
| ASSERT_EQ(0, node->GetStat(&buf)); |
| ASSERT_EQ(S_IXUSR | S_IRUSR, buf.st_mode & S_IRWXU); |
| } |
| |
| TEST(HttpFsDirTest, Mkdir) { |
| StringMap_t args; |
| HttpFsForTesting fs(args, NULL); |
| char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n"; |
| ASSERT_EQ(0, fs.ParseManifest(manifest)); |
| // mkdir of existing directories should give "File exists". |
| EXPECT_EQ(EEXIST, fs.Mkdir(Path("/"), 0)); |
| EXPECT_EQ(EEXIST, fs.Mkdir(Path("/mydir"), 0)); |
| // mkdir of non-existent directories should give "Permission denied". |
| EXPECT_EQ(EACCES, fs.Mkdir(Path("/non_existent"), 0)); |
| } |
| |
| TEST(HttpFsDirTest, Rmdir) { |
| StringMap_t args; |
| HttpFsForTesting fs(args, NULL); |
| char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n"; |
| ASSERT_EQ(0, fs.ParseManifest(manifest)); |
| // Rmdir on existing dirs should give "Permission Denied" |
| EXPECT_EQ(EACCES, fs.Rmdir(Path("/"))); |
| EXPECT_EQ(EACCES, fs.Rmdir(Path("/mydir"))); |
| // Rmdir on existing files should give "Not a direcotory" |
| EXPECT_EQ(ENOTDIR, fs.Rmdir(Path("/mydir/foo"))); |
| // Rmdir on non-existent files should give "No such file or directory" |
| EXPECT_EQ(ENOENT, fs.Rmdir(Path("/non_existent"))); |
| } |
| |
| TEST(HttpFsDirTest, Unlink) { |
| StringMap_t args; |
| HttpFsForTesting fs(args, NULL); |
| char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n"; |
| ASSERT_EQ(0, fs.ParseManifest(manifest)); |
| // Unlink of existing files should give "Permission Denied" |
| EXPECT_EQ(EACCES, fs.Unlink(Path("/mydir/foo"))); |
| // Unlink of existing directory should give "Is a directory" |
| EXPECT_EQ(EISDIR, fs.Unlink(Path("/mydir"))); |
| // Unlink of non-existent files should give "No such file or directory" |
| EXPECT_EQ(ENOENT, fs.Unlink(Path("/non_existent"))); |
| } |
| |
| TEST(HttpFsDirTest, Remove) { |
| StringMap_t args; |
| HttpFsForTesting fs(args, NULL); |
| char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n"; |
| ASSERT_EQ(0, fs.ParseManifest(manifest)); |
| // Remove of existing files should give "Permission Denied" |
| EXPECT_EQ(EACCES, fs.Remove(Path("/mydir/foo"))); |
| // Remove of existing directory should give "Permission Denied" |
| EXPECT_EQ(EACCES, fs.Remove(Path("/mydir"))); |
| // Unlink of non-existent files should give "No such file or directory" |
| EXPECT_EQ(ENOENT, fs.Remove(Path("/non_existent"))); |
| } |
| |
| TEST(HttpFsDirTest, ParseManifest) { |
| StringMap_t args; |
| off_t result_size = 0; |
| |
| HttpFsForTesting fs(args, NULL); |
| |
| // Multiple consecutive newlines or spaces should be ignored. |
| char manifest[] = "-r-- 123 /mydir/foo\n\n-rw- 234 /thatdir/bar\n"; |
| ASSERT_EQ(0, fs.ParseManifest(manifest)); |
| |
| ScopedNode root; |
| EXPECT_EQ(0, fs.FindOrCreateDir(Path("/"), &root)); |
| ASSERT_NE((Node*)NULL, root.get()); |
| EXPECT_EQ(2, root->ChildCount()); |
| |
| ScopedNode dir; |
| EXPECT_EQ(0, fs.FindOrCreateDir(Path("/mydir"), &dir)); |
| ASSERT_NE((Node*)NULL, dir.get()); |
| EXPECT_EQ(1, dir->ChildCount()); |
| |
| Node* node = (*fs.GetNodeCacheForTesting())["/mydir/foo"].get(); |
| EXPECT_NE((Node*)NULL, node); |
| EXPECT_EQ(0, node->GetSize(&result_size)); |
| EXPECT_EQ(123, result_size); |
| |
| // Since these files are cached thanks to the manifest, we can open them |
| // without accessing the PPAPI URL API. |
| ScopedNode foo; |
| ASSERT_EQ(0, fs.Open(Path("/mydir/foo"), O_RDONLY, &foo)); |
| |
| ScopedNode bar; |
| ASSERT_EQ(0, fs.Open(Path("/thatdir/bar"), O_RDWR, &bar)); |
| |
| struct stat sfoo; |
| struct stat sbar; |
| |
| EXPECT_FALSE(foo->GetStat(&sfoo)); |
| EXPECT_FALSE(bar->GetStat(&sbar)); |
| |
| EXPECT_EQ(123, sfoo.st_size); |
| EXPECT_EQ(S_IFREG | S_IRALL, sfoo.st_mode); |
| |
| EXPECT_EQ(234, sbar.st_size); |
| EXPECT_EQ(S_IFREG | S_IRALL | S_IWALL, sbar.st_mode); |
| } |
| |
| TEST(HttpFsBlobUrlTest, Basic) { |
| const char* kUrl = "blob:http://example.com/6b87a5a6-713e"; |
| const char* kContent = "hello"; |
| FakePepperInterfaceURLLoader ppapi; |
| ASSERT_TRUE(ppapi.server_template()->SetBlobEntity(kUrl, kContent, NULL)); |
| |
| StringMap_t args; |
| args["SOURCE"] = kUrl; |
| |
| HttpFsForTesting fs(args, &ppapi); |
| |
| // Any other path than / should fail. |
| ScopedNode node; |
| ASSERT_EQ(ENOENT, fs.Open(Path("/blah"), R_OK, &node)); |
| |
| // Check access to blob file |
| ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node)); |
| ASSERT_EQ(true, node->IsaFile()); |
| |
| // Verify file size and permissions |
| struct stat buf; |
| ASSERT_EQ(0, node->GetStat(&buf)); |
| ASSERT_EQ(S_IRUSR, buf.st_mode & S_IRWXU); |
| ASSERT_EQ(strlen(kContent), buf.st_size); |
| } |