Home | History | Annotate | Download | only in nacl_io_test
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <fcntl.h>
      6 #include <gmock/gmock.h>
      7 #include <ppapi/c/ppb_file_io.h>
      8 #include <ppapi/c/pp_errors.h>
      9 #include <ppapi/c/pp_instance.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 
     13 #include "fake_ppapi/fake_pepper_interface_url_loader.h"
     14 
     15 #include "nacl_io/dir_node.h"
     16 #include "nacl_io/httpfs/http_fs.h"
     17 #include "nacl_io/kernel_handle.h"
     18 #include "nacl_io/kernel_intercept.h"
     19 #include "nacl_io/osdirent.h"
     20 #include "nacl_io/osunistd.h"
     21 
     22 using namespace nacl_io;
     23 
     24 namespace {
     25 
     26 class HttpFsForTesting : public HttpFs {
     27  public:
     28   HttpFsForTesting(StringMap_t map, PepperInterface* ppapi) {
     29     FsInitArgs args(1);
     30     args.string_map = map;
     31     args.ppapi = ppapi;
     32     EXPECT_EQ(0, Init(args));
     33   }
     34 
     35   using HttpFs::GetNodeCacheForTesting;
     36   using HttpFs::ParseManifest;
     37   using HttpFs::FindOrCreateDir;
     38 };
     39 
     40 enum {
     41   kStringMapParamCacheNone = 0,
     42   kStringMapParamCacheContent = 1,
     43   kStringMapParamCacheStat = 2,
     44   kStringMapParamCacheContentStat =
     45       kStringMapParamCacheContent | kStringMapParamCacheStat,
     46 };
     47 typedef uint32_t StringMapParam;
     48 
     49 StringMap_t MakeStringMap(StringMapParam param) {
     50   StringMap_t smap;
     51   if (param & kStringMapParamCacheContent)
     52     smap["cache_content"] = "true";
     53   else
     54     smap["cache_content"] = "false";
     55 
     56   if (param & kStringMapParamCacheStat)
     57     smap["cache_stat"] = "true";
     58   else
     59     smap["cache_stat"] = "false";
     60   return smap;
     61 }
     62 
     63 class HttpFsTest : public ::testing::TestWithParam<StringMapParam> {
     64  public:
     65   HttpFsTest();
     66 
     67  protected:
     68   FakePepperInterfaceURLLoader ppapi_;
     69   HttpFsForTesting fs_;
     70 };
     71 
     72 HttpFsTest::HttpFsTest() : fs_(MakeStringMap(GetParam()), &ppapi_) {}
     73 
     74 class HttpFsLargeFileTest : public HttpFsTest {
     75  public:
     76   HttpFsLargeFileTest() {}
     77 };
     78 
     79 }  // namespace
     80 
     81 TEST_P(HttpFsTest, OpenAndCloseServerError) {
     82   EXPECT_TRUE(ppapi_.server_template()->AddError("file", 500));
     83 
     84   ScopedNode node;
     85   ASSERT_EQ(EIO, fs_.Open(Path("/file"), O_RDONLY, &node));
     86 }
     87 
     88 TEST_P(HttpFsTest, ReadPartial) {
     89   const char contents[] = "0123456789abcdefg";
     90   ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
     91   ppapi_.server_template()->set_allow_partial(true);
     92 
     93   int result_bytes = 0;
     94 
     95   char buf[10];
     96   memset(&buf[0], 0, sizeof(buf));
     97 
     98   ScopedNode node;
     99   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
    100   HandleAttr attr;
    101   EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    102   EXPECT_EQ(sizeof(buf) - 1, result_bytes);
    103   EXPECT_STREQ("012345678", &buf[0]);
    104 
    105   // Read is clamped when reading past the end of the file.
    106   attr.offs = 10;
    107   ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    108   ASSERT_EQ(strlen("abcdefg"), result_bytes);
    109   buf[result_bytes] = 0;
    110   EXPECT_STREQ("abcdefg", &buf[0]);
    111 
    112   // Read nothing when starting past the end of the file.
    113   attr.offs = 100;
    114   EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
    115   EXPECT_EQ(0, result_bytes);
    116 }
    117 
    118 TEST_P(HttpFsTest, ReadPartialNoServerSupport) {
    119   const char contents[] = "0123456789abcdefg";
    120   ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
    121   ppapi_.server_template()->set_allow_partial(false);
    122 
    123   int result_bytes = 0;
    124 
    125   char buf[10];
    126   memset(&buf[0], 0, sizeof(buf));
    127 
    128   ScopedNode node;
    129   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
    130   HandleAttr attr;
    131   EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    132   EXPECT_EQ(sizeof(buf) - 1, result_bytes);
    133   EXPECT_STREQ("012345678", &buf[0]);
    134 
    135   // Read is clamped when reading past the end of the file.
    136   attr.offs = 10;
    137   ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    138   ASSERT_EQ(strlen("abcdefg"), result_bytes);
    139   buf[result_bytes] = 0;
    140   EXPECT_STREQ("abcdefg", &buf[0]);
    141 
    142   // Read nothing when starting past the end of the file.
    143   attr.offs = 100;
    144   EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
    145   EXPECT_EQ(0, result_bytes);
    146 }
    147 
    148 TEST_P(HttpFsTest, Write) {
    149   const char contents[] = "contents";
    150   ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
    151 
    152   ScopedNode node;
    153   ASSERT_EQ(0, fs_.Open(Path("/file"), O_WRONLY, &node));
    154 
    155   // Writing always fails.
    156   HandleAttr attr;
    157   attr.offs = 3;
    158   int bytes_written = 1;  // Set to a non-zero value.
    159   EXPECT_EQ(EACCES, node->Write(attr, "struct", 6, &bytes_written));
    160   EXPECT_EQ(0, bytes_written);
    161 }
    162 
    163 TEST_P(HttpFsTest, GetStat) {
    164   const char contents[] = "contents";
    165   ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
    166 
    167   ScopedNode node;
    168   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
    169 
    170   struct stat statbuf;
    171   EXPECT_EQ(0, node->GetStat(&statbuf));
    172   EXPECT_EQ(S_IFREG | S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode);
    173   EXPECT_EQ(strlen(contents), statbuf.st_size);
    174   // These are not currently set.
    175   EXPECT_EQ(0, statbuf.st_atime);
    176   EXPECT_EQ(0, statbuf.st_ctime);
    177   EXPECT_EQ(0, statbuf.st_mtime);
    178 }
    179 
    180 TEST_P(HttpFsTest, FTruncate) {
    181   const char contents[] = "contents";
    182   ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
    183 
    184   ScopedNode node;
    185   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDWR, &node));
    186   EXPECT_EQ(EACCES, node->FTruncate(4));
    187 }
    188 
    189 // Instantiate the above tests for all caching types.
    190 INSTANTIATE_TEST_CASE_P(
    191     Default,
    192     HttpFsTest,
    193     ::testing::Values((uint32_t)kStringMapParamCacheNone,
    194                       (uint32_t)kStringMapParamCacheContent,
    195                       (uint32_t)kStringMapParamCacheStat,
    196                       (uint32_t)kStringMapParamCacheContentStat));
    197 
    198 TEST_P(HttpFsLargeFileTest, ReadPartial) {
    199   const char contents[] = "0123456789abcdefg";
    200   off_t size = 0x110000000ll;
    201   ASSERT_TRUE(
    202       ppapi_.server_template()->AddEntity("file", contents, size, NULL));
    203   ppapi_.server_template()->set_send_content_length(true);
    204   ppapi_.server_template()->set_allow_partial(true);
    205 
    206   int result_bytes = 0;
    207 
    208   char buf[10];
    209   memset(&buf[0], 0, sizeof(buf));
    210 
    211   ScopedNode node;
    212   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
    213   HandleAttr attr;
    214   EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    215   EXPECT_EQ(sizeof(buf) - 1, result_bytes);
    216   EXPECT_STREQ("012345678", &buf[0]);
    217 
    218   // Read is clamped when reading past the end of the file.
    219   attr.offs = size - 7;
    220   ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
    221   ASSERT_EQ(strlen("abcdefg"), result_bytes);
    222   buf[result_bytes] = 0;
    223   EXPECT_STREQ("abcdefg", &buf[0]);
    224 
    225   // Read nothing when starting past the end of the file.
    226   attr.offs = size + 100;
    227   EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
    228   EXPECT_EQ(0, result_bytes);
    229 }
    230 
    231 TEST_P(HttpFsLargeFileTest, GetStat) {
    232   const char contents[] = "contents";
    233   off_t size = 0x110000000ll;
    234   ASSERT_TRUE(
    235       ppapi_.server_template()->AddEntity("file", contents, size, NULL));
    236   // TODO(binji): If the server doesn't send the content length, this operation
    237   // will be incredibly slow; it will attempt to read all of the data from the
    238   // server to find the file length. Can we do anything smarter?
    239   ppapi_.server_template()->set_send_content_length(true);
    240 
    241   ScopedNode node;
    242   ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
    243 
    244   struct stat statbuf;
    245   EXPECT_EQ(0, node->GetStat(&statbuf));
    246   EXPECT_EQ(S_IFREG | S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode);
    247   EXPECT_EQ(size, statbuf.st_size);
    248   // These are not currently set.
    249   EXPECT_EQ(0, statbuf.st_atime);
    250   EXPECT_EQ(0, statbuf.st_ctime);
    251   EXPECT_EQ(0, statbuf.st_mtime);
    252 }
    253 
    254 // Instantiate the large file tests, only when cache content is off.
    255 // TODO(binji): make cache content smarter, so it doesn't try to cache enormous
    256 // files. See http://crbug.com/369279.
    257 INSTANTIATE_TEST_CASE_P(Default,
    258                         HttpFsLargeFileTest,
    259                         ::testing::Values((uint32_t)kStringMapParamCacheNone,
    260                                           (uint32_t)kStringMapParamCacheStat));
    261 
    262 TEST(HttpFsDirTest, Root) {
    263   StringMap_t args;
    264   HttpFsForTesting fs(args, NULL);
    265 
    266   // Check root node is directory
    267   ScopedNode node;
    268   ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node));
    269   ASSERT_TRUE(node->IsaDir());
    270 
    271   // We have to r+w access to the root node
    272   struct stat buf;
    273   ASSERT_EQ(0, node->GetStat(&buf));
    274   ASSERT_EQ(S_IXUSR | S_IRUSR, buf.st_mode & S_IRWXU);
    275 }
    276 
    277 TEST(HttpFsDirTest, Mkdir) {
    278   StringMap_t args;
    279   HttpFsForTesting fs(args, NULL);
    280   char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
    281   ASSERT_EQ(0, fs.ParseManifest(manifest));
    282   // mkdir of existing directories should give "File exists".
    283   EXPECT_EQ(EEXIST, fs.Mkdir(Path("/"), 0));
    284   EXPECT_EQ(EEXIST, fs.Mkdir(Path("/mydir"), 0));
    285   // mkdir of non-existent directories should give "Permission denied".
    286   EXPECT_EQ(EACCES, fs.Mkdir(Path("/non_existent"), 0));
    287 }
    288 
    289 TEST(HttpFsDirTest, Rmdir) {
    290   StringMap_t args;
    291   HttpFsForTesting fs(args, NULL);
    292   char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
    293   ASSERT_EQ(0, fs.ParseManifest(manifest));
    294   // Rmdir on existing dirs should give "Permission Denied"
    295   EXPECT_EQ(EACCES, fs.Rmdir(Path("/")));
    296   EXPECT_EQ(EACCES, fs.Rmdir(Path("/mydir")));
    297   // Rmdir on existing files should give "Not a direcotory"
    298   EXPECT_EQ(ENOTDIR, fs.Rmdir(Path("/mydir/foo")));
    299   // Rmdir on non-existent files should give "No such file or directory"
    300   EXPECT_EQ(ENOENT, fs.Rmdir(Path("/non_existent")));
    301 }
    302 
    303 TEST(HttpFsDirTest, Unlink) {
    304   StringMap_t args;
    305   HttpFsForTesting fs(args, NULL);
    306   char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
    307   ASSERT_EQ(0, fs.ParseManifest(manifest));
    308   // Unlink of existing files should give "Permission Denied"
    309   EXPECT_EQ(EACCES, fs.Unlink(Path("/mydir/foo")));
    310   // Unlink of existing directory should give "Is a directory"
    311   EXPECT_EQ(EISDIR, fs.Unlink(Path("/mydir")));
    312   // Unlink of non-existent files should give "No such file or directory"
    313   EXPECT_EQ(ENOENT, fs.Unlink(Path("/non_existent")));
    314 }
    315 
    316 TEST(HttpFsDirTest, Remove) {
    317   StringMap_t args;
    318   HttpFsForTesting fs(args, NULL);
    319   char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
    320   ASSERT_EQ(0, fs.ParseManifest(manifest));
    321   // Remove of existing files should give "Permission Denied"
    322   EXPECT_EQ(EACCES, fs.Remove(Path("/mydir/foo")));
    323   // Remove of existing directory should give "Permission Denied"
    324   EXPECT_EQ(EACCES, fs.Remove(Path("/mydir")));
    325   // Unlink of non-existent files should give "No such file or directory"
    326   EXPECT_EQ(ENOENT, fs.Remove(Path("/non_existent")));
    327 }
    328 
    329 TEST(HttpFsDirTest, ParseManifest) {
    330   StringMap_t args;
    331   off_t result_size = 0;
    332 
    333   HttpFsForTesting fs(args, NULL);
    334 
    335   // Multiple consecutive newlines or spaces should be ignored.
    336   char manifest[] = "-r-- 123 /mydir/foo\n\n-rw-   234  /thatdir/bar\n";
    337   ASSERT_EQ(0, fs.ParseManifest(manifest));
    338 
    339   ScopedNode root;
    340   EXPECT_EQ(0, fs.FindOrCreateDir(Path("/"), &root));
    341   ASSERT_NE((Node*)NULL, root.get());
    342   EXPECT_EQ(2, root->ChildCount());
    343 
    344   ScopedNode dir;
    345   EXPECT_EQ(0, fs.FindOrCreateDir(Path("/mydir"), &dir));
    346   ASSERT_NE((Node*)NULL, dir.get());
    347   EXPECT_EQ(1, dir->ChildCount());
    348 
    349   Node* node = (*fs.GetNodeCacheForTesting())["/mydir/foo"].get();
    350   EXPECT_NE((Node*)NULL, node);
    351   EXPECT_EQ(0, node->GetSize(&result_size));
    352   EXPECT_EQ(123, result_size);
    353 
    354   // Since these files are cached thanks to the manifest, we can open them
    355   // without accessing the PPAPI URL API.
    356   ScopedNode foo;
    357   ASSERT_EQ(0, fs.Open(Path("/mydir/foo"), O_RDONLY, &foo));
    358 
    359   ScopedNode bar;
    360   ASSERT_EQ(0, fs.Open(Path("/thatdir/bar"), O_RDWR, &bar));
    361 
    362   struct stat sfoo;
    363   struct stat sbar;
    364 
    365   EXPECT_FALSE(foo->GetStat(&sfoo));
    366   EXPECT_FALSE(bar->GetStat(&sbar));
    367 
    368   EXPECT_EQ(123, sfoo.st_size);
    369   EXPECT_EQ(S_IFREG | S_IRALL, sfoo.st_mode);
    370 
    371   EXPECT_EQ(234, sbar.st_size);
    372   EXPECT_EQ(S_IFREG | S_IRALL | S_IWALL, sbar.st_mode);
    373 }
    374 
    375 TEST(HttpFsBlobUrlTest, Basic) {
    376   const char* kUrl = "blob:http%3A//example.com/6b87a5a6-713e";
    377   const char* kContent = "hello";
    378   FakePepperInterfaceURLLoader ppapi;
    379   ASSERT_TRUE(ppapi.server_template()->SetBlobEntity(kUrl, kContent, NULL));
    380 
    381   StringMap_t args;
    382   args["SOURCE"] = kUrl;
    383 
    384   HttpFsForTesting fs(args, &ppapi);
    385 
    386   // Any other path than / should fail.
    387   ScopedNode node;
    388   ASSERT_EQ(ENOENT, fs.Open(Path("/blah"), R_OK, &node));
    389 
    390   // Check access to blob file
    391   ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node));
    392   ASSERT_EQ(true, node->IsaFile());
    393 
    394   // Verify file size and permissions
    395   struct stat buf;
    396   ASSERT_EQ(0, node->GetStat(&buf));
    397   ASSERT_EQ(S_IRUSR, buf.st_mode & S_IRWXU);
    398   ASSERT_EQ(strlen(kContent), buf.st_size);
    399 }
    400