summaryrefslogtreecommitdiffhomepage
path: root/test/fuse/linux
diff options
context:
space:
mode:
authorCraig Chi <craigchi@google.com>2020-08-10 18:15:32 -0700
committerCraig Chi <craigchi@google.com>2020-08-10 18:15:32 -0700
commit51e64d2fc590b0271d4e0cbbc75882cf81ada182 (patch)
tree1e99952f3b06cb290a414816c21d9a4e698bb1e9 /test/fuse/linux
parentb404b5c255214a37d7f787f9fe24bb8e22509eb4 (diff)
Implement FUSE_GETATTR
FUSE_GETATTR is called when a stat(2), fstat(2), or lstat(2) is issued from VFS2 layer to a FUSE filesystem. Fixes #3175
Diffstat (limited to 'test/fuse/linux')
-rw-r--r--test/fuse/linux/BUILD21
-rw-r--r--test/fuse/linux/fuse_base.cc15
-rw-r--r--test/fuse/linux/fuse_base.h6
-rw-r--r--test/fuse/linux/stat_test.cc169
4 files changed, 197 insertions, 14 deletions
diff --git a/test/fuse/linux/BUILD b/test/fuse/linux/BUILD
index 49dc96c20..4871bb531 100644
--- a/test/fuse/linux/BUILD
+++ b/test/fuse/linux/BUILD
@@ -1,20 +1,31 @@
-load("//tools:defs.bzl", "cc_library", "gtest")
+load("//tools:defs.bzl", "cc_binary", "cc_library", "gtest")
package(
default_visibility = ["//:sandbox"],
licenses = ["notice"],
)
+cc_binary(
+ name = "stat_test",
+ testonly = 1,
+ srcs = ["stat_test.cc"],
+ deps = [
+ gtest,
+ ":fuse_base",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
cc_library(
name = "fuse_base",
testonly = 1,
- srcs = [
- "fuse_base.cc",
- "fuse_base.h",
- ],
+ srcs = ["fuse_base.cc"],
+ hdrs = ["fuse_base.h"],
deps = [
gtest,
"//test/util:posix_error",
+ "//test/util:temp_path",
"//test/util:test_util",
"@com_google_absl//absl/strings:str_format",
],
diff --git a/test/fuse/linux/fuse_base.cc b/test/fuse/linux/fuse_base.cc
index ce69276c9..4a2c64998 100644
--- a/test/fuse/linux/fuse_base.cc
+++ b/test/fuse/linux/fuse_base.cc
@@ -12,23 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "fuse_base.h"
+#include "test/fuse/linux/fuse_base.h"
#include <fcntl.h>
#include <linux/fuse.h>
-#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <iostream>
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
#include "absl/strings/str_format.h"
+#include "gtest/gtest.h"
#include "test/util/posix_error.h"
+#include "test/util/temp_path.h"
#include "test/util/test_util.h"
namespace gvisor {
@@ -78,13 +78,14 @@ void FuseTest::MountFuse() {
EXPECT_THAT(dev_fd_ = open("/dev/fuse", O_RDWR), SyscallSucceeds());
std::string mount_opts = absl::StrFormat("fd=%d,%s", dev_fd_, kMountOpts);
- EXPECT_THAT(mount("fuse", kMountPoint, "fuse", MS_NODEV | MS_NOSUID,
- mount_opts.c_str()),
+ mount_point_ = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
+ EXPECT_THAT(mount("fuse", mount_point_.path().c_str(), "fuse",
+ MS_NODEV | MS_NOSUID, mount_opts.c_str()),
SyscallSucceedsWithValue(0));
}
void FuseTest::UnmountFuse() {
- EXPECT_THAT(umount(kMountPoint), SyscallSucceeds());
+ EXPECT_THAT(umount(mount_point_.path().c_str()), SyscallSucceeds());
// TODO(gvisor.dev/issue/3330): ensure the process is terminated successfully.
}
diff --git a/test/fuse/linux/fuse_base.h b/test/fuse/linux/fuse_base.h
index b008778de..3a2f255a9 100644
--- a/test/fuse/linux/fuse_base.h
+++ b/test/fuse/linux/fuse_base.h
@@ -20,14 +20,13 @@
#include <vector>
-#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "test/util/posix_error.h"
+#include "test/util/temp_path.h"
namespace gvisor {
namespace testing {
-constexpr char kMountPoint[] = "/mnt";
constexpr char kMountOpts[] = "rootmode=755,user_id=0,group_id=0";
class FuseTest : public ::testing::Test {
@@ -55,6 +54,9 @@ class FuseTest : public ::testing::Test {
// complains if the FUSE server responds failure during tests.
void WaitCompleted();
+ protected:
+ TempPath mount_point_;
+
private:
void MountFuse();
void UnmountFuse();
diff --git a/test/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc
new file mode 100644
index 000000000..172e09867
--- /dev/null
+++ b/test/fuse/linux/stat_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fuse.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "test/fuse/linux/fuse_base.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+class StatTest : public FuseTest {
+ public:
+ bool CompareRequest(void* expected_mem, size_t expected_len, void* real_mem,
+ size_t real_len) override {
+ if (expected_len != real_len) return false;
+ struct fuse_in_header* real_header =
+ reinterpret_cast<fuse_in_header*>(real_mem);
+
+ if (real_header->opcode != FUSE_GETATTR) {
+ std::cerr << "expect header opcode " << FUSE_GETATTR << " but got "
+ << real_header->opcode << std::endl;
+ return false;
+ }
+ return true;
+ }
+
+ bool StatsAreEqual(struct stat expected, struct stat actual) {
+ // device number will be dynamically allocated by kernel, we cannot know
+ // in advance
+ actual.st_dev = expected.st_dev;
+ return memcmp(&expected, &actual, sizeof(struct stat)) == 0;
+ }
+};
+
+TEST_F(StatTest, StatNormal) {
+ struct iovec iov_in[2];
+ struct iovec iov_out[2];
+
+ struct fuse_in_header in_header = {
+ .len = sizeof(struct fuse_in_header) + sizeof(struct fuse_getattr_in),
+ .opcode = FUSE_GETATTR,
+ .unique = 4,
+ .nodeid = 1,
+ .uid = 0,
+ .gid = 0,
+ .pid = 4,
+ .padding = 0,
+ };
+ struct fuse_getattr_in in_payload = {0};
+ iov_in[0].iov_len = sizeof(in_header);
+ iov_in[0].iov_base = &in_header;
+ iov_in[1].iov_len = sizeof(in_payload);
+ iov_in[1].iov_base = &in_payload;
+
+ mode_t expected_mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ struct timespec atime = {.tv_sec = 1595436289, .tv_nsec = 134150844};
+ struct timespec mtime = {.tv_sec = 1595436290, .tv_nsec = 134150845};
+ struct timespec ctime = {.tv_sec = 1595436291, .tv_nsec = 134150846};
+ struct fuse_out_header out_header = {
+ .len = sizeof(struct fuse_out_header) + sizeof(struct fuse_attr_out),
+ .error = 0,
+ .unique = 4,
+ };
+ struct fuse_attr attr = {
+ .ino = 1,
+ .size = 512,
+ .blocks = 4,
+ .atime = static_cast<uint64_t>(atime.tv_sec),
+ .mtime = static_cast<uint64_t>(mtime.tv_sec),
+ .ctime = static_cast<uint64_t>(ctime.tv_sec),
+ .atimensec = static_cast<uint32_t>(atime.tv_nsec),
+ .mtimensec = static_cast<uint32_t>(mtime.tv_nsec),
+ .ctimensec = static_cast<uint32_t>(ctime.tv_nsec),
+ .mode = expected_mode,
+ .nlink = 2,
+ .uid = 1234,
+ .gid = 4321,
+ .rdev = 12,
+ .blksize = 4096,
+ };
+ struct fuse_attr_out out_payload = {
+ .attr = attr,
+ };
+ iov_out[0].iov_len = sizeof(out_header);
+ iov_out[0].iov_base = &out_header;
+ iov_out[1].iov_len = sizeof(out_payload);
+ iov_out[1].iov_base = &out_payload;
+
+ SetExpected(iov_in, 2, iov_out, 2);
+
+ struct stat stat_buf;
+ EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf), SyscallSucceeds());
+
+ struct stat expected_stat = {
+ .st_ino = attr.ino,
+ .st_nlink = attr.nlink,
+ .st_mode = expected_mode,
+ .st_uid = attr.uid,
+ .st_gid = attr.gid,
+ .st_rdev = attr.rdev,
+ .st_size = static_cast<off_t>(attr.size),
+ .st_blksize = attr.blksize,
+ .st_blocks = static_cast<blkcnt_t>(attr.blocks),
+ .st_atim = atime,
+ .st_mtim = mtime,
+ .st_ctim = ctime,
+ };
+ EXPECT_TRUE(StatsAreEqual(stat_buf, expected_stat));
+ WaitCompleted();
+}
+
+TEST_F(StatTest, StatNotFound) {
+ struct iovec iov_in[2];
+ struct iovec iov_out[2];
+
+ struct fuse_in_header in_header = {
+ .len = sizeof(struct fuse_in_header) + sizeof(struct fuse_getattr_in),
+ .opcode = FUSE_GETATTR,
+ .unique = 4,
+ };
+ struct fuse_getattr_in in_payload = {0};
+ iov_in[0].iov_len = sizeof(in_header);
+ iov_in[0].iov_base = &in_header;
+ iov_in[1].iov_len = sizeof(in_payload);
+ iov_in[1].iov_base = &in_payload;
+
+ struct fuse_out_header out_header = {
+ .len = sizeof(struct fuse_out_header),
+ .error = -ENOENT,
+ .unique = 4,
+ };
+ iov_out[0].iov_len = sizeof(out_header);
+ iov_out[0].iov_base = &out_header;
+
+ SetExpected(iov_in, 2, iov_out, 1);
+
+ struct stat stat_buf;
+ EXPECT_THAT(stat(mount_point_.path().c_str(), &stat_buf),
+ SyscallFailsWithErrno(ENOENT));
+ WaitCompleted();
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor