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(×, 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