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
7#include <gtest/gtest.h>
8
9#include <string>
10#include <vector>
11
12#include "nacl_io/fuse.h"
13#include "nacl_io/fusefs/fuse_fs.h"
14#include "nacl_io/kernel_handle.h"
15#include "nacl_io/kernel_intercept.h"
16#include "nacl_io/kernel_proxy.h"
17#include "nacl_io/ostime.h"
18
19using namespace nacl_io;
20
21namespace {
22
23class FuseFsForTesting : public FuseFs {
24 public:
25  explicit FuseFsForTesting(fuse_operations* fuse_ops) {
26    FsInitArgs args;
27    args.fuse_ops = fuse_ops;
28    EXPECT_EQ(0, Init(args));
29  }
30};
31
32// Implementation of a simple flat memory filesystem.
33struct File {
34  File() : mode(0666) { memset(&times, 0, sizeof(times)); }
35
36  std::string name;
37  std::vector<uint8_t> data;
38  mode_t mode;
39  timespec times[2];
40};
41
42typedef std::vector<File> Files;
43Files g_files;
44
45bool IsValidPath(const char* path) {
46  if (path == NULL)
47    return false;
48
49  if (strlen(path) <= 1)
50    return false;
51
52  if (path[0] != '/')
53    return false;
54
55  return true;
56}
57
58File* FindFile(const char* path) {
59  if (!IsValidPath(path))
60    return NULL;
61
62  for (Files::iterator iter = g_files.begin(); iter != g_files.end(); ++iter) {
63    if (iter->name == &path[1])
64      return &*iter;
65  }
66
67  return NULL;
68}
69
70int testfs_getattr(const char* path, struct stat* stbuf) {
71  memset(stbuf, 0, sizeof(struct stat));
72
73  if (strcmp(path, "/") == 0) {
74    stbuf->st_mode = S_IFDIR | 0755;
75    return 0;
76  }
77
78  File* file = FindFile(path);
79  if (file == NULL)
80    return -ENOENT;
81
82  stbuf->st_mode = S_IFREG | file->mode;
83  stbuf->st_size = file->data.size();
84  stbuf->st_atime = file->times[0].tv_sec;
85  stbuf->st_atimensec = file->times[0].tv_nsec;
86  stbuf->st_mtime = file->times[1].tv_sec;
87  stbuf->st_mtimensec = file->times[1].tv_nsec;
88  return 0;
89}
90
91int testfs_readdir(const char* path,
92                   void* buf,
93                   fuse_fill_dir_t filler,
94                   off_t offset,
95                   struct fuse_file_info*) {
96  if (strcmp(path, "/") != 0)
97    return -ENOENT;
98
99  filler(buf, ".", NULL, 0);
100  filler(buf, "..", NULL, 0);
101  for (Files::iterator iter = g_files.begin(); iter != g_files.end(); ++iter) {
102    filler(buf, iter->name.c_str(), NULL, 0);
103  }
104  return 0;
105}
106
107int testfs_create(const char* path, mode_t mode, struct fuse_file_info* fi) {
108  if (!IsValidPath(path))
109    return -ENOENT;
110
111  File* file = FindFile(path);
112  if (file != NULL) {
113    if (fi->flags & O_EXCL)
114      return -EEXIST;
115  } else {
116    g_files.push_back(File());
117    file = &g_files.back();
118    file->name = &path[1];  // Skip initial /
119  }
120  file->mode = mode;
121
122  return 0;
123}
124
125int testfs_open(const char* path, struct fuse_file_info*) {
126  // open is only called to open an existing file, otherwise create is
127  // called. We don't need to do any additional work here, the path will be
128  // passed to any other operations.
129  return FindFile(path) != NULL;
130}
131
132int testfs_read(const char* path,
133                char* buf,
134                size_t size,
135                off_t offset,
136                struct fuse_file_info* fi) {
137  File* file = FindFile(path);
138  if (file == NULL)
139    return -ENOENT;
140
141  size_t filesize = file->data.size();
142  // Trying to read past the end of the file.
143  if (offset >= filesize)
144    return 0;
145
146  if (offset + size > filesize)
147    size = filesize - offset;
148
149  memcpy(buf, file->data.data() + offset, size);
150  return size;
151}
152
153int testfs_write(const char* path,
154                 const char* buf,
155                 size_t size,
156                 off_t offset,
157                 struct fuse_file_info*) {
158  File* file = FindFile(path);
159  if (file == NULL)
160    return -ENOENT;
161
162  size_t filesize = file->data.size();
163
164  if (offset + size > filesize)
165    file->data.resize(offset + size);
166
167  memcpy(file->data.data() + offset, buf, size);
168  return size;
169}
170
171int testfs_utimens(const char* path, const struct timespec times[2]) {
172  File* file = FindFile(path);
173  if (file == NULL)
174    return -ENOENT;
175
176  file->times[0] = times[0];
177  file->times[1] = times[1];
178  return 0;
179}
180
181int testfs_chmod(const char* path, mode_t mode) {
182  File* file = FindFile(path);
183  if (file == NULL)
184    return -ENOENT;
185
186  file->mode = mode;
187  return 0;
188}
189
190const char hello_world[] = "Hello, World!\n";
191
192fuse_operations g_fuse_operations = {
193    0,               // flag_nopath
194    0,               // flag_reserved
195    testfs_getattr,  // getattr
196    NULL,            // readlink
197    NULL,            // mknod
198    NULL,            // mkdir
199    NULL,            // unlink
200    NULL,            // rmdir
201    NULL,            // symlink
202    NULL,            // rename
203    NULL,            // link
204    testfs_chmod,    // chmod
205    NULL,            // chown
206    NULL,            // truncate
207    testfs_open,     // open
208    testfs_read,     // read
209    testfs_write,    // write
210    NULL,            // statfs
211    NULL,            // flush
212    NULL,            // release
213    NULL,            // fsync
214    NULL,            // setxattr
215    NULL,            // getxattr
216    NULL,            // listxattr
217    NULL,            // removexattr
218    NULL,            // opendir
219    testfs_readdir,  // readdir
220    NULL,            // releasedir
221    NULL,            // fsyncdir
222    NULL,            // init
223    NULL,            // destroy
224    NULL,            // access
225    testfs_create,   // create
226    NULL,            // ftruncate
227    NULL,            // fgetattr
228    NULL,            // lock
229    testfs_utimens,  // utimens
230    NULL,            // bmap
231    NULL,            // ioctl
232    NULL,            // poll
233    NULL,            // write_buf
234    NULL,            // read_buf
235    NULL,            // flock
236    NULL,            // fallocate
237};
238
239class FuseFsTest : public ::testing::Test {
240 public:
241  FuseFsTest();
242
243  void SetUp();
244
245 protected:
246  FuseFsForTesting fs_;
247};
248
249FuseFsTest::FuseFsTest() : fs_(&g_fuse_operations) {}
250
251void FuseFsTest::SetUp() {
252  // Reset the filesystem.
253  g_files.clear();
254
255  // Add a built-in file.
256  size_t hello_len = strlen(hello_world);
257
258  File hello;
259  hello.name = "hello";
260  hello.data.resize(hello_len);
261  memcpy(hello.data.data(), hello_world, hello_len);
262  g_files.push_back(hello);
263}
264
265}  // namespace
266
267TEST_F(FuseFsTest, OpenAndRead) {
268  ScopedNode node;
269  ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
270
271  char buffer[15] = {0};
272  int bytes_read = 0;
273  HandleAttr attr;
274  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
275  ASSERT_EQ(strlen(hello_world), bytes_read);
276  ASSERT_STREQ(hello_world, buffer);
277
278  // Try to read past the end of the file.
279  attr.offs = strlen(hello_world) - 7;
280  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
281  ASSERT_EQ(7, bytes_read);
282  ASSERT_STREQ("World!\n", buffer);
283}
284
285TEST_F(FuseFsTest, CreateWithMode) {
286  ScopedNode node;
287  struct stat statbuf;
288
289  ASSERT_EQ(0, fs_.OpenWithMode(Path("/hello"),
290                                O_RDWR | O_CREAT, 0723, &node));
291  EXPECT_EQ(0, node->GetStat(&statbuf));
292  EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
293  EXPECT_EQ(0723, statbuf.st_mode & ~S_IFMT);
294}
295
296TEST_F(FuseFsTest, CreateAndWrite) {
297  ScopedNode node;
298  ASSERT_EQ(0, fs_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node));
299
300  HandleAttr attr;
301  const char message[] = "Something interesting";
302  int bytes_written;
303  ASSERT_EQ(0, node->Write(attr, &message[0], strlen(message), &bytes_written));
304  ASSERT_EQ(bytes_written, strlen(message));
305
306  // Now try to read the data back.
307  char buffer[40] = {0};
308  int bytes_read = 0;
309  ASSERT_EQ(0, node->Read(attr, &buffer[0], sizeof(buffer), &bytes_read));
310  ASSERT_EQ(strlen(message), bytes_read);
311  ASSERT_STREQ(message, buffer);
312}
313
314TEST_F(FuseFsTest, GetStat) {
315  struct stat statbuf;
316  ScopedNode node;
317
318  ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
319  EXPECT_EQ(0, node->GetStat(&statbuf));
320  EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
321  EXPECT_EQ(0666, statbuf.st_mode & ~S_IFMT);
322  EXPECT_EQ(strlen(hello_world), statbuf.st_size);
323
324  ASSERT_EQ(0, fs_.Open(Path("/"), O_RDONLY, &node));
325  EXPECT_EQ(0, node->GetStat(&statbuf));
326  EXPECT_EQ(S_IFDIR, statbuf.st_mode & S_IFMT);
327  EXPECT_EQ(0755, statbuf.st_mode & ~S_IFMT);
328
329  // Create a file and stat.
330  ASSERT_EQ(0, fs_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node));
331  EXPECT_EQ(0, node->GetStat(&statbuf));
332  EXPECT_EQ(S_IFREG, statbuf.st_mode & S_IFMT);
333  EXPECT_EQ(0666, statbuf.st_mode & ~S_IFMT);
334  EXPECT_EQ(0, statbuf.st_size);
335}
336
337TEST_F(FuseFsTest, GetDents) {
338  ScopedNode root;
339
340  ASSERT_EQ(0, fs_.Open(Path("/"), O_RDONLY, &root));
341
342  struct dirent entries[4];
343  int bytes_read;
344
345  // Try reading everything.
346  ASSERT_EQ(0, root->GetDents(0, &entries[0], sizeof(entries), &bytes_read));
347  ASSERT_EQ(3 * sizeof(dirent), bytes_read);
348  EXPECT_STREQ(".", entries[0].d_name);
349  EXPECT_STREQ("..", entries[1].d_name);
350  EXPECT_STREQ("hello", entries[2].d_name);
351
352  // Try reading from an offset.
353  memset(&entries, 0, sizeof(entries));
354  ASSERT_EQ(0, root->GetDents(sizeof(dirent), &entries[0], 2 * sizeof(dirent),
355                              &bytes_read));
356  ASSERT_EQ(2 * sizeof(dirent), bytes_read);
357  EXPECT_STREQ("..", entries[0].d_name);
358  EXPECT_STREQ("hello", entries[1].d_name);
359
360  // Add a file and read again.
361  ScopedNode node;
362  ASSERT_EQ(0, fs_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node));
363  ASSERT_EQ(0, root->GetDents(0, &entries[0], sizeof(entries), &bytes_read));
364  ASSERT_EQ(4 * sizeof(dirent), bytes_read);
365  EXPECT_STREQ(".", entries[0].d_name);
366  EXPECT_STREQ("..", entries[1].d_name);
367  EXPECT_STREQ("hello", entries[2].d_name);
368  EXPECT_STREQ("foobar", entries[3].d_name);
369}
370
371TEST_F(FuseFsTest, Utimens) {
372  struct stat statbuf;
373  ScopedNode node;
374
375  struct timespec times[2];
376  times[0].tv_sec = 1000;
377  times[0].tv_nsec = 2000;
378  times[1].tv_sec = 3000;
379  times[1].tv_nsec = 4000;
380
381  ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
382  EXPECT_EQ(0, node->Futimens(times));
383
384  EXPECT_EQ(0, node->GetStat(&statbuf));
385  EXPECT_EQ(times[0].tv_sec, statbuf.st_atime);
386  EXPECT_EQ(times[0].tv_nsec, statbuf.st_atimensec);
387  EXPECT_EQ(times[1].tv_sec, statbuf.st_mtime);
388  EXPECT_EQ(times[1].tv_nsec, statbuf.st_mtimensec);
389}
390
391TEST_F(FuseFsTest, Fchmod) {
392  struct stat statbuf;
393  ScopedNode node;
394
395  ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
396  ASSERT_EQ(0, node->GetStat(&statbuf));
397  EXPECT_EQ(0666, statbuf.st_mode & ~S_IFMT);
398
399  ASSERT_EQ(0, node->Fchmod(0777));
400
401  ASSERT_EQ(0, node->GetStat(&statbuf));
402  EXPECT_EQ(0777, statbuf.st_mode & ~S_IFMT);
403}
404
405namespace {
406
407class KernelProxyFuseTest : public ::testing::Test {
408 public:
409  KernelProxyFuseTest() {}
410
411  void SetUp();
412  void TearDown();
413
414 private:
415  KernelProxy kp_;
416};
417
418void KernelProxyFuseTest::SetUp() {
419  ASSERT_EQ(0, ki_push_state_for_testing());
420  ASSERT_EQ(0, ki_init(&kp_));
421
422  // Register a fuse filesystem.
423  nacl_io_register_fs_type("flatfs", &g_fuse_operations);
424
425  // Unmount the passthrough FS and mount our fuse filesystem.
426  EXPECT_EQ(0, kp_.umount("/"));
427  EXPECT_EQ(0, kp_.mount("", "/", "flatfs", 0, NULL));
428}
429
430void KernelProxyFuseTest::TearDown() {
431  nacl_io_unregister_fs_type("flatfs");
432  ki_uninit();
433}
434
435}  // namespace
436
437TEST_F(KernelProxyFuseTest, Basic) {
438  // Write a file.
439  int fd = ki_open("/hello", O_WRONLY | O_CREAT, 0777);
440  ASSERT_GT(fd, -1);
441  ASSERT_EQ(sizeof(hello_world),
442            ki_write(fd, hello_world, sizeof(hello_world)));
443  EXPECT_EQ(0, ki_close(fd));
444
445  // Then read it back in.
446  fd = ki_open("/hello", O_RDONLY, 0);
447  ASSERT_GT(fd, -1);
448
449  char buffer[30];
450  memset(buffer, 0, sizeof(buffer));
451  ASSERT_EQ(sizeof(hello_world), ki_read(fd, buffer, sizeof(buffer)));
452  EXPECT_STREQ(hello_world, buffer);
453  EXPECT_EQ(0, ki_close(fd));
454}
455