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 if (strcmp(path
, "/foo") == 0) {
79 stbuf
->st_mode
= S_IFDIR
| 0755;
83 File
* file
= FindFile(path
);
87 stbuf
->st_mode
= S_IFREG
| file
->mode
;
88 stbuf
->st_size
= file
->data
.size();
89 stbuf
->st_atime
= file
->times
[0].tv_sec
;
90 stbuf
->st_atimensec
= file
->times
[0].tv_nsec
;
91 stbuf
->st_mtime
= file
->times
[1].tv_sec
;
92 stbuf
->st_mtimensec
= file
->times
[1].tv_nsec
;
96 int testfs_readdir(const char* path
,
98 fuse_fill_dir_t filler
,
100 struct fuse_file_info
*) {
101 if (strcmp(path
, "/") != 0)
104 filler(buf
, ".", NULL
, 0);
105 filler(buf
, "..", NULL
, 0);
106 for (Files::iterator iter
= g_files
.begin(); iter
!= g_files
.end(); ++iter
) {
107 filler(buf
, iter
->name
.c_str(), NULL
, 0);
112 int testfs_create(const char* path
, mode_t mode
, struct fuse_file_info
* fi
) {
113 if (!IsValidPath(path
))
116 File
* file
= FindFile(path
);
118 if (fi
->flags
& O_EXCL
)
121 g_files
.push_back(File());
122 file
= &g_files
.back();
123 file
->name
= &path
[1]; // Skip initial /
130 int testfs_open(const char* path
, struct fuse_file_info
*) {
131 // open is only called to open an existing file, otherwise create is
132 // called. We don't need to do any additional work here, the path will be
133 // passed to any other operations.
134 return FindFile(path
) != NULL
;
137 int testfs_read(const char* path
,
141 struct fuse_file_info
* fi
) {
142 File
* file
= FindFile(path
);
146 size_t filesize
= file
->data
.size();
147 // Trying to read past the end of the file.
148 if (offset
>= filesize
)
151 if (offset
+ size
> filesize
)
152 size
= filesize
- offset
;
154 memcpy(buf
, file
->data
.data() + offset
, size
);
158 int testfs_write(const char* path
,
162 struct fuse_file_info
*) {
163 File
* file
= FindFile(path
);
167 size_t filesize
= file
->data
.size();
169 if (offset
+ size
> filesize
)
170 file
->data
.resize(offset
+ size
);
172 memcpy(file
->data
.data() + offset
, buf
, size
);
176 int testfs_utimens(const char* path
, const struct timespec times
[2]) {
177 File
* file
= FindFile(path
);
181 file
->times
[0] = times
[0];
182 file
->times
[1] = times
[1];
186 int testfs_chmod(const char* path
, mode_t mode
) {
187 File
* file
= FindFile(path
);
195 const char hello_world
[] = "Hello, World!\n";
197 fuse_operations g_fuse_operations
= {
200 testfs_getattr
, // getattr
209 testfs_chmod
, // chmod
214 testfs_write
, // write
224 testfs_readdir
, // readdir
230 testfs_create
, // create
234 testfs_utimens
, // utimens
244 class FuseFsTest
: public ::testing::Test
{
251 FuseFsForTesting fs_
;
254 FuseFsTest::FuseFsTest() : fs_(&g_fuse_operations
) {}
256 void FuseFsTest::SetUp() {
257 // Reset the filesystem.
260 // Add a built-in file.
261 size_t hello_len
= strlen(hello_world
);
264 hello
.name
= "hello";
265 hello
.data
.resize(hello_len
);
266 memcpy(hello
.data
.data(), hello_world
, hello_len
);
267 g_files
.push_back(hello
);
272 TEST_F(FuseFsTest
, OpenAndRead
) {
274 ASSERT_EQ(0, fs_
.Open(Path("/hello"), O_RDONLY
, &node
));
276 char buffer
[15] = {0};
279 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
280 ASSERT_EQ(strlen(hello_world
), bytes_read
);
281 ASSERT_STREQ(hello_world
, buffer
);
283 // Try to read past the end of the file.
284 attr
.offs
= strlen(hello_world
) - 7;
285 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
286 ASSERT_EQ(7, bytes_read
);
287 ASSERT_STREQ("World!\n", buffer
);
290 TEST_F(FuseFsTest
, CreateWithMode
) {
294 ASSERT_EQ(0, fs_
.OpenWithMode(Path("/hello"),
295 O_RDWR
| O_CREAT
, 0723, &node
));
296 EXPECT_EQ(0, node
->GetStat(&statbuf
));
297 EXPECT_TRUE(S_ISREG(statbuf
.st_mode
));
298 EXPECT_EQ(0723, statbuf
.st_mode
& S_MODEBITS
);
301 TEST_F(FuseFsTest
, CreateAndWrite
) {
303 ASSERT_EQ(0, fs_
.Open(Path("/foobar"), O_RDWR
| O_CREAT
, &node
));
306 const char message
[] = "Something interesting";
308 ASSERT_EQ(0, node
->Write(attr
, &message
[0], strlen(message
), &bytes_written
));
309 ASSERT_EQ(bytes_written
, strlen(message
));
311 // Now try to read the data back.
312 char buffer
[40] = {0};
314 ASSERT_EQ(0, node
->Read(attr
, &buffer
[0], sizeof(buffer
), &bytes_read
));
315 ASSERT_EQ(strlen(message
), bytes_read
);
316 ASSERT_STREQ(message
, buffer
);
319 TEST_F(FuseFsTest
, GetStat
) {
323 ASSERT_EQ(0, fs_
.Open(Path("/hello"), O_RDONLY
, &node
));
324 EXPECT_EQ(0, node
->GetStat(&statbuf
));
325 EXPECT_TRUE(S_ISREG(statbuf
.st_mode
));
326 EXPECT_EQ(0666, statbuf
.st_mode
& S_MODEBITS
);
327 EXPECT_EQ(strlen(hello_world
), statbuf
.st_size
);
329 ASSERT_EQ(0, fs_
.Open(Path("/"), O_RDONLY
, &node
));
330 EXPECT_EQ(0, node
->GetStat(&statbuf
));
331 EXPECT_TRUE(S_ISDIR(statbuf
.st_mode
));
332 EXPECT_EQ(0755, statbuf
.st_mode
& S_MODEBITS
);
334 // Create a file and stat.
335 ASSERT_EQ(0, fs_
.Open(Path("/foobar"), O_RDWR
| O_CREAT
, &node
));
336 EXPECT_EQ(0, node
->GetStat(&statbuf
));
337 EXPECT_TRUE(S_ISREG(statbuf
.st_mode
));
338 EXPECT_EQ(0666, statbuf
.st_mode
& S_MODEBITS
);
339 EXPECT_EQ(0, statbuf
.st_size
);
342 TEST_F(FuseFsTest
, GetDents
) {
345 ASSERT_EQ(0, fs_
.Open(Path("/"), O_RDONLY
, &root
));
347 struct dirent entries
[4];
350 // Try reading everything.
351 ASSERT_EQ(0, root
->GetDents(0, &entries
[0], sizeof(entries
), &bytes_read
));
352 ASSERT_EQ(3 * sizeof(dirent
), bytes_read
);
353 EXPECT_STREQ(".", entries
[0].d_name
);
354 EXPECT_STREQ("..", entries
[1].d_name
);
355 EXPECT_STREQ("hello", entries
[2].d_name
);
357 // Try reading from an offset.
358 memset(&entries
, 0, sizeof(entries
));
359 ASSERT_EQ(0, root
->GetDents(sizeof(dirent
), &entries
[0], 2 * sizeof(dirent
),
361 ASSERT_EQ(2 * sizeof(dirent
), bytes_read
);
362 EXPECT_STREQ("..", entries
[0].d_name
);
363 EXPECT_STREQ("hello", entries
[1].d_name
);
365 // Add a file and read again.
367 ASSERT_EQ(0, fs_
.Open(Path("/foobar"), O_RDWR
| O_CREAT
, &node
));
368 ASSERT_EQ(0, root
->GetDents(0, &entries
[0], sizeof(entries
), &bytes_read
));
369 ASSERT_EQ(4 * sizeof(dirent
), bytes_read
);
370 EXPECT_STREQ(".", entries
[0].d_name
);
371 EXPECT_STREQ("..", entries
[1].d_name
);
372 EXPECT_STREQ("hello", entries
[2].d_name
);
373 EXPECT_STREQ("foobar", entries
[3].d_name
);
376 TEST_F(FuseFsTest
, Utimens
) {
380 struct timespec times
[2];
381 times
[0].tv_sec
= 1000;
382 times
[0].tv_nsec
= 2000;
383 times
[1].tv_sec
= 3000;
384 times
[1].tv_nsec
= 4000;
386 ASSERT_EQ(0, fs_
.Open(Path("/hello"), O_RDONLY
, &node
));
387 EXPECT_EQ(0, node
->Futimens(times
));
389 EXPECT_EQ(0, node
->GetStat(&statbuf
));
390 EXPECT_EQ(times
[0].tv_sec
, statbuf
.st_atime
);
391 EXPECT_EQ(times
[0].tv_nsec
, statbuf
.st_atimensec
);
392 EXPECT_EQ(times
[1].tv_sec
, statbuf
.st_mtime
);
393 EXPECT_EQ(times
[1].tv_nsec
, statbuf
.st_mtimensec
);
396 TEST_F(FuseFsTest
, Fchmod
) {
400 ASSERT_EQ(0, fs_
.Open(Path("/hello"), O_RDONLY
, &node
));
401 ASSERT_EQ(0, node
->GetStat(&statbuf
));
402 EXPECT_EQ(0666, statbuf
.st_mode
& S_MODEBITS
);
404 ASSERT_EQ(0, node
->Fchmod(0777));
406 ASSERT_EQ(0, node
->GetStat(&statbuf
));
407 EXPECT_EQ(0777, statbuf
.st_mode
& S_MODEBITS
);
412 class KernelProxyFuseTest
: public ::testing::Test
{
414 KernelProxyFuseTest() {}
423 void KernelProxyFuseTest::SetUp() {
424 ASSERT_EQ(0, ki_push_state_for_testing());
425 ASSERT_EQ(0, ki_init(&kp_
));
427 // Register a fuse filesystem.
428 nacl_io_register_fs_type("flatfs", &g_fuse_operations
);
430 // Unmount the passthrough FS and mount our fuse filesystem.
431 EXPECT_EQ(0, kp_
.umount("/"));
432 EXPECT_EQ(0, kp_
.mount("", "/", "flatfs", 0, NULL
));
435 void KernelProxyFuseTest::TearDown() {
436 nacl_io_unregister_fs_type("flatfs");
442 TEST_F(KernelProxyFuseTest
, Basic
) {
444 int fd
= ki_open("/hello", O_WRONLY
| O_CREAT
, 0777);
446 ASSERT_EQ(sizeof(hello_world
),
447 ki_write(fd
, hello_world
, sizeof(hello_world
)));
448 EXPECT_EQ(0, ki_close(fd
));
450 // Then read it back in.
451 fd
= ki_open("/hello", O_RDONLY
, 0);
455 memset(buffer
, 0, sizeof(buffer
));
456 ASSERT_EQ(sizeof(hello_world
), ki_read(fd
, buffer
, sizeof(buffer
)));
457 EXPECT_STREQ(hello_world
, buffer
);
458 EXPECT_EQ(0, ki_close(fd
));
461 TEST_F(KernelProxyFuseTest
, Chdir
) {
462 ASSERT_EQ(0, ki_chdir("/foo"));