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