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
));