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 <errno.h>
6#include <fcntl.h>
7
8#include <set>
9#include <string>
10
11#include <gmock/gmock.h>
12#include <ppapi/c/ppb_file_io.h>
13#include <ppapi/c/pp_directory_entry.h>
14#include <ppapi/c/pp_errors.h>
15#include <ppapi/c/pp_instance.h>
16#if defined(WIN32)
17#include <windows.h>  // For Sleep()
18#endif
19
20#include "fake_ppapi/fake_pepper_interface_html5_fs.h"
21#include "nacl_io/kernel_handle.h"
22#include "nacl_io/html5fs/html5_fs.h"
23#include "nacl_io/osdirent.h"
24#include "nacl_io/osunistd.h"
25#include "nacl_io/pepper_interface_delegate.h"
26#include "sdk_util/scoped_ref.h"
27#include "mock_util.h"
28#include "pepper_interface_mock.h"
29
30using namespace nacl_io;
31using namespace sdk_util;
32
33using ::testing::_;
34using ::testing::DoAll;
35using ::testing::Invoke;
36using ::testing::Mock;
37using ::testing::Return;
38
39namespace {
40
41class Html5FsForTesting : public Html5Fs {
42 public:
43  Html5FsForTesting(StringMap_t& string_map, PepperInterface* ppapi,
44                    int expected_error = 0) {
45    FsInitArgs args;
46    args.string_map = string_map;
47    args.ppapi = ppapi;
48    Error error = Init(args);
49    EXPECT_EQ(expected_error, error);
50  }
51
52  bool Exists(const char* filename) {
53    ScopedNode node;
54    if (Open(Path(filename), O_RDONLY, &node))
55      return false;
56
57    struct stat buf;
58    return node->GetStat(&buf) == 0;
59  }
60};
61
62class Html5FsTest : public ::testing::Test {
63 public:
64  Html5FsTest();
65
66 protected:
67  FakePepperInterfaceHtml5Fs ppapi_html5_;
68  PepperInterfaceMock ppapi_mock_;
69  PepperInterfaceDelegate ppapi_;
70};
71
72Html5FsTest::Html5FsTest()
73    : ppapi_mock_(ppapi_html5_.GetInstance()),
74      ppapi_(ppapi_html5_.GetInstance()) {
75  // Default delegation to the html5 pepper interface.
76  ppapi_.SetCoreInterfaceDelegate(ppapi_html5_.GetCoreInterface());
77  ppapi_.SetFileSystemInterfaceDelegate(ppapi_html5_.GetFileSystemInterface());
78  ppapi_.SetFileRefInterfaceDelegate(ppapi_html5_.GetFileRefInterface());
79  ppapi_.SetFileIoInterfaceDelegate(ppapi_html5_.GetFileIoInterface());
80  ppapi_.SetVarInterfaceDelegate(ppapi_html5_.GetVarInterface());
81}
82
83}  // namespace
84
85TEST_F(Html5FsTest, FilesystemType) {
86  const char* filesystem_type_strings[] = {"", "PERSISTENT", "TEMPORARY", NULL};
87  PP_FileSystemType filesystem_type_values[] = {
88      PP_FILESYSTEMTYPE_LOCALPERSISTENT,  // Default to persistent.
89      PP_FILESYSTEMTYPE_LOCALPERSISTENT, PP_FILESYSTEMTYPE_LOCALTEMPORARY};
90
91  const char* expected_size_strings[] = {"100", "12345", NULL};
92  const int expected_size_values[] = {100, 12345};
93
94  FileSystemInterfaceMock* filesystem_mock =
95      ppapi_mock_.GetFileSystemInterface();
96
97  FakeFileSystemInterface* filesystem_fake =
98      static_cast<FakeFileSystemInterface*>(
99          ppapi_html5_.GetFileSystemInterface());
100
101  for (int i = 0; filesystem_type_strings[i] != NULL; ++i) {
102    const char* filesystem_type_string = filesystem_type_strings[i];
103    PP_FileSystemType expected_filesystem_type = filesystem_type_values[i];
104
105    for (int j = 0; expected_size_strings[j] != NULL; ++j) {
106      const char* expected_size_string = expected_size_strings[j];
107      int64_t expected_expected_size = expected_size_values[j];
108
109      ppapi_.SetFileSystemInterfaceDelegate(filesystem_mock);
110
111      ON_CALL(*filesystem_mock, Create(_, _)).WillByDefault(
112          Invoke(filesystem_fake, &FakeFileSystemInterface::Create));
113
114      EXPECT_CALL(*filesystem_mock,
115                  Create(ppapi_.GetInstance(), expected_filesystem_type));
116
117      EXPECT_CALL(*filesystem_mock, Open(_, expected_expected_size, _))
118          .WillOnce(DoAll(CallCallback<2>(int32_t(PP_OK)),
119                          Return(int32_t(PP_OK_COMPLETIONPENDING))));
120
121      StringMap_t map;
122      map["type"] = filesystem_type_string;
123      map["expected_size"] = expected_size_string;
124      ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
125
126      Mock::VerifyAndClearExpectations(&filesystem_mock);
127    }
128  }
129}
130
131TEST_F(Html5FsTest, PassFilesystemResource) {
132  // Fail if given a bad resource.
133  {
134    StringMap_t map;
135    map["filesystem_resource"] = "0";
136    ScopedRef<Html5FsForTesting> fs(
137        new Html5FsForTesting(map, &ppapi_, EINVAL));
138  }
139
140  {
141    EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
142    PP_Resource filesystem = ppapi_html5_.GetFileSystemInterface()->Create(
143        ppapi_html5_.GetInstance(), PP_FILESYSTEMTYPE_LOCALPERSISTENT);
144
145    ASSERT_EQ(int32_t(PP_OK), ppapi_html5_.GetFileSystemInterface()->Open(
146              filesystem, 0, PP_BlockUntilComplete()));
147
148    StringMap_t map;
149    char buffer[30];
150    snprintf(buffer, 30, "%d", filesystem);
151    map["filesystem_resource"] = buffer;
152    ScopedRef<Html5FsForTesting> fs(
153        new Html5FsForTesting(map, &ppapi_));
154
155    ASSERT_TRUE(fs->Exists("/foo"));
156
157    ppapi_html5_.GetCoreInterface()->ReleaseResource(filesystem);
158  }
159}
160
161TEST_F(Html5FsTest, MountSubtree) {
162  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo/bar",
163                                                               NULL));
164  StringMap_t map;
165  map["SOURCE"] = "/foo";
166  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
167
168  ASSERT_TRUE(fs->Exists("/bar"));
169  ASSERT_FALSE(fs->Exists("/foo/bar"));
170}
171
172TEST_F(Html5FsTest, Mkdir) {
173  StringMap_t map;
174  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
175
176  // mkdir at the root should return EEXIST, not EACCES.
177  EXPECT_EQ(EEXIST, fs->Mkdir(Path("/"), 0644));
178
179  Path path("/foo");
180  ASSERT_FALSE(fs->Exists("/foo"));
181  ASSERT_EQ(0, fs->Mkdir(path, 0644));
182
183  struct stat stat;
184  ScopedNode node;
185  ASSERT_EQ(0, fs->Open(path, O_RDONLY, &node));
186  EXPECT_EQ(0, node->GetStat(&stat));
187  EXPECT_EQ(S_IFDIR, stat.st_mode & S_IFDIR);
188}
189
190TEST_F(Html5FsTest, Remove) {
191  const char* kPath = "/foo";
192  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile(kPath, NULL));
193
194  StringMap_t map;
195  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
196
197  Path path(kPath);
198  ASSERT_TRUE(fs->Exists(kPath));
199  ASSERT_EQ(0, fs->Remove(path));
200  EXPECT_FALSE(fs->Exists(kPath));
201}
202
203TEST_F(Html5FsTest, Unlink) {
204  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
205  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
206
207  StringMap_t map;
208  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
209
210  ASSERT_TRUE(fs->Exists("/dir"));
211  ASSERT_TRUE(fs->Exists("/file"));
212  ASSERT_EQ(0, fs->Unlink(Path("/file")));
213  ASSERT_EQ(EISDIR, fs->Unlink(Path("/dir")));
214  EXPECT_FALSE(fs->Exists("/file"));
215  EXPECT_TRUE(fs->Exists("/dir"));
216}
217
218TEST_F(Html5FsTest, Rmdir) {
219  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/file", NULL));
220  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
221
222  StringMap_t map;
223  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
224
225  ASSERT_EQ(ENOTDIR, fs->Rmdir(Path("/file")));
226  EXPECT_EQ(0, fs->Rmdir(Path("/dir")));
227  EXPECT_FALSE(fs->Exists("/dir"));
228  EXPECT_TRUE(fs->Exists("/file"));
229}
230
231TEST_F(Html5FsTest, Rename) {
232  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddEmptyFile("/foo", NULL));
233
234  StringMap_t map;
235  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
236
237  ASSERT_TRUE(fs->Exists("/foo"));
238  ASSERT_EQ(0, fs->Rename(Path("/foo"), Path("/bar")));
239  EXPECT_FALSE(fs->Exists("/foo"));
240  EXPECT_TRUE(fs->Exists("/bar"));
241}
242
243TEST_F(Html5FsTest, OpenForCreate) {
244  StringMap_t map;
245  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
246
247  EXPECT_FALSE(fs->Exists("/foo"));
248
249  Path path("/foo");
250  ScopedNode node;
251  ASSERT_EQ(0, fs->Open(path, O_CREAT | O_RDWR, &node));
252
253  // Write some data.
254  char contents[] = "contents";
255  int bytes_written = 0;
256  EXPECT_EQ(0, node->Write(HandleAttr(), &contents[0], strlen(contents),
257                           &bytes_written));
258  EXPECT_EQ(strlen(contents), bytes_written);
259
260  // Create again.
261  ASSERT_EQ(0, fs->Open(path, O_CREAT, &node));
262
263  // Check that the file still has data.
264  off_t size;
265  EXPECT_EQ(0, node->GetSize(&size));
266  EXPECT_EQ(strlen(contents), size);
267
268  // Open exclusively.
269  EXPECT_EQ(EEXIST, fs->Open(path, O_CREAT | O_EXCL, &node));
270
271  // Try to truncate without write access.
272  EXPECT_EQ(EINVAL, fs->Open(path, O_CREAT | O_TRUNC, &node));
273
274  // Open and truncate.
275  ASSERT_EQ(0, fs->Open(path, O_CREAT | O_TRUNC | O_WRONLY, &node));
276
277  // File should be empty.
278  EXPECT_EQ(0, node->GetSize(&size));
279  EXPECT_EQ(0, size);
280}
281
282TEST_F(Html5FsTest, Read) {
283  const char contents[] = "contents";
284  ASSERT_TRUE(
285      ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
286  ASSERT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
287  StringMap_t map;
288  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
289
290  ScopedNode node;
291  ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
292
293  char buffer[10] = {0};
294  int bytes_read = 0;
295  HandleAttr attr;
296  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
297  ASSERT_EQ(strlen(contents), bytes_read);
298  ASSERT_STREQ(contents, buffer);
299
300  // Read nothing past the end of the file.
301  attr.offs = 100;
302  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
303  ASSERT_EQ(0, bytes_read);
304
305  // Read part of the data.
306  attr.offs = 4;
307  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
308  ASSERT_EQ(strlen(contents) - 4, bytes_read);
309  buffer[bytes_read] = 0;
310  ASSERT_STREQ("ents", buffer);
311
312  // Writing should fail.
313  int bytes_written = 1;  // Set to a non-zero value.
314  attr.offs = 0;
315  ASSERT_EQ(EACCES,
316            node->Write(attr, &buffer[0], sizeof(buffer), &bytes_written));
317  ASSERT_EQ(0, bytes_written);
318
319  // Reading from a directory should fail.
320  ASSERT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
321  ASSERT_EQ(EISDIR, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
322}
323
324TEST_F(Html5FsTest, Write) {
325  const char contents[] = "contents";
326  EXPECT_TRUE(
327      ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
328  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
329
330  StringMap_t map;
331  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
332
333  ScopedNode node;
334  ASSERT_EQ(0, fs->Open(Path("/file"), O_WRONLY, &node));
335
336  // Reading should fail.
337  char buffer[10];
338  int bytes_read = 1;  // Set to a non-zero value.
339  HandleAttr attr;
340  EXPECT_EQ(EACCES, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
341  EXPECT_EQ(0, bytes_read);
342
343  // Reopen as read-write.
344  ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
345
346  int bytes_written = 1;  // Set to a non-zero value.
347  attr.offs = 3;
348  EXPECT_EQ(0, node->Write(attr, "struct", 6, &bytes_written));
349  EXPECT_EQ(6, bytes_written);
350
351  attr.offs = 0;
352  EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
353  EXPECT_EQ(9, bytes_read);
354  buffer[bytes_read] = 0;
355  EXPECT_STREQ("construct", buffer);
356
357  // Writing to a directory should fail.
358  EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDWR, &node));
359  EXPECT_EQ(EISDIR, node->Write(attr, &buffer[0], sizeof(buffer), &bytes_read));
360}
361
362TEST_F(Html5FsTest, GetStat) {
363  const int creation_time = 1000;
364  const int access_time = 2000;
365  const int modified_time = 3000;
366  const char contents[] = "contents";
367
368  // Create fake file.
369  FakeHtml5FsNode* fake_node;
370  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddFile(
371      "/file", contents, &fake_node));
372  fake_node->set_creation_time(creation_time);
373  fake_node->set_last_access_time(access_time);
374  fake_node->set_last_modified_time(modified_time);
375
376  // Create fake directory.
377  EXPECT_TRUE(
378      ppapi_html5_.filesystem_template()->AddDirectory("/dir", &fake_node));
379  fake_node->set_creation_time(creation_time);
380  fake_node->set_last_access_time(access_time);
381  fake_node->set_last_modified_time(modified_time);
382
383  StringMap_t map;
384  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
385
386  ScopedNode node;
387  ASSERT_EQ(0, fs->Open(Path("/file"), O_RDONLY, &node));
388
389  struct stat statbuf;
390  EXPECT_EQ(0, node->GetStat(&statbuf));
391  EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
392  EXPECT_EQ(S_IRALL | S_IWALL | S_IXALL, statbuf.st_mode & ~S_IFMT);
393  EXPECT_EQ(strlen(contents), statbuf.st_size);
394  EXPECT_EQ(access_time, statbuf.st_atime);
395  EXPECT_EQ(creation_time, statbuf.st_ctime);
396  EXPECT_EQ(modified_time, statbuf.st_mtime);
397
398  // Test Get* and Isa* methods.
399  off_t size;
400  EXPECT_EQ(0, node->GetSize(&size));
401  EXPECT_EQ(strlen(contents), size);
402  EXPECT_FALSE(node->IsaDir());
403  EXPECT_TRUE(node->IsaFile());
404  EXPECT_EQ(ENOTTY, node->Isatty());
405
406  // GetStat on a directory...
407  EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
408  EXPECT_EQ(0, node->GetStat(&statbuf));
409  EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT);
410  EXPECT_EQ(S_IRALL | S_IWALL | S_IXALL, statbuf.st_mode & ~S_IFMT);
411  EXPECT_EQ(0, statbuf.st_size);
412  EXPECT_EQ(access_time, statbuf.st_atime);
413  EXPECT_EQ(creation_time, statbuf.st_ctime);
414  EXPECT_EQ(modified_time, statbuf.st_mtime);
415
416  // Test Get* and Isa* methods.
417  EXPECT_EQ(0, node->GetSize(&size));
418  EXPECT_EQ(0, size);
419  EXPECT_TRUE(node->IsaDir());
420  EXPECT_FALSE(node->IsaFile());
421  EXPECT_EQ(ENOTTY, node->Isatty());
422}
423
424TEST_F(Html5FsTest, FTruncate) {
425  const char contents[] = "contents";
426  EXPECT_TRUE(
427      ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
428  EXPECT_TRUE(ppapi_html5_.filesystem_template()->AddDirectory("/dir", NULL));
429
430  StringMap_t map;
431  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
432
433  ScopedNode node;
434  ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
435
436  HandleAttr attr;
437  char buffer[10] = {0};
438  int bytes_read = 0;
439
440  // First make the file shorter...
441  EXPECT_EQ(0, node->FTruncate(4));
442  EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
443  EXPECT_EQ(4, bytes_read);
444  buffer[bytes_read] = 0;
445  EXPECT_STREQ("cont", buffer);
446
447  // Now make the file longer...
448  EXPECT_EQ(0, node->FTruncate(8));
449  EXPECT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
450  EXPECT_EQ(8, bytes_read);
451  buffer[bytes_read] = 0;
452  EXPECT_STREQ("cont\0\0\0\0", buffer);
453
454  // Ftruncate should fail for a directory.
455  EXPECT_EQ(0, fs->Open(Path("/dir"), O_RDONLY, &node));
456  EXPECT_EQ(EISDIR, node->FTruncate(4));
457}
458
459TEST_F(Html5FsTest, GetDents) {
460  const char contents[] = "contents";
461  EXPECT_TRUE(
462      ppapi_html5_.filesystem_template()->AddFile("/file", contents, NULL));
463
464  StringMap_t map;
465  ScopedRef<Html5FsForTesting> fs(new Html5FsForTesting(map, &ppapi_));
466
467  ScopedNode root;
468  ASSERT_EQ(0, fs->Open(Path("/"), O_RDONLY, &root));
469
470  ScopedNode node;
471  ASSERT_EQ(0, fs->Open(Path("/file"), O_RDWR, &node));
472
473  struct stat stat;
474  ASSERT_EQ(0, node->GetStat(&stat));
475  ino_t file1_ino = stat.st_ino;
476
477  // Should fail for regular files.
478  const size_t kMaxDirents = 5;
479  dirent dirents[kMaxDirents];
480  int bytes_read = 1;  // Set to a non-zero value.
481
482  memset(&dirents[0], 0, sizeof(dirents));
483  EXPECT_EQ(ENOTDIR,
484            node->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
485  EXPECT_EQ(0, bytes_read);
486
487  // Should work with root directory.
488  // +2 to test a size that is not a multiple of sizeof(dirent).
489  // Expect it to round down.
490  memset(&dirents[0], 0, sizeof(dirents));
491  EXPECT_EQ(
492      0, root->GetDents(0, &dirents[0], sizeof(dirent) * 3 + 2, &bytes_read));
493
494  {
495    size_t num_dirents = bytes_read / sizeof(dirent);
496    EXPECT_EQ(3, num_dirents);
497    EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
498
499    std::multiset<std::string> dirnames;
500    for (size_t i = 0; i < num_dirents; ++i) {
501      EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
502      EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
503      dirnames.insert(dirents[i].d_name);
504    }
505
506    EXPECT_EQ(1, dirnames.count("file"));
507    EXPECT_EQ(1, dirnames.count("."));
508    EXPECT_EQ(1, dirnames.count(".."));
509  }
510
511  // Add another file...
512  ASSERT_EQ(0, fs->Open(Path("/file2"), O_CREAT, &node));
513  ASSERT_EQ(0, node->GetStat(&stat));
514  ino_t file2_ino = stat.st_ino;
515
516  // These files SHOULD not hash to the same value but COULD.
517  EXPECT_NE(file1_ino, file2_ino);
518
519  // Read the root directory again.
520  memset(&dirents[0], 0, sizeof(dirents));
521  EXPECT_EQ(0, root->GetDents(0, &dirents[0], sizeof(dirents), &bytes_read));
522
523  {
524    size_t num_dirents = bytes_read / sizeof(dirent);
525    EXPECT_EQ(4, num_dirents);
526    EXPECT_EQ(sizeof(dirent) * num_dirents, bytes_read);
527
528    std::multiset<std::string> dirnames;
529    for (size_t i = 0; i < num_dirents; ++i) {
530      EXPECT_EQ(sizeof(dirent), dirents[i].d_off);
531      EXPECT_EQ(sizeof(dirent), dirents[i].d_reclen);
532      dirnames.insert(dirents[i].d_name);
533
534      if (!strcmp(dirents[i].d_name, "file")) {
535        EXPECT_EQ(dirents[i].d_ino, file1_ino);
536      }
537      if (!strcmp(dirents[i].d_name, "file2")) {
538        EXPECT_EQ(dirents[i].d_ino, file2_ino);
539      }
540    }
541
542    EXPECT_EQ(1, dirnames.count("file"));
543    EXPECT_EQ(1, dirnames.count("file2"));
544    EXPECT_EQ(1, dirnames.count("."));
545    EXPECT_EQ(1, dirnames.count(".."));
546  }
547}
548