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.
7 #include <gtest/gtest.h>
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"
19 using namespace nacl_io;
23 class FuseFsForTesting : public FuseFs {
25 explicit FuseFsForTesting(fuse_operations* fuse_ops) {
27 args.fuse_ops = fuse_ops;
28 EXPECT_EQ(0, Init(args));
32 // Implementation of a simple flat memory filesystem.
34 File() : mode(0666) { memset(×, 0, sizeof(times)); }
37 std::vector<uint8_t> data;
42 typedef std::vector<File> Files;
45 bool IsValidPath(const char* path) {
49 if (strlen(path) <= 1)
58 File* FindFile(const char* path) {
59 if (!IsValidPath(path))
62 for (Files::iterator iter = g_files.begin(); iter != g_files.end(); ++iter) {
63 if (iter->name == &path[1])
70 int testfs_getattr(const char* path, struct stat* stbuf) {
71 memset(stbuf, 0, sizeof(struct stat));
73 if (strcmp(path, "/") == 0) {
74 stbuf->st_mode = S_IFDIR | 0755;
78 File* file = FindFile(path);
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;
91 int testfs_readdir(const char* path,
93 fuse_fill_dir_t filler,
95 struct fuse_file_info*) {
96 if (strcmp(path, "/") != 0)
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);
107 int testfs_create(const char* path, mode_t mode, struct fuse_file_info* fi) {
108 if (!IsValidPath(path))
111 File* file = FindFile(path);
113 if (fi->flags & O_EXCL)
116 g_files.push_back(File());
117 file = &g_files.back();
118 file->name = &path[1]; // Skip initial /
125 int 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;
132 int testfs_read(const char* path,
136 struct fuse_file_info* fi) {
137 File* file = FindFile(path);
141 size_t filesize = file->data.size();
142 // Trying to read past the end of the file.
143 if (offset >= filesize)
146 if (offset + size > filesize)
147 size = filesize - offset;
149 memcpy(buf, file->data.data() + offset, size);
153 int testfs_write(const char* path,
157 struct fuse_file_info*) {
158 File* file = FindFile(path);
162 size_t filesize = file->data.size();
164 if (offset + size > filesize)
165 file->data.resize(offset + size);
167 memcpy(file->data.data() + offset, buf, size);
171 int testfs_utimens(const char* path, const struct timespec times[2]) {
172 File* file = FindFile(path);
176 file->times[0] = times[0];
177 file->times[1] = times[1];
181 int testfs_chmod(const char* path, mode_t mode) {
182 File* file = FindFile(path);
190 const char hello_world[] = "Hello, World!\n";
192 fuse_operations g_fuse_operations = {
195 testfs_getattr, // getattr
204 testfs_chmod, // chmod
209 testfs_write, // write
219 testfs_readdir, // readdir
225 testfs_create, // create
229 testfs_utimens, // utimens
239 class FuseFsTest : public ::testing::Test {
246 FuseFsForTesting fs_;
249 FuseFsTest::FuseFsTest() : fs_(&g_fuse_operations) {}
251 void FuseFsTest::SetUp() {
252 // Reset the filesystem.
255 // Add a built-in file.
256 size_t hello_len = strlen(hello_world);
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);
267 TEST_F(FuseFsTest, OpenAndRead) {
269 ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
271 char buffer[15] = {0};
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);
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);
285 TEST_F(FuseFsTest, CreateWithMode) {
289 ASSERT_EQ(0, fs_.OpenWithMode(Path("/hello"),
290 O_RDWR | O_CREAT, 0723, &node));
291 EXPECT_EQ(0, node->GetStat(&statbuf));
292 EXPECT_TRUE(S_ISREG(statbuf.st_mode));
293 EXPECT_EQ(0723, statbuf.st_mode & S_MODEBITS);
296 TEST_F(FuseFsTest, CreateAndWrite) {
298 ASSERT_EQ(0, fs_.Open(Path("/foobar"), O_RDWR | O_CREAT, &node));
301 const char message[] = "Something interesting";
303 ASSERT_EQ(0, node->Write(attr, &message[0], strlen(message), &bytes_written));
304 ASSERT_EQ(bytes_written, strlen(message));
306 // Now try to read the data back.
307 char buffer[40] = {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);
314 TEST_F(FuseFsTest, GetStat) {
318 ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
319 EXPECT_EQ(0, node->GetStat(&statbuf));
320 EXPECT_TRUE(S_ISREG(statbuf.st_mode));
321 EXPECT_EQ(0666, statbuf.st_mode & S_MODEBITS);
322 EXPECT_EQ(strlen(hello_world), statbuf.st_size);
324 ASSERT_EQ(0, fs_.Open(Path("/"), O_RDONLY, &node));
325 EXPECT_EQ(0, node->GetStat(&statbuf));
326 EXPECT_TRUE(S_ISDIR(statbuf.st_mode));
327 EXPECT_EQ(0755, statbuf.st_mode & S_MODEBITS);
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_TRUE(S_ISREG(statbuf.st_mode));
333 EXPECT_EQ(0666, statbuf.st_mode & S_MODEBITS);
334 EXPECT_EQ(0, statbuf.st_size);
337 TEST_F(FuseFsTest, GetDents) {
340 ASSERT_EQ(0, fs_.Open(Path("/"), O_RDONLY, &root));
342 struct dirent entries[4];
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);
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),
356 ASSERT_EQ(2 * sizeof(dirent), bytes_read);
357 EXPECT_STREQ("..", entries[0].d_name);
358 EXPECT_STREQ("hello", entries[1].d_name);
360 // Add a file and read again.
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);
371 TEST_F(FuseFsTest, Utimens) {
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;
381 ASSERT_EQ(0, fs_.Open(Path("/hello"), O_RDONLY, &node));
382 EXPECT_EQ(0, node->Futimens(times));
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);
391 TEST_F(FuseFsTest, Fchmod) {
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_MODEBITS);
399 ASSERT_EQ(0, node->Fchmod(0777));
401 ASSERT_EQ(0, node->GetStat(&statbuf));
402 EXPECT_EQ(0777, statbuf.st_mode & S_MODEBITS);
407 class KernelProxyFuseTest : public ::testing::Test {
409 KernelProxyFuseTest() {}
418 void KernelProxyFuseTest::SetUp() {
419 ASSERT_EQ(0, ki_push_state_for_testing());
420 ASSERT_EQ(0, ki_init(&kp_));
422 // Register a fuse filesystem.
423 nacl_io_register_fs_type("flatfs", &g_fuse_operations);
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));
430 void KernelProxyFuseTest::TearDown() {
431 nacl_io_unregister_fs_type("flatfs");
437 TEST_F(KernelProxyFuseTest, Basic) {
439 int fd = ki_open("/hello", O_WRONLY | O_CREAT, 0777);
441 ASSERT_EQ(sizeof(hello_world),
442 ki_write(fd, hello_world, sizeof(hello_world)));
443 EXPECT_EQ(0, ki_close(fd));
445 // Then read it back in.
446 fd = ki_open("/hello", O_RDONLY, 0);
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));